X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=KernelLand%2FModules%2FUSB%2FUHCI%2Fuhci.c;h=e57a1c300ca9c38da964ed1265bdc49c861ffb69;hb=3d85d14c564e245c00d31b1adf9c4ee7c2d9754a;hp=7b40b143dfbe984faa7b2783037a92a3de3b2572;hpb=51ab5f489bc356940c95cc936fd0508e8f07ea97;p=tpg%2Facess2.git diff --git a/KernelLand/Modules/USB/UHCI/uhci.c b/KernelLand/Modules/USB/UHCI/uhci.c index 7b40b143..e57a1c30 100644 --- a/KernelLand/Modules/USB/UHCI/uhci.c +++ b/KernelLand/Modules/USB/UHCI/uhci.c @@ -12,23 +12,40 @@ #include #include #include "uhci.h" +#include +#include // === CONSTANTS === -#define MAX_CONTROLLERS 4 -#define NUM_TDs 1024 +#define MAX_CONTROLLERS 8 +//#define NUM_TDs 1024 +#define NUM_TDs (PAGE_SIZE/sizeof(tUHCI_TD)) +#define MAX_PACKET_SIZE 0x400 +#define MAX_INTERRUPT_LOAD 1024 // Maximum bytes per frame for interrupts + +#define PID_IN 0x69 +#define PID_OUT 0xE1 +#define PID_SETUP 0x2D // === PROTOTYPES === int UHCI_Initialise(char **Arguments); void UHCI_Cleanup(); + int UHCI_int_InitHost(tUHCI_Controller *Host); +// -- List internals tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont); -void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_TD *TD); -void *UHCI_int_SendTransaction(tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); -void *UHCI_DataIN(void *Ptr, int Fcn, int Endpt, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); -void *UHCI_DataOUT(void *Ptr, int Fcn, int Endpt, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); -void *UHCI_SendSetup(void *Ptr, int Fcn, int Endpt, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); - int UHCI_IsTransferComplete(void *Ptr, void *Handle); - int UHCI_Int_InitHost(tUHCI_Controller *Host); +void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_QH *QH, tUHCI_TD *TD); +tUHCI_TD *UHCI_int_CreateTD(tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); +// --- API +void *UHCI_InterruptIN(void *Ptr, int Dest, int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); +void *UHCI_InterruptOUT(void *Ptr, int Dest, int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); +void UHCI_StopInterrupt(void *Ptr, void *Handle); +void *UHCI_ControlSETUP(void *Ptr, int Dest, int Tgl, void *Data, size_t Length); +void *UHCI_ControlOUT(void *Ptr, int Dest, int Tgl, tUSBHostCb Cb, void *CbData, void *Data, size_t Length); +void *UHCI_ControlIN(void *Ptr, int Dest, int Tgl, tUSBHostCb Cb, void *CbData, void *Data, size_t Length); +void *UHCI_BulkOUT(void *Ptr, int Dest, int bToggle, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); +void *UHCI_BulkIN(void *Ptr, int Dest, int bToggle, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length); + void UHCI_CheckPortUpdate(void *Ptr); +void UHCI_int_InterruptThread(void *Unused); void UHCI_InterruptHandler(int IRQ, void *Ptr); // static void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value); @@ -38,15 +55,23 @@ static Uint16 _InWord(tUHCI_Controller *Host, int Reg); // === GLOBALS === MODULE_DEFINE(0, VERSION, USB_UHCI, UHCI_Initialise, NULL, "USB_Core", NULL); -tUHCI_TD gaUHCI_TDPool[NUM_TDs]; +tUHCI_TD *gaUHCI_TDPool; tUHCI_Controller gUHCI_Controllers[MAX_CONTROLLERS]; tUSBHostDef gUHCI_HostDef = { - .SendIN = UHCI_DataIN, - .SendOUT = UHCI_DataOUT, - .SendSETUP = UHCI_SendSetup, - .CheckPorts = UHCI_CheckPortUpdate, - .IsOpComplete = UHCI_IsTransferComplete + .InterruptIN = UHCI_InterruptIN, + .InterruptOUT = UHCI_InterruptOUT, + .StopInterrupt = UHCI_StopInterrupt, + + .ControlSETUP = UHCI_ControlSETUP, + .ControlIN = UHCI_ControlIN, + .ControlOUT = UHCI_ControlOUT, + + .BulkOUT = UHCI_BulkOUT, + .BulkIN = UHCI_BulkIN, + + .CheckPorts = UHCI_CheckPortUpdate }; +tSemaphore gUHCI_InterruptSempahore; // === CODE === /** @@ -60,6 +85,21 @@ int UHCI_Initialise(char **Arguments) ENTER(""); + // Initialise with no maximum value + Semaphore_Init( &gUHCI_InterruptSempahore, 0, 0, "UHCI", "Interrupt Queue"); + + if( PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, -1) < 0 ) + { + LEAVE('i', MODULE_ERR_NOTNEEDED); + return MODULE_ERR_NOTNEEDED; + } + + { + tPAddr tmp; + gaUHCI_TDPool = (void *) MM_AllocDMA(1, 32, &tmp); + memset(gaUHCI_TDPool, 0, PAGE_SIZE); + } + // Enumerate PCI Bus, getting a maximum of `MAX_CONTROLLERS` devices while( (id = PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS ) { @@ -87,12 +127,16 @@ int UHCI_Initialise(char **Arguments) IRQ_AddHandler(cinfo->IRQNum, UHCI_InterruptHandler, cinfo); // Initialise Host - ret = UHCI_Int_InitHost(&gUHCI_Controllers[i]); + ret = UHCI_int_InitHost(cinfo); // Detect an error if(ret != 0) { LEAVE('i', ret); return ret; } + + // Spin off interrupt handling thread + Proc_SpawnWorker( UHCI_int_InterruptThread, cinfo ); + cinfo->RootHub = USB_RegisterHost(&gUHCI_HostDef, cinfo, 2); LOG("cinfo->RootHub = %p", cinfo->RootHub); @@ -100,11 +144,6 @@ int UHCI_Initialise(char **Arguments) i ++; } - if(i == 0) { - LEAVE('i', MODULE_ERR_NOTNEEDED); - return MODULE_ERR_NOTNEEDED; - } - if(i == MAX_CONTROLLERS) { Log_Warning("UHCI", "Over "EXPAND_STR(MAX_CONTROLLERS)" UHCI controllers detected, ignoring rest"); } @@ -120,78 +159,160 @@ void UHCI_Cleanup() { } -tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont) +/** + * \brief Initialises a UHCI host controller + * \param Host Pointer - Host to initialise + */ +int UHCI_int_InitHost(tUHCI_Controller *Host) { - int i; - for(i = 0; i < NUM_TDs; i ++) + ENTER("pHost", Host); + + // - 1 Page, 32-bit address + // - 1 page = 1024 4 byte entries + Host->FrameList = (void *) MM_AllocDMA(1, 32, &Host->PhysFrameList); + if( !Host->FrameList ) { + Log_Warning("UHCI", "Unable to allocate frame list, aborting"); + LEAVE('i', -1); + return -1; + } + + Host->TDQHPage = (void *) MM_AllocDMA(1, 32, &Host->PhysTDQHPage); + if( !Host->TDQHPage ) { + // TODO: Clean up + Log_Warning("UHCI", "Unable to allocate QH page, aborting"); + LEAVE('i', -1); + return -1; + } + + // Fill frame list + // - The numbers 0...31, but bit reversed (16 (0b1000) = 1 (0b00001) + const int dest_offsets[] = { + 0,16,8,24,4,20,12,28,2,18,10,26,6,22,14,30, + 1,17,9,25,5,21,13,29,3,19,11,27,7,23,15,31 + }; + for( int i = 0; i < 1024; i ++ ) { + Uint32 addr = MM_GetPhysAddr( (tVAddr)&Host->TDQHPage->ControlQH ); + Host->FrameList[i] = addr | 2; + } + for( int i = 0; i < 64; i ++ ) { + int ofs = dest_offsets[ i & (32-1) ] * 2 + (i >= 32); + Uint32 addr = Host->PhysTDQHPage + ofs * sizeof(tUHCI_QH); + LOG("Slot %i to (%i,%i,%i,%i) ms slots", + ofs, 0 + i*4, 256 + i*4, 512 + i*4, 768 + i*4); + Host->FrameList[ 0 + i*4] = addr | 2; + Host->FrameList[256 + i*4] = addr | 2; + Host->FrameList[512 + i*4] = addr | 2; + Host->FrameList[768 + i*4] = addr | 2; + } + + // Build up interrupt binary tree { - if(gaUHCI_TDPool[i].Link == 0) { - gaUHCI_TDPool[i].Link = 1; - gaUHCI_TDPool[i].Control = 1 << 23; - return &gaUHCI_TDPool[i]; + tUHCI_QH *dest = Host->TDQHPage->InterruptQHs; + Uint32 destphys = Host->PhysTDQHPage; + + // Set up next pointer to index to i/2 in the next step + for( int _count = 64; _count > 1; _count /= 2 ) + { + for( int i = 0; i < _count; i ++ ) { + dest[i].Next = destphys + (_count + i/2) * sizeof(tUHCI_QH) + 2; + dest[i].Child = 1; + } + dest += _count; destphys += _count * sizeof(tUHCI_QH); } - // Still in use? Skip - if( gaUHCI_TDPool[i].Control & (1 << 23) ) - continue ; - // Is there a callback on it? Skip - if( gaUHCI_TDPool[i]._info.Callback ) - continue ; - // TODO: Garbage collect, but that means removing from the list too - #if 0 - // Ok, this is actually unused - gaUHCI_TDPool[i].Link = 1; - gaUHCI_TDPool[i].Control = 1 << 23; - return &gaUHCI_TDPool[i]; - #endif + // Skip padding, and move to control QH + dest->Next = MM_GetPhysAddr( (tVAddr)&Host->TDQHPage->BulkQH ) | 2; + dest->Child = 1; } - return NULL; + + // Set up control and bulk queues + Host->TDQHPage->ControlQH.Next = MM_GetPhysAddr( (tVAddr)&Host->TDQHPage->BulkQH ) | 2; + Host->TDQHPage->ControlQH.Child = 1; + Host->TDQHPage->BulkQH.Next = 1; + Host->TDQHPage->BulkQH.Child = 1; + + // Global reset + _OutWord( Host, USBCMD, 4 ); + Time_Delay(10); + _OutWord( Host, USBCMD, 0 ); + + // Allocate Frame List + // Set frame length to 1 ms + _OutByte( Host, SOFMOD, 64 ); + + // Set Frame List + _OutDWord( Host, FLBASEADD, Host->PhysFrameList ); + _OutWord( Host, FRNUM, 0 ); + + // Enable Interrupts + _OutWord( Host, USBINTR, 0x000F ); + PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 ); + + // Enable processing + _OutWord( Host, USBCMD, 0x0001 ); + + LEAVE('i', 0); + return 0; } -tUHCI_TD *UHCI_int_GetTDFromPhys(tPAddr PAddr) +// -------------------------------------------------------------------- +// TDs and QH Allocation/Appending +// -------------------------------------------------------------------- +tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont) { - // TODO: Fix this to work with a non-contiguous pool - static tPAddr td_pool_base; - const int pool_size = NUM_TDs; - int offset; - if(!td_pool_base) td_pool_base = MM_GetPhysAddr( (tVAddr)gaUHCI_TDPool ); - offset = (PAddr - td_pool_base) / sizeof(gaUHCI_TDPool[0]); - if( offset < 0 || offset >= pool_size ) + static tMutex lock; + Mutex_Acquire( &lock ); + for( int i = 0; i < NUM_TDs; i ++ ) { - Log_Error("UHCI", "TD PAddr %P not from pool", PAddr); - return NULL; + if(gaUHCI_TDPool[i]._info.bActive == 0) + { + gaUHCI_TDPool[i].Link = 1; + gaUHCI_TDPool[i].Control = TD_CTL_ACTIVE; + gaUHCI_TDPool[i]._info.bActive = 1; + gaUHCI_TDPool[i]._info.QueueIndex = 128; + Mutex_Release( &lock ); + return &gaUHCI_TDPool[i]; + } } - return gaUHCI_TDPool + offset; + Mutex_Release( &lock ); + return NULL; } -void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_TD *TD) +void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_QH *QH, tUHCI_TD *TD) { - int next_frame = (_InWord(Cont, FRNUM) + 2) & (1024-1); - tUHCI_TD *prev_td; - Uint32 link; + static tMutex lock; // TODO: Should I use a shortlock (avoid being preempted) - // TODO: How to handle FRNUM incrementing while we are in this function? + Mutex_Acquire(&lock); + + // Ensure that there is an interrupt for each used frame + TD->Control |= TD_CTL_IOC; + TD->_info.QueueIndex = ((tVAddr)QH - (tVAddr)Cont->TDQHPage->InterruptQHs) / sizeof(tUHCI_QH); + LOG("TD(%p)->QueueIndex = %i", TD, TD->_info.QueueIndex); + // Update length + TD->Control &= ~0x7FF; + TD->Control |= (TD->Token >> 21) & 0x7FF; + + // Stop controller + _OutWord( Cont, USBCMD, 0x0000 ); + + // Add + TD->Link = 1; + if( QH->Child & 1 ) { + QH->Child = MM_GetPhysAddr( (tVAddr)TD ); + } + else { + // Depth first + QH->_LastItem->Link = MM_GetPhysAddr( (tVAddr)TD ) | 4; + } + QH->_LastItem = TD; - // Empty list - if( Cont->FrameList[next_frame] & 1 ) - { - // TODO: Ensure 32-bit paddr - Cont->FrameList[next_frame] = MM_GetPhysAddr( (tVAddr)TD ); - TD->Control |= (1 << 24); // Ensure that there is an interrupt for each used frame - LOG("next_frame = %i", next_frame); - return; - } - - // Find the end of the list - link = Cont->FrameList[next_frame]; - do { - prev_td = UHCI_int_GetTDFromPhys(link); - link = prev_td->Link; - } while( !(link & 1) ); + // Reenable controller + _OutWord( Cont, USBCMD, 0x0001 ); - // Append - prev_td->Link = MM_GetPhysAddr( (tVAddr)TD ); + // DEBUG! + LOG("QH(%p)->Child = %x", QH, QH->Child); + LOG("TD(%p)->Control = %x, ->Link = %x", TD, TD->Control, TD->Link); - LOG("next_frame = %i, prev_td = %p", next_frame, prev_td); + Mutex_Release(&lock); } /** @@ -200,134 +321,295 @@ void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_TD *TD) * \param Addr Function Address * 16 + Endpoint * \param bTgl Data toggle value */ -void *UHCI_int_SendTransaction( +tUHCI_TD *UHCI_int_CreateTD( tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl, tUSBHostCb Cb, void *CbData, void *Data, size_t Length) { tUHCI_TD *td; + tUHCI_ExtraTDInfo *info = NULL; - if( Length > 0x400 ) return NULL; // Controller allows up to 0x500, but USB doesn't + if( Length > 0x400 ) { + Log_Error("UHCI", "Transaction length too large (%i > 0x400)", Length); + return NULL; // Controller allows up to 0x500, but USB doesn't + } td = UHCI_int_AllocateTD(Cont); - if( !td ) { - // TODO: Wait for one to free? Log_Error("UHCI", "No avaliable TDs, transaction dropped"); return NULL; } - td->Link = 1; + LOG("TD %p %i bytes, Type %x to 0x%x", + td, Length, Type, Addr); + td->Control = (Length - 1) & 0x7FF; - td->Control |= (1 << 23); + td->Control |= TD_CTL_ACTIVE; // Active set + td->Control |= (3 << 27); // 3 retries + // High speed device (must be explicitly enabled + if( Addr & 0x8000 ) + ; + else + td->Control |= 1 << 26; + td->Token = ((Length - 1) & 0x7FF) << 21; td->Token |= (bTgl & 1) << 19; td->Token |= (Addr & 0xF) << 15; td->Token |= ((Addr/16) & 0xFF) << 8; td->Token |= Type; - // TODO: Ensure 32-bit paddr - if( ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE ) { - Log_Warning("UHCI", "TODO: Support non single page transfers (%x + %x > %x)", - (tVAddr)Data & (PAGE_SIZE-1), Length, PAGE_SIZE - ); - // TODO: Need to enable IOC to copy the data back -// td->BufferPointer = - td->_info.bCopyData = 1; - return NULL; + if( + ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE + #if PHYS_BITS > 32 + || MM_GetPhysAddr( (tVAddr)Data ) >> 32 + #endif + ) + { + td->BufferPointer = MM_AllocPhysRange(1, 32); + + LOG("Allocated page %x", td->BufferPointer); + + if( Type == 0x69 ) // IN token + { + LOG("Relocated IN"); + info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 ); + info->Offset = ((tVAddr)Data & (PAGE_SIZE-1)); + info->FirstPage = MM_GetPhysAddr( (tVAddr)Data ); + info->SecondPage = MM_GetPhysAddr( (tVAddr)Data + Length - 1 ); + } + else + { + LOG("Relocated OUT/SETUP"); + void *ptr = MM_MapTemp(td->BufferPointer); + memcpy( ptr, Data, Length ); + MM_FreeTemp(ptr); + td->Control |= TD_CTL_IOC; + } + td->_info.bFreePointer = 1; } - else { + else + { td->BufferPointer = MM_GetPhysAddr( (tVAddr)Data ); - td->_info.bCopyData = 0; + td->_info.bFreePointer = 0; } // Interrupt on completion - if( Cb ) { - td->Control |= (1 << 24); + if( Cb ) + { + if( !info ) + info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 ); LOG("IOC Cb=%p CbData=%p", Cb, CbData); - td->_info.Callback = Cb; // NOTE: if ERRPTR then the TD is kept allocated until checked - td->_info.CallbackPtr = CbData; + info->Callback = Cb; + info->CallbackPtr = CbData; } - td->_info.DataPtr = Data; - - UHCI_int_AppendTD(Cont, td); + if( info ) { + LOG("info = %p", info); + td->Control |= TD_CTL_IOC; + td->_info.ExtraInfo = info; + } return td; } -void *UHCI_DataIN(void *Ptr, int Fcn, int Endpt, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) +void UHCI_int_SetInterruptPoll(tUHCI_Controller *Cont, tUHCI_TD *TD, int Period) { - return UHCI_int_SendTransaction(Ptr, Fcn*16+Endpt, 0x69, DataTgl, Cb, CbData, Buf, Length); + tUHCI_QH *qh; + const int qh_offsets[] = {126, 124, 120, 112, 96, 64, 0}; + const int qh_sizes[] = { 1, 2, 4, 8, 16, 32, 64}; + + // Bounds limit + if( Period < 0 ) return ; + if( Period > 256 ) Period = 256; + if( Period == 255 ) Period = 256; + + // Get the log base2 of the period + int period_slot = 0; + while( Period >>= 1 ) period_slot ++; + + // Adjust for the 4ms minimum period + if( period_slot < 2 ) period_slot = 0; + else period_slot -= 2; + + // _AppendTD calculates this from qh, but we use it to determine qh + TD->_info.QueueIndex = qh_offsets[period_slot]; + // TODO: Find queue with lowest load +#if 1 + int min_load = 0; + int min_load_slot = 0; + for( int i = 0; i < qh_sizes[period_slot]; i ++ ) + { + int load, index; + index = qh_offsets[period_slot] + i; + load = 0; + while( index >= 0 && index < 127 ) + { + qh = Cont->TDQHPage->InterruptQHs + index; + load += Cont->InterruptLoad[index]; + index = ((qh->Next & ~3) - Cont->PhysTDQHPage)/sizeof(tUHCI_QH); + } + + LOG("Slot %i (and below) load %i", qh_offsets[period_slot] + i, load); + + // i = 0 will initialise the values, otherwise update if lower + if( i == 0 || load < min_load ) + { + min_load = load; + min_load_slot = i; + } + // - Fast return if no load + if( load == 0 ) break; + } + min_load_slot += qh_offsets[period_slot]; + TD->_info.QueueIndex = min_load_slot; + if( min_load + (TD->Control & 0x7FF) > MAX_INTERRUPT_LOAD ) + { + Log_Warning("UHCI", "Interrupt load on %i ms is too high (slot %i load %i bytes)", + 1 << (period_slot+2), min_load_slot, min_load + ); + } + Cont->InterruptLoad[min_load_slot] += (TD->Control & 0x7FF); +#endif + qh = Cont->TDQHPage->InterruptQHs + TD->_info.QueueIndex; + + LOG("period_slot = %i, QueueIndex = %i", + period_slot, TD->_info.QueueIndex); + + // Stop any errors causing the TD to stop (NAK will error) + // - If the device is unplugged, the removal code should remove the interrupt + TD->Control &= ~(3 << 27); + + UHCI_int_AppendTD(Cont, qh, TD); } -void *UHCI_DataOUT(void *Ptr, int Fcn, int Endpt, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) +void *UHCI_InterruptIN(void *Ptr, int Dest, int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) { - return UHCI_int_SendTransaction(Ptr, Fcn*16+Endpt, 0xE1, DataTgl, Cb, CbData, Buf, Length); + tUHCI_TD *td; + + if( Period < 0 ) return NULL; + + ENTER("pPtr xDest iPeriod pCb pCbData pBuf iLength", + Ptr, Dest, Period, Cb, CbData, Buf, Length); + + // TODO: Data toggle? + td = UHCI_int_CreateTD(Ptr, Dest, PID_IN, 0, Cb, CbData, Buf, Length); + if( !td ) return NULL; + + UHCI_int_SetInterruptPoll(Ptr, td, Period); + + LEAVE('p', td); + return td; } +// TODO: Does interrupt OUT make sense? +void *UHCI_InterruptOUT(void *Ptr, int Dest, int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) +{ + tUHCI_TD *td; + + if( Period < 0 ) return NULL; + + ENTER("pPtr xDest iPeriod pCb pCbData pBuf, iLength", + Ptr, Dest, Period, Cb, CbData, Buf, Length); + + // TODO: Data toggle? + td = UHCI_int_CreateTD(Ptr, Dest, PID_OUT, 0, Cb, CbData, Buf, Length); + if( !td ) return NULL; + + UHCI_int_SetInterruptPoll(Ptr, td, Period); -void *UHCI_SendSetup(void *Ptr, int Fcn, int Endpt, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) + LEAVE('p', td); + return td; +} + +void UHCI_StopInterrupt(void *Ptr, void *Handle) { - return UHCI_int_SendTransaction(Ptr, Fcn*16+Endpt, 0x2D, DataTgl, Cb, CbData, Buf, Length); + // TODO: Stop interrupt transaction + Log_Error("UHCI", "TODO: Implement UHCI_StopInterrupt"); } -int UHCI_IsTransferComplete(void *Ptr, void *Handle) +void *UHCI_ControlSETUP(void *Ptr, int Dest, int Tgl, void *Data, size_t Length) { - tUHCI_TD *td = Handle; - int ret; - ret = !(td->Control & (1 << 23)); - if(ret) { - td->_info.Callback = NULL; - td->Link = 0; - } - return ret; + tUHCI_Controller *Cont = Ptr; + tUHCI_QH *qh = &Cont->TDQHPage->ControlQH; + tUHCI_TD *td; + + ENTER("pPtr xDest iTgl pData iLength", Ptr, Dest, Tgl, Data, Length); + + td = UHCI_int_CreateTD(Cont, Dest, PID_SETUP, Tgl, NULL, NULL, Data, Length); + UHCI_int_AppendTD(Cont, qh, td); + + LEAVE('p', td); + + return td; } +void *UHCI_ControlOUT(void *Ptr, int Dest, int Tgl, tUSBHostCb Cb, void *CbData, void *Data, size_t Length) +{ + tUHCI_Controller *Cont = Ptr; + tUHCI_QH *qh = &Cont->TDQHPage->ControlQH; + tUHCI_TD *td; -// === INTERNAL FUNCTIONS === -/** - * \fn int UHCI_Int_InitHost(tUCHI_Controller *Host) - * \brief Initialises a UHCI host controller - * \param Host Pointer - Host to initialise - */ -int UHCI_Int_InitHost(tUHCI_Controller *Host) + ENTER("pPtr xDest iTgl pCb pCbData pData iLength", Ptr, Dest, Tgl, Cb, CbData, Data, Length); + + td = UHCI_int_CreateTD(Cont, Dest, PID_OUT, Tgl, Cb, CbData, Data, Length); + UHCI_int_AppendTD(Cont, qh, td); + + LEAVE('p', td); + return td; +} +void *UHCI_ControlIN(void *Ptr, int Dest, int Tgl, tUSBHostCb Cb, void *CbData, void *Data, size_t Length) { - ENTER("pHost", Host); + tUHCI_Controller *Cont = Ptr; + tUHCI_QH *qh = &Cont->TDQHPage->ControlQH; + tUHCI_TD *td; - _OutWord( Host, USBCMD, 4 ); // GRESET - // TODO: Wait for at least 10ms - _OutWord( Host, USBCMD, 0 ); // GRESET + ENTER("pPtr xDest iTgl pCb pCbData pData iLength", Ptr, Dest, Tgl, Cb, CbData, Data, Length); - // Allocate Frame List - // - 1 Page, 32-bit address - // - 1 page = 1024 4 byte entries - Host->FrameList = (void *) MM_AllocDMA(1, 32, &Host->PhysFrameList); - if( !Host->FrameList ) { - Log_Warning("UHCI", "Unable to allocate frame list, aborting"); - LEAVE('i', -1); - return -1; + td = UHCI_int_CreateTD(Cont, Dest, PID_IN, !!Tgl, Cb, CbData, Data, Length); + UHCI_int_AppendTD(Cont, qh, td); + + LEAVE('p', td); + return td; +} + +void *UHCI_BulkOUT(void *Ptr, int Dest, int bToggle, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) +{ + tUHCI_Controller *Cont = Ptr; + tUHCI_QH *qh = &Cont->TDQHPage->BulkQH; + tUHCI_TD *td; + char *src = Buf; + + ENTER("pPtr xDest ibToggle pCb pCbData pData iLength", Ptr, Dest, bToggle, Cb, CbData, Buf, Length); + + if( Length > MAX_PACKET_SIZE ) { + Log_Error("UHCI", "Passed an oversized packet by the USB code (%i > %i)", Length, MAX_PACKET_SIZE); + LEAVE('n'); } - LOG("Allocated frame list 0x%x (0x%x)", Host->FrameList, Host->PhysFrameList); - memsetd( Host->FrameList, 1, 1024 ); // Clear List (Disabling all entries) - - //! \todo Properly fill frame list - - // Set frame length to 1 ms - _OutByte( Host, SOFMOD, 64 ); - // Set Frame List - _OutDWord( Host, FLBASEADD, Host->PhysFrameList ); - _OutWord( Host, FRNUM, 0 ); - - // Enable Interrupts - _OutWord( Host, USBINTR, 0x000F ); - PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 ); + td = UHCI_int_CreateTD(Cont, Dest, PID_OUT, bToggle, Cb, CbData, src, Length); + UHCI_int_AppendTD(Cont, qh, td); - // Enable processing - _OutWord( Host, USBCMD, 0x0001 ); + LEAVE('p', td); + return td; +} +void *UHCI_BulkIN(void *Ptr, int Dest, int bToggle, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length) +{ + tUHCI_Controller *Cont = Ptr; + tUHCI_QH *qh = &Cont->TDQHPage->BulkQH; + tUHCI_TD *td; + char *dst = Buf; - LEAVE('i', 0); - return 0; + ENTER("pPtr xDest ibToggle pCb pCbData pData iLength", Ptr, Dest, bToggle, Cb, CbData, Buf, Length); + if( Length > MAX_PACKET_SIZE ) { + Log_Error("UHCI", "Passed an oversized packet by the USB code (%i > %i)", Length, MAX_PACKET_SIZE); + LEAVE('n'); + } + + td = UHCI_int_CreateTD(Cont, Dest, PID_IN, bToggle, Cb, CbData, dst, Length); + UHCI_int_AppendTD(Cont, qh, td); + + LEAVE('p', td); + return td; } +// === INTERNAL FUNCTIONS === void UHCI_CheckPortUpdate(void *Ptr) { tUHCI_Controller *Host = Ptr; @@ -367,58 +649,246 @@ void UHCI_CheckPortUpdate(void *Ptr) } } +tUHCI_TD *UHCI_int_GetTDFromPhys(tUHCI_Controller *Controller, Uint32 PAddr) +{ + if( PAddr >= Controller->PhysTDQHPage && PAddr < Controller->PhysTDQHPage + PAGE_SIZE ) + { + PAddr -= Controller->PhysTDQHPage; + PAddr -= (128+2)*sizeof(tUHCI_QH); + if( PAddr > PAGE_SIZE ) return NULL; // Wrapping will bring above page size + PAddr /= sizeof(tUHCI_TD); + return &Controller->TDQHPage->LocalTDPool[PAddr]; + } + + + tPAddr global_pool = MM_GetPhysAddr( (tVAddr)gaUHCI_TDPool ); + + if( PAddr < global_pool || PAddr >= global_pool + PAGE_SIZE ) return NULL; + + PAddr -= global_pool; + PAddr /= sizeof(tUHCI_TD); + return &gaUHCI_TDPool[PAddr]; +} + +void UHCI_int_CleanQH(tUHCI_Controller *Cont, tUHCI_QH *QH) +{ + tUHCI_TD *td, *prev = NULL; + Uint32 cur_td; + int nCleaned = 0; + + // Disable controller + _OutWord( Cont, USBCMD, 0x0000 ); + + // Scan QH list + cur_td = QH->Child; + LOG("cur_td = 0x%08x", cur_td); + while( !(cur_td & 1) ) + { + td = UHCI_int_GetTDFromPhys(Cont, cur_td); + if(!td) { + Log_Warning("UHCI", "_int_CleanQH: QH %p contains TD %x, which was not from a pool", + QH, cur_td); + break ; + } + + // Active? Ok. + if( td->Control & TD_CTL_ACTIVE ) { + LOG("%p still active", td); + prev = td; + cur_td = td->Link; + continue ; + } + + LOG("Removed %p from QH %p", td, QH); + + if( !prev ) + QH->Child = td->Link; + else + prev->Link = td->Link; + cur_td = td->Link; + nCleaned ++; + } + + if( nCleaned == 0 ) { + LOG("Nothing cleaned... what the?"); + } + + // re-enable controller + _OutWord( Cont, USBCMD, 0x0001 ); +} + +void UHCI_int_HandleTDComplete(tUHCI_Controller *Cont, tUHCI_TD *TD) +{ + int byte_count = (TD->Control & 0x7FF)+1; + tUHCI_ExtraTDInfo *info = TD->_info.ExtraInfo; + + // Handle non page-aligned destination (or with a > 32-bit paddr) + // TODO: Needs fixing for alignment issues + if(info->FirstPage) + { + char *src, *dest; + int src_ofs = TD->BufferPointer & (PAGE_SIZE-1); + src = MM_MapTemp(TD->BufferPointer); + dest = MM_MapTemp(info->FirstPage); + // Check for a single page transfer + if( byte_count + info->Offset <= PAGE_SIZE ) + { + LOG("Single page copy %P to %P of %p", + TD->BufferPointer, info->FirstPage, TD); + memcpy(dest + info->Offset, src + src_ofs, byte_count); + } + else + { + // Multi-page + LOG("Multi page copy %P to (%P,%P) of %p", + TD->BufferPointer, info->FirstPage, info->SecondPage, TD); + int part_len = PAGE_SIZE - info->Offset; + memcpy(dest + info->Offset, src + src_ofs, part_len); + MM_FreeTemp( dest ); + dest = MM_MapTemp(info->SecondPage); + memcpy(dest, src + src_ofs + part_len, byte_count - part_len); + } + MM_FreeTemp( src ); + MM_FreeTemp( dest ); + } + + // Callback + if( info->Callback != NULL ) + { + LOG("Calling cb %p (%i bytes)", info->Callback, byte_count); + void *ptr = MM_MapTemp( TD->BufferPointer ); + info->Callback( info->CallbackPtr, ptr, byte_count ); + MM_FreeTemp( ptr ); + } + + // Clean up info + if( TD->_info.QueueIndex > 127 ) + { + free( info ); + TD->_info.ExtraInfo = NULL; + } +} + +void UHCI_int_InterruptThread(void *Pointer) +{ + tUHCI_Controller *Cont = Pointer; + Threads_SetName("UHCI Interrupt Handler"); + for( ;; ) + { + int nSeen = 0; + + LOG("zzzzz...."); + // 0 = Take all + Semaphore_Wait(&gUHCI_InterruptSempahore, 0); + LOG("Huh?"); + + for( int i = 0; i < NUM_TDs; i ++ ) + { + tUHCI_TD *td; + + td = &gaUHCI_TDPool[i]; + + // Skip completely inactive TDs + if( td->_info.bActive == 0 ) continue ; + // Skip ones that are still in use + if( td->Control & TD_CTL_ACTIVE ) continue ; + + nSeen ++; + + // If no callback/alt buffer, mark as free and move on + if( td->_info.ExtraInfo ) + { + UHCI_int_HandleTDComplete(Cont, td); + } + + // Error check + if( td->Control & 0x00FF0000 ) { + LOG("td->control(Status) = %s%s%s%s%s%s%s%s", + td->Control & TD_CTL_ACTIVE ? "Active, " : "", + td->Control & TD_CTL_STALLED ? "Stalled, " : "", + td->Control & TD_CTL_DATABUFERR ? "Data Buffer Error, " : "", + td->Control & TD_CTL_BABBLE ? "Babble, " : "", + td->Control & TD_CTL_NAK ? "NAK, " : "", + td->Control & TD_CTL_CRCERR ? "CRC Error, " : "", + td->Control & TD_CTL_BITSTUFF ? "Bitstuff Error, " : "", + td->Control & TD_CTL_RESERVED ? "Reserved " : "" + ); + LOG("From queue %i", td->_info.QueueIndex); + // Clean up QH (removing all inactive entries) + UHCI_int_CleanQH(Cont, Cont->TDQHPage->InterruptQHs + td->_info.QueueIndex); + td->Control = 0; + } + + // Handle rescheduling of interrupt TDs + if( td->_info.QueueIndex <= 127 ) + { + LOG("Re-schedule interrupt %p (offset %i)", td, td->_info.QueueIndex); + // TODO: Flip toggle? + td->Control |= TD_CTL_ACTIVE; + // Add back into controller's interrupt list + UHCI_int_AppendTD( + Cont, + Cont->TDQHPage->InterruptQHs + td->_info.QueueIndex, + td + ); + continue ; + } + + // Clean up + if( td->_info.bFreePointer ) + MM_DerefPhys( td->BufferPointer ); + + // Clean up + LOG("Cleaned %p (->Control = %x)", td, td->Control); + td->_info.bActive = 0; + } + + if( nSeen == 0 ) { + LOG("Why did you wake me?"); + } + } +} + void UHCI_InterruptHandler(int IRQ, void *Ptr) { tUHCI_Controller *Host = Ptr; - int frame = (_InWord(Host, FRNUM) - 1) & 0x3FF; +// int frame = (_InWord(Host, FRNUM) - 1) & 0x3FF; Uint16 status = _InWord(Host, USBSTS); -// Log_Debug("UHCI", "UHIC Interrupt, status = 0x%x, frame = %i", status, frame); + + LOG("%p: status = 0x%04x", Ptr, status); // Interrupt-on-completion if( status & 1 ) { - tPAddr link; + // TODO: Support isochronous transfers (will need updating the frame pointer) + Semaphore_Signal(&gUHCI_InterruptSempahore, 1); + } + + // USB Error Interrupt + if( status & 2 ) + { - for( int i = 0; i < 10; i ++ ) - { - link = Host->FrameList[frame]; - Host->FrameList[frame] = 1; - while( link && !(link & 1) ) - { - tUHCI_TD *td = UHCI_int_GetTDFromPhys(link); - int byte_count = (td->Control&0x7FF)+1; - LOG("link = 0x%x, td = %p, byte_count = %i", link, td, byte_count); - // Handle non-page aligned destination - // TODO: This will break if the destination is not in global memory - if(td->_info.bCopyData) - { - void *ptr = (void*)MM_MapTemp(td->BufferPointer); - Log_Debug("UHCI", "td->_info.DataPtr = %p", td->_info.DataPtr); - memcpy(td->_info.DataPtr, ptr, byte_count); - MM_FreeTemp((tVAddr)ptr); - } - // Callback - if(td->_info.Callback && td->_info.Callback != INVLPTR) - { - LOG("Calling cb %p", td->_info.Callback); - td->_info.Callback(td->_info.CallbackPtr, td->_info.DataPtr, byte_count); - td->_info.Callback = NULL; - } - link = td->Link; - if( td->_info.Callback != INVLPTR ) - td->Link = 0; - } - - if(frame == 0) - frame = 0x3ff; - else - frame --; - } + } + + // Resume Detect + // - Fired if in suspend state and a USB device sends the RESUME signal + if( status & 4 ) + { + + } + + // Host System Error + if( status & 8 ) + { -// Host->LastCleanedFrame = frame; } - LOG("status = 0x%02x", status); + // Host Controller Process Error + if( status & 0x10 ) + { + Log_Error("UHCI", "Host controller process error on controller %p", Ptr); + } + _OutWord(Host, USBSTS, status); }