3 * - By John Hodge (thePowersGang)
5 * Universal Host Controller Interface
8 #define VERSION VER2(0,5)
16 #include <semaphore.h>
19 #define MAX_CONTROLLERS 8
20 //#define NUM_TDs 1024
21 #define NUM_TDs (PAGE_SIZE/sizeof(tUHCI_TD))
22 #define MAX_PACKET_SIZE 0x400
23 #define MAX_INTERRUPT_LOAD 1024 // Maximum bytes per frame for interrupts
27 #define PID_SETUP 0x2D
30 int UHCI_Initialise(char **Arguments);
32 int UHCI_int_InitHost(tUHCI_Controller *Host);
34 tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont);
35 void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_QH *QH, tUHCI_TD *TD);
36 tUHCI_TD *UHCI_int_CreateTD(tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
38 void *UHCI_InitInterrupt(void *Ptr, int Endpt, int bOutbound, int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Len);
39 void *UHCI_InitIsoch(void *Ptr, int Endpt, size_t MaxPacketSize);
40 void *UHCI_InitControl(void *Ptr, int Endpt, size_t MaxPacketSize);
41 void *UHCI_InitBulk(void *Ptr, int Endpt, size_t MaxPacketSize);
42 void UHCI_RemoveEndpoint(void *Ptr, void *EndptHandle);
43 void *UHCI_SendIsoch(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length, int When);
44 void *UHCI_SendControl(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData,
46 const void *SetupData, size_t SetupLength,
47 const void *OutData, size_t OutLength,
48 void *InData, size_t InLength
50 void *UHCI_SendBulk(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length);
52 void UHCI_CheckPortUpdate(void *Ptr);
53 void UHCI_int_InterruptThread(void *Unused);
54 void UHCI_InterruptHandler(int IRQ, void *Ptr);
56 static void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value);
57 static void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value);
58 static void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value);
59 static Uint16 _InWord(tUHCI_Controller *Host, int Reg);
62 MODULE_DEFINE(0, VERSION, USB_UHCI, UHCI_Initialise, NULL, "USB_Core", NULL);
63 tUHCI_TD *gaUHCI_TDPool;
64 tUHCI_Controller gUHCI_Controllers[MAX_CONTROLLERS];
65 tUSBHostDef gUHCI_HostDef = {
66 .InitInterrupt = UHCI_InitInterrupt,
67 // .InitIsoch = UHCI_InitIsoch,
68 .InitControl = UHCI_InitControl,
69 .InitBulk = UHCI_InitBulk,
70 .RemEndpoint = UHCI_RemoveEndpoint,
72 // .SendIsoch = UHCI_SendIsoch,
73 .SendControl = UHCI_SendControl,
74 .SendBulk = UHCI_SendBulk,
77 .CheckPorts = UHCI_CheckPortUpdate,
78 // .ClearPortFeature = NULL,
79 // .GetBusState = NULL,
80 // .GetPortStatus = NULL,
81 // .SetPortFeature = NULL
83 tSemaphore gUHCI_InterruptSempahore;
87 * \fn int UHCI_Initialise()
88 * \brief Called to initialise the UHCI Driver
90 int UHCI_Initialise(char **Arguments)
97 if( Arguments && *Arguments && strcmp(*Arguments, "0") == 0 )
99 LOG("Disabled by argument");
100 LEAVE('i', MODULE_ERR_NOTNEEDED);
101 return MODULE_ERR_NOTNEEDED;
104 // Initialise with no maximum value
105 Semaphore_Init( &gUHCI_InterruptSempahore, 0, 0, "UHCI", "Interrupt Queue");
107 if( PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, -1) < 0 )
109 LEAVE('i', MODULE_ERR_NOTNEEDED);
110 return MODULE_ERR_NOTNEEDED;
115 gaUHCI_TDPool = (void *) MM_AllocDMA(1, 32, &tmp);
116 memset(gaUHCI_TDPool, 0, PAGE_SIZE);
117 LOG("gaUHCI_TDPool = %p (%P)", gaUHCI_TDPool, tmp);
120 // Enumerate PCI Bus, getting a maximum of `MAX_CONTROLLERS` devices
121 // Class:SubClass:Protocol = 0xC (Serial) : 0x3 (USB) : 0x00 (UHCI)
122 while( (id = PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS )
124 tUHCI_Controller *cinfo = &gUHCI_Controllers[i];
128 base_addr = PCI_GetBAR(id, 4);
132 cinfo->IOBase = base_addr & ~1;
133 cinfo->MemIOMap = NULL;
137 cinfo->MemIOMap = (void*)MM_MapHWPages(base_addr, 1);
139 cinfo->IRQNum = PCI_GetIRQ(id);
141 Log_Debug("UHCI", "Controller PCI #%i: IO Base = 0x%x, IRQ %i",
142 id, base_addr, cinfo->IRQNum);
144 IRQ_AddHandler(cinfo->IRQNum, UHCI_InterruptHandler, cinfo);
147 ret = UHCI_int_InitHost(cinfo);
154 // Spin off interrupt handling thread
155 Proc_SpawnWorker( UHCI_int_InterruptThread, cinfo );
158 cinfo->RootHub = USB_RegisterHost(&gUHCI_HostDef, cinfo, 2);
159 LOG("cinfo->RootHub = %p", cinfo->RootHub);
164 if(i == MAX_CONTROLLERS) {
165 Log_Warning("UHCI", "Over "EXPAND_STR(MAX_CONTROLLERS)" UHCI controllers detected, ignoring rest");
167 LEAVE('i', MODULE_ERR_OK);
168 return MODULE_ERR_OK;
172 * \fn void UHCI_Cleanup()
173 * \brief Called just before module is unloaded
180 * \brief Initialises a UHCI host controller
181 * \param Host Pointer - Host to initialise
183 int UHCI_int_InitHost(tUHCI_Controller *Host)
185 ENTER("pHost", Host);
187 // - 1 Page, 32-bit address
188 // - 1 page = 1024 4 byte entries
189 Host->FrameList = (void *) MM_AllocDMA(1, 32, &Host->PhysFrameList);
190 if( !Host->FrameList ) {
191 Log_Warning("UHCI", "Unable to allocate frame list, aborting");
195 LOG("->FrameList = %p (%P)", Host->FrameList, Host->PhysFrameList);
197 Host->TDQHPage = (void *) MM_AllocDMA(1, 32, &Host->PhysTDQHPage);
198 if( !Host->TDQHPage ) {
200 Log_Warning("UHCI", "Unable to allocate QH page, aborting");
204 LOG("->TDQHPage = %p (%P)", Host->TDQHPage, Host->PhysTDQHPage);
207 // - The numbers 0...31, but bit reversed (16 (0b1000) = 1 (0b00001)
208 const int dest_offsets[] = {
209 0,16,8,24,4,20,12,28,2,18,10,26,6,22,14,30,
210 1,17,9,25,5,21,13,29,3,19,11,27,7,23,15,31
212 // Fill all slots (but every 4th will be changed below
213 for( int i = 0; i < 1024; i ++ ) {
214 Uint32 addr = MM_GetPhysAddr( &Host->TDQHPage->ControlQH );
215 Host->FrameList[i] = addr | 2;
217 for( int i = 0; i < 64; i ++ ) {
218 int ofs = dest_offsets[ i & (32-1) ] * 2 + (i >= 32);
219 Uint32 addr = Host->PhysTDQHPage + ofs * sizeof(tUHCI_QH);
220 LOG("Slot %i to (%i,%i,%i,%i) ms slots",
221 ofs, 0 + i*4, 256 + i*4, 512 + i*4, 768 + i*4);
222 Host->FrameList[ 0 + i*4] = addr | 2;
223 Host->FrameList[256 + i*4] = addr | 2;
224 Host->FrameList[512 + i*4] = addr | 2;
225 Host->FrameList[768 + i*4] = addr | 2;
228 // Build up interrupt binary tree
230 tUHCI_QH *dest = Host->TDQHPage->InterruptQHs;
231 Uint32 destphys = Host->PhysTDQHPage;
233 // Set up next pointer to index to i/2 in the next step
234 for( int _count = 64; _count > 1; _count /= 2 )
236 LOG("count=%i, dest=%p, destphys=%P", _count, dest, destphys);
237 for( int i = 0; i < _count; i ++ ) {
238 LOG(" %i-%i: %P==%P", _count, i, MM_GetPhysAddr(dest+i), destphys+i*sizeof(tUHCI_QH));
239 dest[i].Next = destphys + (_count + i/2) * sizeof(tUHCI_QH) + 2;
242 dest += _count; destphys += _count * sizeof(tUHCI_QH);
244 // Skip padding, and move to control QH
245 dest->Next = MM_GetPhysAddr( &Host->TDQHPage->BulkQH ) | 2;
249 // Set up control and bulk queues
250 Host->TDQHPage->ControlQH.Next = MM_GetPhysAddr( &Host->TDQHPage->BulkQH ) | 2;
251 Host->TDQHPage->ControlQH.Child = 1;
252 Host->TDQHPage->BulkQH.Next = 1;
253 Host->TDQHPage->BulkQH.Child = 1;
256 _OutWord( Host, USBCMD, 4 );
258 _OutWord( Host, USBCMD, 0 );
260 // Allocate Frame List
261 // Set frame length to 1 ms
262 _OutByte( Host, SOFMOD, 64 );
265 _OutDWord( Host, FLBASEADD, Host->PhysFrameList );
266 _OutWord( Host, FRNUM, 0 );
269 _OutWord( Host, USBINTR, 0x000F );
270 PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 );
273 LOG("Processing enabling");
274 _OutWord( Host, USBCMD, 0x0001 );
280 // --------------------------------------------------------------------
281 // TDs and QH Allocation/Appending
282 // --------------------------------------------------------------------
283 tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont)
286 Mutex_Acquire( &lock );
287 for( int i = 0; i < NUM_TDs; i ++ )
289 if(gaUHCI_TDPool[i]._info.bActive == 0)
291 gaUHCI_TDPool[i].Link = 1;
292 gaUHCI_TDPool[i].Control = TD_CTL_ACTIVE;
293 gaUHCI_TDPool[i]._info.bActive = 1;
294 gaUHCI_TDPool[i]._info.QueueIndex = 128;
295 Mutex_Release( &lock );
296 return &gaUHCI_TDPool[i];
299 Mutex_Release( &lock );
303 void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_QH *QH, tUHCI_TD *TD)
305 static tMutex lock; // TODO: Should I use a shortlock (avoid being preempted)
307 Mutex_Acquire(&lock);
309 // Ensure that there is an interrupt for each used frame
310 TD->Control |= TD_CTL_IOC;
311 TD->_info.QueueIndex = ((tVAddr)QH - (tVAddr)Cont->TDQHPage->InterruptQHs) / sizeof(tUHCI_QH);
312 LOG("TD(%p)->QueueIndex = %i", TD, TD->_info.QueueIndex);
314 TD->Control &= ~0x7FF;
315 TD->Control |= (TD->Token >> 21) & 0x7FF;
318 tPAddr tdaddr = MM_GetPhysAddr( TD );
320 _OutWord( Cont, USBCMD, 0x0000 );
324 if( QH->Child & 1 ) {
329 QH->_LastItem->Link = tdaddr | 4;
333 // Reenable controller
334 _OutWord( Cont, USBCMD, 0x0001 );
337 LOG("QH(%p)->Child = %x", QH, QH->Child);
338 LOG("TD(%p)->Control = %x, ->Link = %x", TD, TD->Control, TD->Link);
340 Mutex_Release(&lock);
344 * \brief Send a transaction to the USB bus
345 * \param Cont Controller pointer
346 * \param Addr Function Address * 16 + Endpoint
347 * \param bTgl Data toggle value
349 tUHCI_TD *UHCI_int_CreateTD(
350 tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl,
351 tUSBHostCb Cb, void *CbData, void *Data, size_t Length)
354 tUHCI_ExtraTDInfo *info = NULL;
356 if( Length > 0x400 ) {
357 Log_Error("UHCI", "Transaction length too large (%i > 0x400)", Length);
358 return NULL; // Controller allows up to 0x500, but USB doesn't
361 td = UHCI_int_AllocateTD(Cont);
363 Log_Error("UHCI", "No avaliable TDs, transaction dropped");
367 LOG("TD %p %i bytes, Type %x to 0x%x",
368 td, Length, Type, Addr);
370 td->Control = (Length - 1) & 0x7FF;
371 td->Control |= TD_CTL_ACTIVE; // Active set
372 td->Control |= (3 << 27); // 3 retries
373 // High speed device (must be explicitly enabled
377 td->Control |= 1 << 26;
379 td->Token = ((Length - 1) & 0x7FF) << 21;
380 td->Token |= (bTgl & 1) << 19;
381 td->Token |= (Addr & 0xF) << 15;
382 td->Token |= ((Addr/16) & 0xFF) << 8;
386 ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE
388 || MM_GetPhysAddr( Data ) >> 32
392 td->BufferPointer = MM_AllocPhysRange(1, 32);
394 LOG("Allocated page %x", td->BufferPointer);
396 if( Type == 0x69 ) // IN token
399 info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 );
400 info->Offset = ((tVAddr)Data & (PAGE_SIZE-1));
401 info->FirstPage = MM_GetPhysAddr( Data );
402 info->SecondPage = MM_GetPhysAddr( (const char *)Data + Length - 1 );
406 LOG("Relocated OUT/SETUP");
407 void *ptr = MM_MapTemp(td->BufferPointer);
408 memcpy( ptr, Data, Length );
410 td->Control |= TD_CTL_IOC;
412 td->_info.bFreePointer = 1;
416 td->BufferPointer = MM_GetPhysAddr( Data );
417 td->_info.bFreePointer = 0;
420 // Interrupt on completion
424 info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 );
425 LOG("IOC Cb=%p CbData=%p", Cb, CbData);
427 info->CallbackPtr = CbData;
431 LOG("info = %p", info);
432 td->Control |= TD_CTL_IOC;
433 td->_info.ExtraInfo = info;
439 void UHCI_int_SetInterruptPoll(tUHCI_Controller *Cont, tUHCI_TD *TD, int Period)
442 const int qh_offsets[] = {126, 124, 120, 112, 96, 64, 0};
443 const int qh_sizes[] = { 1, 2, 4, 8, 16, 32, 64};
446 if( Period < 0 ) return ;
447 if( Period > 256 ) Period = 256;
448 if( Period == 255 ) Period = 256;
450 // Get the log base2 of the period
452 while( Period >>= 1 ) period_slot ++;
454 // Adjust for the 4ms minimum period
455 if( period_slot < 2 ) period_slot = 0;
456 else period_slot -= 2;
458 // _AppendTD calculates this from qh, but we use it to determine qh
459 TD->_info.QueueIndex = qh_offsets[period_slot];
460 // TODO: Find queue with lowest load
463 int min_load_slot = 0;
464 for( int i = 0; i < qh_sizes[period_slot]; i ++ )
467 index = qh_offsets[period_slot] + i;
469 while( index >= 0 && index < 127 )
471 qh = Cont->TDQHPage->InterruptQHs + index;
472 load += Cont->InterruptLoad[index];
473 index = ((qh->Next & ~3) - Cont->PhysTDQHPage)/sizeof(tUHCI_QH);
476 LOG("Slot %i (and below) load %i", qh_offsets[period_slot] + i, load);
478 // i = 0 will initialise the values, otherwise update if lower
479 if( i == 0 || load < min_load )
484 // - Fast return if no load
485 if( load == 0 ) break;
487 min_load_slot += qh_offsets[period_slot];
488 TD->_info.QueueIndex = min_load_slot;
489 if( min_load + (TD->Control & 0x7FF) > MAX_INTERRUPT_LOAD )
491 Log_Warning("UHCI", "Interrupt load on %i ms is too high (slot %i load %i bytes)",
492 1 << (period_slot+2), min_load_slot, min_load
495 Cont->InterruptLoad[min_load_slot] += (TD->Control & 0x7FF);
497 qh = Cont->TDQHPage->InterruptQHs + TD->_info.QueueIndex;
499 LOG("period_slot = %i, QueueIndex = %i",
500 period_slot, TD->_info.QueueIndex);
502 // Stop any errors causing the TD to stop (NAK will error)
503 // - If the device is unplugged, the removal code should remove the interrupt
504 TD->Control &= ~(3 << 27);
506 UHCI_int_AppendTD(Cont, qh, TD);
509 // --------------------------------------------------------------------
511 // --------------------------------------------------------------------
512 void *UHCI_InitInterrupt(void *Ptr, int Endpt, int bOutbound,
513 int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Len)
516 if( Period <= 0 ) return NULL;
518 ENTER("pPtr xEndpt bbOutbound iPeriod pCb pCbData pBuf iLen",
519 Ptr, Endpt, bOutbound, Period, Cb, CbData, Buf, Len);
521 // TODO: Data toggle?
522 td = UHCI_int_CreateTD(Ptr, Endpt, (bOutbound ? PID_OUT : PID_IN), 0, Cb, CbData, Buf, Len);
523 if( !td ) return NULL;
525 UHCI_int_SetInterruptPoll(Ptr, td, Period);
531 void *UHCI_int_InitEndpt(tUHCI_Controller *Cont, int Type, int Endpt, size_t MaxPacketSize)
533 if( Endpt >= 256*16 )
536 if( MaxPacketSize > MAX_PACKET_SIZE) {
537 Log_Warning("UHCI", "MaxPacketSize for %x greater than controller max (%i > %i)",
538 Endpt, MaxPacketSize, MAX_PACKET_SIZE);
542 if( Cont->DevInfo[Endpt / 16] == NULL ) {
543 Cont->DevInfo[Endpt / 16] = calloc( 1, sizeof(*Cont->DevInfo[0]) );
545 tUHCI_EndpointInfo *epi = &Cont->DevInfo[Endpt/16]->EndpointInfo[Endpt%16];
548 Log_Warning("UHCI", "Endpoint %x reused?", Endpt);
552 epi->MaxPacketSize = MaxPacketSize;
556 return (void*)(tVAddr)(Endpt+1);
560 void *UHCI_InitControl(void *Ptr, int Endpt, size_t MaxPacketSize)
562 return UHCI_int_InitEndpt(Ptr, 1, Endpt, MaxPacketSize);
565 void *UHCI_InitBulk(void *Ptr, int Endpt, size_t MaxPacketSize)
567 return UHCI_int_InitEndpt(Ptr, 2, Endpt, MaxPacketSize);
570 void UHCI_RemoveEndpoint(void *Ptr, void *Handle)
572 tUHCI_Controller *Cont = Ptr;
576 if( (tVAddr)Handle <= 256*16 ) {
577 int addr = (tVAddr)Handle;
578 Cont->DevInfo[addr/16]->EndpointInfo[addr%16].Type = 0;
581 // TODO: Stop interrupt transaction
582 Log_Error("UHCI", "TODO: Implement stopping interrupt polling");
586 void *UHCI_SendControl(void *Ptr, void *Endpt, tUSBHostCb Cb, void *CbData,
587 int bOutbound, // (1) SETUP, OUT, IN vs (0) SETUP, IN, OUT
588 const void *SetupData, size_t SetupLength,
589 const void *OutData, size_t OutLength,
590 void *InData, size_t InLength
593 ENTER("pPtr pEndpt bOutbound", Ptr, Endpt, bOutbound);
595 tUHCI_Controller *Cont = Ptr;
596 tUHCI_QH *qh = &Cont->TDQHPage->ControlQH;
598 tUHCI_EndpointInfo *epi;
602 if( Endpt == NULL ) {
603 Log_Error("UHCI", "Passed a NULL Endpoint handle");
608 // Sanity check Endpt
609 if( (tVAddr)Endpt > 0x800 ) {
613 dest = (tVAddr)Endpt - 1;
614 if( Cont->DevInfo[dest/16] == NULL ) LEAVE_RET('n', NULL);
615 epi = &Cont->DevInfo[dest/16]->EndpointInfo[dest%16];
616 if( epi->Type != 1 ) LEAVE_RET('n', NULL);
617 mps = epi->MaxPacketSize;
620 // TODO: Build up list and then append to QH in one operation
622 char *data_ptr, *status_ptr;
623 size_t data_len, status_len;
624 Uint8 data_pid, status_pid;
627 data_pid = PID_OUT; data_ptr = (void*)OutData; data_len = OutLength;
628 status_pid = PID_IN; status_ptr = InData; status_len = InLength;
631 data_pid = PID_IN; data_ptr = InData; data_len = InLength;
632 status_pid = PID_OUT; status_ptr = (void*)OutData; status_len = OutLength;
635 // Sanity check data lengths
636 if( SetupLength > mps ) LEAVE_RET('n', NULL);
637 if( status_len > mps ) LEAVE_RET('n', NULL);
639 // Create and append SETUP packet
640 td = UHCI_int_CreateTD(Cont, dest, PID_SETUP, tgl, NULL, NULL, (void*)SetupData, SetupLength);
641 UHCI_int_AppendTD(Cont, qh, td);
645 while( data_len > 0 )
647 size_t len = MIN(data_len, mps);
648 td = UHCI_int_CreateTD(Cont, dest, data_pid, tgl, NULL, NULL, data_ptr, len);
649 UHCI_int_AppendTD(Cont, qh, td);
657 td = UHCI_int_CreateTD(Cont, dest, status_pid, tgl, Cb, CbData, status_ptr, status_len);
658 UHCI_int_AppendTD(Cont, qh, td);
661 // Update toggle value
665 // for( int i = 0; i < 1024; i ++ )
667 // LOG("- FrameList[%i] = %x", i, Cont->FrameList[i]);
675 void *UHCI_SendBulk(void *Ptr, void *Endpt, tUSBHostCb Cb, void *CbData, int bOutbound, void *Data, size_t Length)
677 tUHCI_Controller *Cont = Ptr;
678 tUHCI_QH *qh = &Cont->TDQHPage->BulkQH;
680 tUHCI_EndpointInfo *epi;
684 ENTER("pPtr pEndpt pCb pCbData bOutbound pData iLength", Ptr, Endpt, Cb, CbData, bOutbound, Data, Length);
686 if( Endpt == NULL ) {
687 Log_Error("UHCI", "_SendBulk passed a NULL endpoint handle");
692 // Sanity check Endpt
693 if( (tVAddr)Endpt > 256*16 ) {
694 Log_Error("UHCI", "_SendBulk passed an interrupt endpoint handle");
698 dest = (tVAddr)Endpt - 1;
699 if( Cont->DevInfo[dest/16] == NULL ) {
700 Log_Error("UHCI", "_SendBulk passed an uninitialised handle");
704 epi = &Cont->DevInfo[dest/16]->EndpointInfo[dest%16];
705 if( epi->Type != 2 ) {
706 Log_Error("UHCI", "_SendBulk passed an invalid endpoint type (%i!=2)", epi->Type);
711 mps = epi->MaxPacketSize;
713 Uint8 pid = (bOutbound ? PID_OUT : PID_IN);
718 size_t len = MIN(mps, Length);
720 td = UHCI_int_CreateTD(Cont, dest, pid, tgl, Cb, (len == Length ? CbData : NULL), pos, len);
721 UHCI_int_AppendTD(Cont, qh, td);
734 // ==========================
735 // === INTERNAL FUNCTIONS ===
736 // ==========================
737 void UHCI_CheckPortUpdate(void *Ptr)
739 tUHCI_Controller *Host = Ptr;
741 for( int i = 0; i < 2; i ++ )
743 int port = PORTSC1 + i*2;
746 status = _InWord(Host, port);
747 // Check for port change
748 if( !(status & 0x0002) ) continue;
749 _OutWord(Host, port, 0x0002);
751 // Check if the port is connected
754 // Tell the USB code it's gone.
755 USB_DeviceDisconnected(Host->RootHub, i);
760 LOG("Port %i has something", i);
761 // Reset port (set bit 9)
763 _OutWord(Host, port, 0x0200);
764 Time_Delay(50); // 50ms delay
765 _OutWord(Host, port, _InWord(Host, port) & ~0x0200);
768 Time_Delay(50); // 50ms delay
769 _OutWord(Host, port, _InWord(Host, port) | 0x0004);
770 // Tell USB there's a new device
771 USB_DeviceConnected(Host->RootHub, i);
776 tUHCI_TD *UHCI_int_GetTDFromPhys(tUHCI_Controller *Controller, Uint32 PAddr)
778 if( PAddr >= Controller->PhysTDQHPage && PAddr < Controller->PhysTDQHPage + PAGE_SIZE )
780 PAddr -= Controller->PhysTDQHPage;
781 PAddr -= (128+2)*sizeof(tUHCI_QH);
782 if( PAddr > PAGE_SIZE ) return NULL; // Wrapping will bring above page size
783 PAddr /= sizeof(tUHCI_TD);
784 return &Controller->TDQHPage->LocalTDPool[PAddr];
788 tPAddr global_pool = MM_GetPhysAddr( gaUHCI_TDPool );
790 if( PAddr < global_pool || PAddr >= global_pool + PAGE_SIZE ) return NULL;
792 PAddr -= global_pool;
793 PAddr /= sizeof(tUHCI_TD);
794 return &gaUHCI_TDPool[PAddr];
797 void UHCI_int_CleanQH(tUHCI_Controller *Cont, tUHCI_QH *QH)
799 tUHCI_TD *td, *prev = NULL;
803 // Disable controller
804 _OutWord( Cont, USBCMD, 0x0000 );
808 LOG("cur_td = 0x%08x", cur_td);
809 while( !(cur_td & 1) )
811 td = UHCI_int_GetTDFromPhys(Cont, cur_td);
813 Log_Warning("UHCI", "_int_CleanQH: QH %p contains TD %x, which was not from a pool",
819 if( td->Control & TD_CTL_ACTIVE ) {
820 LOG("%p still active", td);
826 LOG("Removed %p from QH %p", td, QH);
830 QH->Child = td->Link;
832 prev->Link = td->Link;
837 if( nCleaned == 0 ) {
838 LOG("Nothing cleaned... what the?");
841 // re-enable controller
842 _OutWord( Cont, USBCMD, 0x0001 );
845 void UHCI_int_HandleTDComplete(tUHCI_Controller *Cont, tUHCI_TD *TD)
847 int byte_count = (TD->Control & 0x7FF)+1;
848 tUHCI_ExtraTDInfo *info = TD->_info.ExtraInfo;
850 // Handle non page-aligned destination (or with a > 32-bit paddr)
851 // TODO: Needs fixing for alignment issues
855 int src_ofs = TD->BufferPointer & (PAGE_SIZE-1);
856 src = MM_MapTemp(TD->BufferPointer);
857 dest = MM_MapTemp(info->FirstPage);
858 // Check for a single page transfer
859 if( byte_count + info->Offset <= PAGE_SIZE )
861 LOG("Single page copy %P to %P of %p",
862 TD->BufferPointer, info->FirstPage, TD);
863 memcpy(dest + info->Offset, src + src_ofs, byte_count);
868 LOG("Multi page copy %P to (%P,%P) of %p",
869 TD->BufferPointer, info->FirstPage, info->SecondPage, TD);
870 int part_len = PAGE_SIZE - info->Offset;
871 memcpy(dest + info->Offset, src + src_ofs, part_len);
873 dest = MM_MapTemp(info->SecondPage);
874 memcpy(dest, src + src_ofs + part_len, byte_count - part_len);
881 if( info->Callback != NULL )
883 LOG("Calling cb %p (%i bytes)", info->Callback, byte_count);
884 void *ptr = MM_MapTemp( TD->BufferPointer );
885 info->Callback( info->CallbackPtr, ptr, byte_count );
890 if( TD->_info.QueueIndex > 127 )
893 TD->_info.ExtraInfo = NULL;
897 void UHCI_int_InterruptThread(void *Pointer)
899 tUHCI_Controller *Cont = Pointer;
900 Threads_SetName("UHCI Interrupt Handler");
907 Semaphore_Wait(&gUHCI_InterruptSempahore, 0);
910 for( int i = 0; i < NUM_TDs; i ++ )
914 td = &gaUHCI_TDPool[i];
916 // Skip completely inactive TDs
917 if( td->_info.bActive == 0 ) continue ;
918 // Skip ones that are still in use
919 if( td->Control & TD_CTL_ACTIVE ) continue ;
923 // If no callback/alt buffer, mark as free and move on
924 if( td->_info.ExtraInfo )
926 UHCI_int_HandleTDComplete(Cont, td);
930 if( td->Control & 0x00FF0000 ) {
931 LOG("td->control(Status) = %s%s%s%s%s%s%s%s",
932 td->Control & TD_CTL_ACTIVE ? "Active, " : "",
933 td->Control & TD_CTL_STALLED ? "Stalled, " : "",
934 td->Control & TD_CTL_DATABUFERR ? "Data Buffer Error, " : "",
935 td->Control & TD_CTL_BABBLE ? "Babble, " : "",
936 td->Control & TD_CTL_NAK ? "NAK, " : "",
937 td->Control & TD_CTL_CRCERR ? "CRC Error, " : "",
938 td->Control & TD_CTL_BITSTUFF ? "Bitstuff Error, " : "",
939 td->Control & TD_CTL_RESERVED ? "Reserved " : ""
941 LOG("From queue %i", td->_info.QueueIndex);
942 // Clean up QH (removing all inactive entries)
943 UHCI_int_CleanQH(Cont, Cont->TDQHPage->InterruptQHs + td->_info.QueueIndex);
947 // Handle rescheduling of interrupt TDs
948 if( td->_info.QueueIndex <= 127 )
950 LOG("Re-schedule interrupt %p (offset %i)", td, td->_info.QueueIndex);
951 // TODO: Flip toggle?
952 td->Control |= TD_CTL_ACTIVE;
953 // Add back into controller's interrupt list
956 Cont->TDQHPage->InterruptQHs + td->_info.QueueIndex,
963 if( td->_info.bFreePointer )
964 MM_DerefPhys( td->BufferPointer );
967 LOG("Cleaned %p (->Control = %x)", td, td->Control);
968 td->_info.bActive = 0;
972 LOG("Why did you wake me?");
977 void UHCI_InterruptHandler(int IRQ, void *Ptr)
979 tUHCI_Controller *Host = Ptr;
980 // int frame = (_InWord(Host, FRNUM) - 1) & 0x3FF;
981 Uint16 status = _InWord(Host, USBSTS);
983 LOG("%p: status = 0x%04x", Ptr, status);
985 // Interrupt-on-completion
988 // TODO: Support isochronous transfers (will need updating the frame pointer)
989 Semaphore_Signal(&gUHCI_InterruptSempahore, 1);
992 // USB Error Interrupt
995 Log_Notice("UHCI", "USB Error");
999 // - Fired if in suspend state and a USB device sends the RESUME signal
1002 Log_Notice("UHCI", "Resume Detect");
1005 // Host System Error
1008 Log_Notice("UHCI", "Host System Error");
1011 // Host Controller Process Error
1014 Log_Error("UHCI", "Host controller process error on controller %p", Ptr);
1016 //for( int i = 0; i < 1024; i += 4 ) {
1017 // LOG("%4i: %x", i, Host->FrameList[i]);
1020 tPAddr phys = Host->TDQHPage->ControlQH.Child;
1021 while( !(phys & 1) && MM_GetRefCount(phys & ~15))
1023 tUHCI_TD *td = UHCI_int_GetTDFromPhys(Host, phys);
1024 LOG("%08P: %08x %08x %08x", phys, td->Control, td->Token, td->BufferPointer);
1029 _OutWord(Host, USBSTS, status);
1032 void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value)
1034 if( Host->MemIOMap )
1035 ((Uint8*)Host->MemIOMap)[Reg] = Value;
1037 outb(Host->IOBase + Reg, Value);
1040 void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value)
1042 if( Host->MemIOMap )
1043 Host->MemIOMap[Reg/2] = Value;
1045 outw(Host->IOBase + Reg, Value);
1048 void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value)
1050 if( Host->MemIOMap )
1051 ((Uint32*)Host->MemIOMap)[Reg/4] = Value;
1053 outd(Host->IOBase + Reg, Value);
1056 Uint16 _InWord(tUHCI_Controller *Host, int Reg)
1058 if( Host->MemIOMap )
1059 return Host->MemIOMap[Reg/2];
1061 return inw(Host->IOBase + Reg);