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 );
195 Mutex_Release(&lock);
199 * \brief Send a transaction to the USB bus
200 * \param Cont Controller pointer
201 * \param Addr Function Address * 16 + Endpoint
202 * \param bTgl Data toggle value
204 void *UHCI_int_SendTransaction(
205 tUHCI_Controller *Cont, int Addr, Uint8 Type, int bTgl,
206 tUSBHostCb Cb, void *CbData, void *Data, size_t Length)
209 tUHCI_ExtraTDInfo *info = NULL;
211 if( Length > 0x400 ) {
212 Log_Error("UHCI", "Transaction length too large (%i > 0x400)", Length);
213 return NULL; // Controller allows up to 0x500, but USB doesn't
216 td = UHCI_int_AllocateTD(Cont);
219 // TODO: Wait for one to free?
220 Log_Error("UHCI", "No avaliable TDs, transaction dropped");
224 LOG("TD %p %i bytes, Type %x to 0x%x",
225 td, Length, Type, Addr);
227 td->Control = (Length - 1) & 0x7FF;
228 td->Control |= (1 << 23);
229 td->Token = ((Length - 1) & 0x7FF) << 21;
230 td->Token |= (bTgl & 1) << 19;
231 td->Token |= (Addr & 0xF) << 15;
232 td->Token |= ((Addr/16) & 0xFF) << 8;
236 ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE
238 || MM_GetPhysAddr( (tVAddr)Data ) >> 32
242 td->BufferPointer = MM_AllocPhysRange(1, 32);
244 LOG("Allocated page %x", td->BufferPointer);
246 if( Type == 0x69 ) // IN token
249 info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 );
250 info->Offset = ((tVAddr)Data & (PAGE_SIZE-1));
251 info->FirstPage = MM_GetPhysAddr( (tVAddr)Data );
252 info->SecondPage = MM_GetPhysAddr( (tVAddr)Data + Length - 1 );
256 LOG("Relocated OUT/SETUP");
257 tVAddr ptr = MM_MapTemp(td->BufferPointer);
258 memcpy( (void*)ptr, Data, Length );
260 td->Control |= (1 << 24);
262 td->_info.bFreePointer = 1;
266 td->BufferPointer = MM_GetPhysAddr( (tVAddr)Data );
267 td->_info.bFreePointer = 0;
270 // Interrupt on completion
274 info = calloc( sizeof(tUHCI_ExtraTDInfo), 1 );
275 LOG("IOC Cb=%p CbData=%p", Cb, CbData);
276 // NOTE: if ERRPTR then the TD is kept allocated until checked
278 info->CallbackPtr = CbData;
282 LOG("info = %p", info);
283 td->Control |= (1 << 24);
284 td->_info.ExtraInfo = info;
287 UHCI_int_AppendTD(Cont, td);
292 void *UHCI_DataIN(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
294 return UHCI_int_SendTransaction(Ptr, Dest, 0x69, DataTgl, Cb, CbData, Buf, Length);
297 void *UHCI_DataOUT(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
299 return UHCI_int_SendTransaction(Ptr, Dest, 0xE1, DataTgl, Cb, CbData, Buf, Length);
302 void *UHCI_SendSetup(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
304 return UHCI_int_SendTransaction(Ptr, Dest, 0x2D, DataTgl, Cb, CbData, Buf, Length);
307 int UHCI_IsTransferComplete(void *Ptr, void *Handle)
309 tUHCI_TD *td = Handle;
311 tUHCI_Controller *Cont = &gUHCI_Controllers[0];
312 LOG("%p->Control = %x", td, td->Control);
313 LOG("USBSTS = 0x%x, USBINTR = 0x%x", _InWord(Cont, USBSTS), _InWord(Cont, USBINTR));
314 LOG("Cont->BulkQH.Child = %x", Cont->BulkQH.Child);
316 if(td->Control & (1 << 23)) {
319 // LOG("inactive, waiting for completion");
320 if(td->_info.bComplete)
322 td->_info.bActive = 0;
323 td->_info.bComplete = 0;
333 // === INTERNAL FUNCTIONS ===
335 * \fn int UHCI_Int_InitHost(tUCHI_Controller *Host)
336 * \brief Initialises a UHCI host controller
337 * \param Host Pointer - Host to initialise
339 int UHCI_Int_InitHost(tUHCI_Controller *Host)
341 ENTER("pHost", Host);
343 _OutWord( Host, USBCMD, 4 ); // GRESET
345 _OutWord( Host, USBCMD, 0 ); // GRESET
347 // Allocate Frame List
348 // - 1 Page, 32-bit address
349 // - 1 page = 1024 4 byte entries
350 Host->FrameList = (void *) MM_AllocDMA(1, 32, &Host->PhysFrameList);
351 if( !Host->FrameList ) {
352 Log_Warning("UHCI", "Unable to allocate frame list, aborting");
357 // TODO: Handle QHs not being in a 32-bit paddr range
358 // Need another page, probably get some more TDs from it too
360 // Set up interrupt and bulk queue
361 Host->InterruptQH.Next = MM_GetPhysAddr( (tVAddr)&Host->ControlQH ) | 2;
362 Host->InterruptQH.Child = 1;
363 Host->ControlQH.Next = MM_GetPhysAddr( (tVAddr)&Host->BulkQH ) | 2;
364 Host->ControlQH.Child = 1;
365 Host->BulkQH.Next = 1;
366 Host->BulkQH.Child = 1;
368 LOG("Allocated frame list 0x%x (0x%x)", Host->FrameList, Host->PhysFrameList);
369 for( int i = 0; i < 1024; i ++ )
370 Host->FrameList[i] = MM_GetPhysAddr( (tVAddr)&Host->InterruptQH ) | 2;
372 // Set frame length to 1 ms
373 _OutByte( Host, SOFMOD, 64 );
376 _OutDWord( Host, FLBASEADD, Host->PhysFrameList );
377 _OutWord( Host, FRNUM, 0 );
380 _OutWord( Host, USBINTR, 0x000F );
381 PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 );
384 _OutWord( Host, USBCMD, 0x0001 );
390 void UHCI_CheckPortUpdate(void *Ptr)
392 tUHCI_Controller *Host = Ptr;
394 for( int i = 0; i < 2; i ++ )
396 int port = PORTSC1 + i*2;
399 status = _InWord(Host, port);
400 // Check for port change
401 if( !(status & 0x0002) ) continue;
402 _OutWord(Host, port, 0x0002);
404 // Check if the port is connected
407 // Tell the USB code it's gone.
408 USB_DeviceDisconnected(Host->RootHub, i);
413 LOG("Port %i has something", i);
414 // Reset port (set bit 9)
416 _OutWord(Host, port, 0x0200);
417 Time_Delay(50); // 50ms delay
418 _OutWord(Host, port, _InWord(Host, port) & ~0x0200);
421 Time_Delay(50); // 50ms delay
422 _OutWord(Host, port, _InWord(Host, port) | 0x0004);
423 // Tell USB there's a new device
424 USB_DeviceConnected(Host->RootHub, i);
429 void UHCI_int_InterruptThread(void *Unused)
431 Threads_SetName("UHCI Interrupt Handler");
436 Semaphore_Wait(&gUHCI_InterruptSempahore, 0);
439 for( int i = 0; i < NUM_TDs; i ++ )
442 tUHCI_ExtraTDInfo *info;
445 td = &gaUHCI_TDPool[i];
446 info = td->_info.ExtraInfo;
448 // Skip completely inactive TDs
449 if( td->_info.bActive == 0 ) continue ;
450 // Skip ones that are still in use
451 if( td->Control & (1 << 23) ) continue ;
453 // If no callback/alt buffer, mark as free and move on
454 if( td->_info.ExtraInfo )
456 // Get size of transfer
457 byte_count = (td->Control & 0x7FF)+1;
459 // Handle non page-aligned destination (with a > 32-bit paddr)
463 int src_ofs = td->BufferPointer & (PAGE_SIZE-1);
464 src = (void *) MM_MapTemp(td->BufferPointer);
465 dest = (void *) MM_MapTemp(info->FirstPage);
466 // Check for a single page transfer
467 if( byte_count + info->Offset <= PAGE_SIZE )
469 LOG("Single page copy %P to %P of %p",
470 td->BufferPointer, info->FirstPage, td);
471 memcpy(dest + info->Offset, src + src_ofs, byte_count);
476 LOG("Multi page copy %P to (%P,%P) of %p",
477 td->BufferPointer, info->FirstPage, info->SecondPage, td);
478 int part_len = PAGE_SIZE - info->Offset;
479 memcpy(dest + info->Offset, src + src_ofs, part_len);
480 MM_FreeTemp( (tVAddr)dest );
481 dest = (void *) MM_MapTemp(info->SecondPage);
482 memcpy(dest, src + src_ofs + part_len, byte_count - part_len);
484 MM_FreeTemp( (tVAddr)src );
485 MM_FreeTemp( (tVAddr)dest );
488 // Don't mark as inactive, the check should do that
489 if( info->Callback == INVLPTR )
491 LOG("Marking %p as complete", td);
492 td->_info.bComplete = 1;
494 td->_info.ExtraInfo = NULL;
495 if( td->_info.bFreePointer )
496 MM_DerefPhys( td->BufferPointer );
501 if( info->Callback != NULL )
503 LOG("Calling cb %p", info->Callback);
504 void *ptr = (void *) MM_MapTemp( td->BufferPointer );
505 info->Callback( info->CallbackPtr, ptr, byte_count );
506 MM_FreeTemp( (tVAddr)ptr );
511 td->_info.ExtraInfo = NULL;
514 if( td->_info.bFreePointer )
515 MM_DerefPhys( td->BufferPointer );
518 td->_info.bActive = 0;
519 LOG("Cleaned %p", td);
524 void UHCI_InterruptHandler(int IRQ, void *Ptr)
526 tUHCI_Controller *Host = Ptr;
527 // int frame = (_InWord(Host, FRNUM) - 1) & 0x3FF;
528 Uint16 status = _InWord(Host, USBSTS);
530 // Interrupt-on-completion
533 // TODO: Support isochronous transfers (will need updating the frame pointer)
534 Semaphore_Signal(&gUHCI_InterruptSempahore, 1);
537 LOG("status = 0x%04x", status);
538 _OutWord(Host, USBSTS, status);
541 void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value)
544 ((Uint8*)Host->MemIOMap)[Reg] = Value;
546 outb(Host->IOBase + Reg, Value);
549 void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value)
552 Host->MemIOMap[Reg/2] = Value;
554 outw(Host->IOBase + Reg, Value);
557 void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value)
560 ((Uint32*)Host->MemIOMap)[Reg/4] = Value;
562 outd(Host->IOBase + Reg, Value);
565 Uint16 _InWord(tUHCI_Controller *Host, int Reg)
568 return Host->MemIOMap[Reg/2];
570 return inw(Host->IOBase + Reg);