3 * - By John Hodge (thePowersGang)
5 * Universal Host Controller Interface
8 #define VERSION VER2(0,5)
16 #include <semaphore.h>
19 #define MAX_CONTROLLERS 4
20 //#define NUM_TDs 1024
24 int UHCI_Initialise(char **Arguments);
26 tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont);
27 void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_TD *TD);
28 void *UHCI_int_SendTransaction(tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
29 void *UHCI_DataIN(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
30 void *UHCI_DataOUT(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
31 void *UHCI_SendSetup(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
32 int UHCI_IsTransferComplete(void *Ptr, void *Handle);
33 int UHCI_Int_InitHost(tUHCI_Controller *Host);
34 void UHCI_CheckPortUpdate(void *Ptr);
35 void UHCI_int_InterruptThread(void *Unused);
36 void UHCI_InterruptHandler(int IRQ, void *Ptr);
38 static void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value);
39 static void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value);
40 static void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value);
41 static Uint16 _InWord(tUHCI_Controller *Host, int Reg);
44 MODULE_DEFINE(0, VERSION, USB_UHCI, UHCI_Initialise, NULL, "USB_Core", NULL);
45 tUHCI_TD gaUHCI_TDPool[NUM_TDs];
46 tUHCI_Controller gUHCI_Controllers[MAX_CONTROLLERS];
47 tUSBHostDef gUHCI_HostDef = {
48 .SendIN = UHCI_DataIN,
49 .SendOUT = UHCI_DataOUT,
50 .SendSETUP = UHCI_SendSetup,
51 .IsOpComplete = UHCI_IsTransferComplete,
52 .CheckPorts = UHCI_CheckPortUpdate
54 tSemaphore gUHCI_InterruptSempahore;
58 * \fn int UHCI_Initialise()
59 * \brief Called to initialise the UHCI Driver
61 int UHCI_Initialise(char **Arguments)
68 // Initialise with no maximum value
69 Semaphore_Init( &gUHCI_InterruptSempahore, 0, 0, "UHCI", "Interrupt Queue");
71 if( PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, -1) < 0 )
73 LEAVE('i', MODULE_ERR_NOTNEEDED);
74 return MODULE_ERR_NOTNEEDED;
77 // Spin off interrupt handling thread
78 Proc_SpawnWorker( UHCI_int_InterruptThread, NULL );
80 // Enumerate PCI Bus, getting a maximum of `MAX_CONTROLLERS` devices
81 while( (id = PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS )
83 tUHCI_Controller *cinfo = &gUHCI_Controllers[i];
85 // NOTE: Check "protocol" from PCI?
88 base_addr = PCI_GetBAR(id, 4);
92 cinfo->IOBase = base_addr & ~1;
93 cinfo->MemIOMap = NULL;
97 cinfo->MemIOMap = (void*)MM_MapHWPages(base_addr, 1);
99 cinfo->IRQNum = PCI_GetIRQ(id);
101 Log_Debug("UHCI", "Controller PCI #%i: IO Base = 0x%x, IRQ %i",
102 id, base_addr, cinfo->IRQNum);
104 IRQ_AddHandler(cinfo->IRQNum, UHCI_InterruptHandler, cinfo);
107 ret = UHCI_Int_InitHost(&gUHCI_Controllers[i]);
114 cinfo->RootHub = USB_RegisterHost(&gUHCI_HostDef, cinfo, 2);
115 LOG("cinfo->RootHub = %p", cinfo->RootHub);
120 if(i == MAX_CONTROLLERS) {
121 Log_Warning("UHCI", "Over "EXPAND_STR(MAX_CONTROLLERS)" UHCI controllers detected, ignoring rest");
123 LEAVE('i', MODULE_ERR_OK);
124 return MODULE_ERR_OK;
128 * \fn void UHCI_Cleanup()
129 * \brief Called just before module is unloaded
135 tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont)
138 Mutex_Acquire( &lock );
139 for( int i = 0; i < NUM_TDs; i ++ )
141 if(gaUHCI_TDPool[i]._info.bActive == 0)
143 gaUHCI_TDPool[i].Link = 1;
144 gaUHCI_TDPool[i].Control = (1 << 23);
145 gaUHCI_TDPool[i]._info.bActive = 1;
146 gaUHCI_TDPool[i]._info.bComplete = 0;
147 Mutex_Release( &lock );
148 return &gaUHCI_TDPool[i];
151 Mutex_Release( &lock );
155 void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_TD *TD)
157 static tMutex lock; // TODO: Should I use a shortlock (avoid being preempted)
159 Mutex_Acquire(&lock);
164 next_frame = (_InWord(Cont, FRNUM) + 2) & (1024-1);
166 TD->Control |= (1 << 24); // Ensure that there is an interrupt for each used frame
168 TD->Link = Cont->FrameList[next_frame];
169 Cont->FrameList[next_frame] = MM_GetPhysAddr( (tVAddr)TD );
172 // TODO: Support other QHs
173 tUHCI_QH *qh = &Cont->BulkQH;
175 // Ensure that there is an interrupt for each used frame
176 TD->Control |= (1 << 24);
179 _OutWord( Cont, USBCMD, 0x0000 );
183 if( qh->Child & 1 ) {
184 qh->Child = MM_GetPhysAddr( (tVAddr)TD );
187 qh->_LastItem->Link = MM_GetPhysAddr( (tVAddr)TD );
191 // Reenable controller
192 _OutWord( Cont, USBCMD, 0x0001 );
193 LOG("TD(%p)->Control = %x", TD, TD->Control);
196 Mutex_Release(&lock);
200 * \brief Send a transaction to the USB bus
201 * \param Cont Controller pointer
202 * \param Addr Function Address * 16 + Endpoint
203 * \param bTgl Data toggle value
205 void *UHCI_int_SendTransaction(
206 tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl,
207 tUSBHostCb Cb, void *CbData, void *Data, size_t Length)
210 tUHCI_ExtraTDInfo *info = NULL;
212 if( Length > 0x400 ) {
213 Log_Error("UHCI", "Transaction length too large (%i > 0x400)", Length);
214 return NULL; // Controller allows up to 0x500, but USB doesn't
217 td = UHCI_int_AllocateTD(Cont);
220 // TODO: Wait for one to free?
221 Log_Error("UHCI", "No avaliable TDs, transaction dropped");
225 LOG("TD %p %i bytes, Type %x to 0x%x",
226 td, Length, Type, Addr);
228 td->Control = (Length - 1) & 0x7FF;
229 td->Control |= (1 << 23);
230 td->Token = ((Length - 1) & 0x7FF) << 21;
231 td->Token |= (bTgl & 1) << 19;
232 td->Token |= (Addr & 0xF) << 15;
233 td->Token |= ((Addr/16) & 0xFF) << 8;
237 ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE
239 || MM_GetPhysAddr( (tVAddr)Data ) >> 32
243 td->BufferPointer = MM_AllocPhysRange(1, 32);
245 LOG("Allocated page %x", td->BufferPointer);
247 if( Type == 0x69 ) // IN token
250 info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 );
251 info->Offset = ((tVAddr)Data & (PAGE_SIZE-1));
252 info->FirstPage = MM_GetPhysAddr( (tVAddr)Data );
253 info->SecondPage = MM_GetPhysAddr( (tVAddr)Data + Length - 1 );
257 LOG("Relocated OUT/SETUP");
258 tVAddr ptr = MM_MapTemp(td->BufferPointer);
259 memcpy( (void*)ptr, Data, Length );
261 td->Control |= (1 << 24);
263 td->_info.bFreePointer = 1;
267 td->BufferPointer = MM_GetPhysAddr( (tVAddr)Data );
268 td->_info.bFreePointer = 0;
271 // Interrupt on completion
275 info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 );
276 LOG("IOC Cb=%p CbData=%p", Cb, CbData);
277 // NOTE: if ERRPTR then the TD is kept allocated until checked
279 info->CallbackPtr = CbData;
283 LOG("info = %p", info);
284 td->Control |= (1 << 24);
285 td->_info.ExtraInfo = info;
286 LOG("TD(%p)->Control = 0x%0x", td, td->Control);
289 UHCI_int_AppendTD(Cont, td);
294 void *UHCI_DataIN(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
296 return UHCI_int_SendTransaction(Ptr, Dest, 0x69, DataTgl, Cb, CbData, Buf, Length);
299 void *UHCI_DataOUT(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
301 return UHCI_int_SendTransaction(Ptr, Dest, 0xE1, DataTgl, Cb, CbData, Buf, Length);
304 void *UHCI_SendSetup(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
306 return UHCI_int_SendTransaction(Ptr, Dest, 0x2D, DataTgl, Cb, CbData, Buf, Length);
309 int UHCI_IsTransferComplete(void *Ptr, void *Handle)
311 tUHCI_TD *td = Handle;
313 tUHCI_Controller *Cont = &gUHCI_Controllers[0];
314 LOG("%p->Control = 0x%0x", td, td->Control);
315 LOG("USBSTS = 0x%x, USBINTR = 0x%x", _InWord(Cont, USBSTS), _InWord(Cont, USBINTR));
316 LOG("Cont->BulkQH.Child = %x", Cont->BulkQH.Child);
318 if(td->Control & (1 << 23)) {
321 // LOG("inactive, waiting for completion");
322 if(td->_info.bComplete)
324 td->_info.bActive = 0;
325 td->_info.bComplete = 0;
335 // === INTERNAL FUNCTIONS ===
337 * \fn int UHCI_Int_InitHost(tUCHI_Controller *Host)
338 * \brief Initialises a UHCI host controller
339 * \param Host Pointer - Host to initialise
341 int UHCI_Int_InitHost(tUHCI_Controller *Host)
343 ENTER("pHost", Host);
345 _OutWord( Host, USBCMD, 4 ); // GRESET
347 _OutWord( Host, USBCMD, 0 ); // GRESET
349 // Allocate Frame List
350 // - 1 Page, 32-bit address
351 // - 1 page = 1024 4 byte entries
352 Host->FrameList = (void *) MM_AllocDMA(1, 32, &Host->PhysFrameList);
353 if( !Host->FrameList ) {
354 Log_Warning("UHCI", "Unable to allocate frame list, aborting");
359 // TODO: Handle QHs not being in a 32-bit paddr range
360 // Need another page, probably get some more TDs from it too
362 // Set up interrupt and bulk queue
363 Host->InterruptQH.Next = MM_GetPhysAddr( (tVAddr)&Host->ControlQH ) | 2;
364 Host->InterruptQH.Child = 1;
365 Host->ControlQH.Next = MM_GetPhysAddr( (tVAddr)&Host->BulkQH ) | 2;
366 Host->ControlQH.Child = 1;
367 Host->BulkQH.Next = 1;
368 Host->BulkQH.Child = 1;
370 LOG("Allocated frame list 0x%x (0x%x)", Host->FrameList, Host->PhysFrameList);
371 for( int i = 0; i < 1024; i ++ )
372 Host->FrameList[i] = MM_GetPhysAddr( (tVAddr)&Host->InterruptQH ) | 2;
374 // Set frame length to 1 ms
375 _OutByte( Host, SOFMOD, 64 );
378 _OutDWord( Host, FLBASEADD, Host->PhysFrameList );
379 _OutWord( Host, FRNUM, 0 );
382 _OutWord( Host, USBINTR, 0x000F );
383 PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 );
386 _OutWord( Host, USBCMD, 0x0001 );
392 void UHCI_CheckPortUpdate(void *Ptr)
394 tUHCI_Controller *Host = Ptr;
396 for( int i = 0; i < 2; i ++ )
398 int port = PORTSC1 + i*2;
401 status = _InWord(Host, port);
402 // Check for port change
403 if( !(status & 0x0002) ) continue;
404 _OutWord(Host, port, 0x0002);
406 // Check if the port is connected
409 // Tell the USB code it's gone.
410 USB_DeviceDisconnected(Host->RootHub, i);
415 LOG("Port %i has something", i);
416 // Reset port (set bit 9)
418 _OutWord(Host, port, 0x0200);
419 Time_Delay(50); // 50ms delay
420 _OutWord(Host, port, _InWord(Host, port) & ~0x0200);
423 Time_Delay(50); // 50ms delay
424 _OutWord(Host, port, _InWord(Host, port) | 0x0004);
425 // Tell USB there's a new device
426 USB_DeviceConnected(Host->RootHub, i);
431 void UHCI_int_InterruptThread(void *Unused)
433 Threads_SetName("UHCI Interrupt Handler");
438 Semaphore_Wait(&gUHCI_InterruptSempahore, 0);
441 for( int i = 0; i < NUM_TDs; i ++ )
444 tUHCI_ExtraTDInfo *info;
447 td = &gaUHCI_TDPool[i];
448 info = td->_info.ExtraInfo;
450 // Skip completely inactive TDs
451 if( td->_info.bActive == 0 ) continue ;
452 // Skip ones that are still in use
453 if( td->Control & (1 << 23) ) continue ;
455 // If no callback/alt buffer, mark as free and move on
456 if( td->_info.ExtraInfo )
458 // Get size of transfer
459 byte_count = (td->Control & 0x7FF)+1;
461 // Handle non page-aligned destination (with a > 32-bit paddr)
465 int src_ofs = td->BufferPointer & (PAGE_SIZE-1);
466 src = (void *) MM_MapTemp(td->BufferPointer);
467 dest = (void *) MM_MapTemp(info->FirstPage);
468 // Check for a single page transfer
469 if( byte_count + info->Offset <= PAGE_SIZE )
471 LOG("Single page copy %P to %P of %p",
472 td->BufferPointer, info->FirstPage, td);
473 memcpy(dest + info->Offset, src + src_ofs, byte_count);
478 LOG("Multi page copy %P to (%P,%P) of %p",
479 td->BufferPointer, info->FirstPage, info->SecondPage, td);
480 int part_len = PAGE_SIZE - info->Offset;
481 memcpy(dest + info->Offset, src + src_ofs, part_len);
482 MM_FreeTemp( (tVAddr)dest );
483 dest = (void *) MM_MapTemp(info->SecondPage);
484 memcpy(dest, src + src_ofs + part_len, byte_count - part_len);
486 MM_FreeTemp( (tVAddr)src );
487 MM_FreeTemp( (tVAddr)dest );
490 // Don't mark as inactive, the check should do that
491 if( info->Callback == INVLPTR )
493 LOG("Marking %p as complete", td);
494 td->_info.bComplete = 1;
496 td->_info.ExtraInfo = NULL;
497 if( td->_info.bFreePointer )
498 MM_DerefPhys( td->BufferPointer );
503 if( info->Callback != NULL )
505 LOG("Calling cb %p", info->Callback);
506 void *ptr = (void *) MM_MapTemp( td->BufferPointer );
507 info->Callback( info->CallbackPtr, ptr, byte_count );
508 MM_FreeTemp( (tVAddr)ptr );
513 td->_info.ExtraInfo = NULL;
516 if( td->_info.bFreePointer )
517 MM_DerefPhys( td->BufferPointer );
520 td->_info.bActive = 0;
521 LOG("Cleaned %p", td);
526 void UHCI_InterruptHandler(int IRQ, void *Ptr)
528 tUHCI_Controller *Host = Ptr;
529 // int frame = (_InWord(Host, FRNUM) - 1) & 0x3FF;
530 Uint16 status = _InWord(Host, USBSTS);
532 // Interrupt-on-completion
535 // TODO: Support isochronous transfers (will need updating the frame pointer)
536 Semaphore_Signal(&gUHCI_InterruptSempahore, 1);
539 LOG("status = 0x%04x", status);
540 _OutWord(Host, USBSTS, status);
543 void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value)
546 ((Uint8*)Host->MemIOMap)[Reg] = Value;
548 outb(Host->IOBase + Reg, Value);
551 void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value)
554 Host->MemIOMap[Reg/2] = Value;
556 outw(Host->IOBase + Reg, Value);
559 void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value)
562 ((Uint32*)Host->MemIOMap)[Reg/4] = Value;
564 outd(Host->IOBase + Reg, Value);
567 Uint16 _InWord(tUHCI_Controller *Host, int Reg)
570 return Host->MemIOMap[Reg/2];
572 return inw(Host->IOBase + Reg);