48960c21870a68831bb7e0b6d62d3efcdf62dc4b
[tpg/acess2.git] / KernelLand / Modules / USB / UHCI / uhci.c
1 /*
2  * Acess 2 USB Stack
3  * - By John Hodge (thePowersGang)
4  *
5  * Universal Host Controller Interface
6  */
7 #define DEBUG   1
8 #define VERSION VER2(0,5)
9 #include <acess.h>
10 #include <vfs.h>
11 #include <drv_pci.h>
12 #include <modules.h>
13 #include <usb_host.h>
14 #include "uhci.h"
15 #include <timers.h>
16 #include <semaphore.h>
17
18 // === CONSTANTS ===
19 #define MAX_CONTROLLERS 4
20 //#define NUM_TDs       1024
21 #define NUM_TDs 64
22
23 // === PROTOTYPES ===
24  int    UHCI_Initialise(char **Arguments);
25 void    UHCI_Cleanup();
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);
37 // 
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);
42
43 // === GLOBALS ===
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
53         };
54 tSemaphore      gUHCI_InterruptSempahore;
55
56 // === CODE ===
57 /**
58  * \fn int UHCI_Initialise()
59  * \brief Called to initialise the UHCI Driver
60  */
61 int UHCI_Initialise(char **Arguments)
62 {
63          int    i=0, id=-1;
64          int    ret;
65         
66         ENTER("");
67         
68         // Initialise with no maximum value
69         Semaphore_Init( &gUHCI_InterruptSempahore, 0, 0, "UHCI", "Interrupt Queue");
70
71         if( PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, -1) < 0 )
72         {
73                 LEAVE('i', MODULE_ERR_NOTNEEDED);
74                 return MODULE_ERR_NOTNEEDED;
75         }
76         
77         // Spin off interrupt handling thread
78         Proc_SpawnWorker( UHCI_int_InterruptThread, NULL );
79
80         // Enumerate PCI Bus, getting a maximum of `MAX_CONTROLLERS` devices
81         while( (id = PCI_GetDeviceByClass(0x0C0300, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS )
82         {
83                 tUHCI_Controller        *cinfo = &gUHCI_Controllers[i];
84                 Uint32  base_addr;
85                 // NOTE: Check "protocol" from PCI?
86                 
87                 cinfo->PciId = id;
88                 base_addr = PCI_GetBAR(id, 4);
89                 
90                 if( base_addr & 1 )
91                 {
92                         cinfo->IOBase = base_addr & ~1;
93                         cinfo->MemIOMap = NULL;
94                 }
95                 else
96                 {
97                         cinfo->MemIOMap = (void*)MM_MapHWPages(base_addr, 1);
98                 }
99                 cinfo->IRQNum = PCI_GetIRQ(id);
100                 
101                 Log_Debug("UHCI", "Controller PCI #%i: IO Base = 0x%x, IRQ %i",
102                         id, base_addr, cinfo->IRQNum);
103                 
104                 IRQ_AddHandler(cinfo->IRQNum, UHCI_InterruptHandler, cinfo);
105         
106                 // Initialise Host
107                 ret = UHCI_Int_InitHost(&gUHCI_Controllers[i]);
108                 // Detect an error
109                 if(ret != 0) {
110                         LEAVE('i', ret);
111                         return ret;
112                 }
113                 
114                 cinfo->RootHub = USB_RegisterHost(&gUHCI_HostDef, cinfo, 2);
115                 LOG("cinfo->RootHub = %p", cinfo->RootHub);
116
117                 i ++;
118         }
119
120         if(i == MAX_CONTROLLERS) {
121                 Log_Warning("UHCI", "Over "EXPAND_STR(MAX_CONTROLLERS)" UHCI controllers detected, ignoring rest");
122         }
123         LEAVE('i', MODULE_ERR_OK);
124         return MODULE_ERR_OK;
125 }
126
127 /**
128  * \fn void UHCI_Cleanup()
129  * \brief Called just before module is unloaded
130  */
131 void UHCI_Cleanup()
132 {
133 }
134
135 tUHCI_TD *UHCI_int_AllocateTD(tUHCI_Controller *Cont)
136 {
137         static tMutex   lock;
138         Mutex_Acquire( &lock );
139         for( int i = 0; i < NUM_TDs; i ++ )
140         {
141                 if(gaUHCI_TDPool[i]._info.bActive == 0)
142                 {
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];
149                 }
150         }
151         Mutex_Release( &lock );
152         return NULL;
153 }
154
155 void UHCI_int_AppendTD(tUHCI_Controller *Cont, tUHCI_TD *TD)
156 {
157         static tMutex   lock;   // TODO: Should I use a shortlock (avoid being preempted)
158
159         Mutex_Acquire(&lock);
160         
161         #if 0
162          int    next_frame;
163
164         next_frame = (_InWord(Cont, FRNUM) + 2) & (1024-1);
165
166         TD->Control |= (1 << 24);       // Ensure that there is an interrupt for each used frame
167         
168         TD->Link = Cont->FrameList[next_frame];
169         Cont->FrameList[next_frame] = MM_GetPhysAddr( (tVAddr)TD );
170         #else
171
172         // TODO: Support other QHs
173         tUHCI_QH *qh = &Cont->BulkQH;
174         
175         // Ensure that there is an interrupt for each used frame
176         TD->Control |= (1 << 24);
177
178         // Stop controller
179         _OutWord( Cont, USBCMD, 0x0000 );
180         
181         // Add
182         TD->Link = 1;
183         if( qh->Child & 1 ) {
184                 qh->Child = MM_GetPhysAddr( (tVAddr)TD );
185         }
186         else {
187                 qh->_LastItem->Link = MM_GetPhysAddr( (tVAddr)TD );
188         }
189         qh->_LastItem = TD;
190
191         // Reenable controller
192         _OutWord( Cont, USBCMD, 0x0001 );
193         LOG("TD(%p)->Control = %x", TD, TD->Control);
194         #endif
195
196         Mutex_Release(&lock);
197 }
198
199 /**
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
204  */
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)
208 {
209         tUHCI_TD        *td;
210         tUHCI_ExtraTDInfo       *info = NULL;
211
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
215         }
216
217         td = UHCI_int_AllocateTD(Cont);
218
219         if( !td ) {
220                 // TODO: Wait for one to free?
221                 Log_Error("UHCI", "No avaliable TDs, transaction dropped");
222                 return NULL;
223         }
224
225         LOG("TD %p %i bytes, Type %x to 0x%x",
226                 td, Length, Type, Addr);
227
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;
234         td->Token |= Type;
235
236         if(
237                 ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE
238         #if PHYS_BITS > 32
239                 || MM_GetPhysAddr( (tVAddr)Data ) >> 32
240         #endif
241                 )
242         {
243                 td->BufferPointer = MM_AllocPhysRange(1, 32);
244
245                 LOG("Allocated page %x", td->BufferPointer);            
246
247                 if( Type == 0x69 )      // IN token
248                 {
249                         LOG("Relocated IN");
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 );
254                 }
255                 else
256                 {
257                         LOG("Relocated OUT/SETUP");
258                         tVAddr  ptr = MM_MapTemp(td->BufferPointer);
259                         memcpy( (void*)ptr, Data, Length );
260                         MM_FreeTemp(ptr);
261                         td->Control |= (1 << 24);
262                 }
263                 td->_info.bFreePointer = 1;
264         }
265         else
266         {
267                 td->BufferPointer = MM_GetPhysAddr( (tVAddr)Data );
268                 td->_info.bFreePointer = 0;
269         }
270
271         // Interrupt on completion
272         if( Cb )
273         {
274                 if( !info )
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
278                 info->Callback = Cb;
279                 info->CallbackPtr = CbData;
280         }
281         
282         if( info ) {
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);
287         }
288
289         UHCI_int_AppendTD(Cont, td);
290
291         return td;
292 }
293
294 void *UHCI_DataIN(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
295 {
296         return UHCI_int_SendTransaction(Ptr, Dest, 0x69, DataTgl, Cb, CbData, Buf, Length);
297 }
298
299 void *UHCI_DataOUT(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
300 {
301         return UHCI_int_SendTransaction(Ptr, Dest, 0xE1, DataTgl, Cb, CbData, Buf, Length);
302 }
303
304 void *UHCI_SendSetup(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
305 {
306         return UHCI_int_SendTransaction(Ptr, Dest, 0x2D, DataTgl, Cb, CbData, Buf, Length);
307 }
308
309 int UHCI_IsTransferComplete(void *Ptr, void *Handle)
310 {
311         tUHCI_TD        *td = Handle;
312         #if DEBUG
313         tUHCI_Controller        *Cont = &gUHCI_Controllers[0];
314         LOG("%p->Control = 0x%08x", 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);
317         #endif
318         if(td->Control & (1 << 23)) {
319                 return 0;
320         }
321         LOG("inactive, waiting for completion");
322         if(td->_info.bComplete)
323         {
324                 td->_info.bActive = 0;
325                 td->_info.bComplete = 0;
326                 td->Link = 0;
327                 return 1;
328         }
329         else
330         {
331                 return 0;
332         }
333 }
334
335 // === INTERNAL FUNCTIONS ===
336 /**
337  * \fn int UHCI_Int_InitHost(tUCHI_Controller *Host)
338  * \brief Initialises a UHCI host controller
339  * \param Host  Pointer - Host to initialise
340  */
341 int UHCI_Int_InitHost(tUHCI_Controller *Host)
342 {
343         ENTER("pHost", Host);
344
345         _OutWord( Host, USBCMD, 4 );    // GRESET
346         Time_Delay(10);
347         _OutWord( Host, USBCMD, 0 );    // GRESET
348         
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");
355                 LEAVE('i', -1);
356                 return -1;
357         }
358
359         // TODO: Handle QHs not being in a 32-bit paddr range
360         // Need another page, probably get some more TDs from it too
361
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;
369
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;
373         
374         // Set frame length to 1 ms
375         _OutByte( Host, SOFMOD, 64 );
376         
377         // Set Frame List
378         _OutDWord( Host, FLBASEADD, Host->PhysFrameList );
379         _OutWord( Host, FRNUM, 0 );
380         
381         // Enable Interrupts
382         _OutWord( Host, USBINTR, 0x000F );
383         PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 );
384
385         // Enable processing
386         _OutWord( Host, USBCMD, 0x0001 );
387
388         LEAVE('i', 0);
389         return 0;
390 }
391
392 void UHCI_CheckPortUpdate(void *Ptr)
393 {
394         tUHCI_Controller        *Host = Ptr;
395         // Enable ports
396         for( int i = 0; i < 2; i ++ )
397         {
398                  int    port = PORTSC1 + i*2;
399                 Uint16  status;
400         
401                 status = _InWord(Host, port);
402                 // Check for port change
403                 if( !(status & 0x0002) )        continue;
404                 _OutWord(Host, port, 0x0002);
405                 
406                 // Check if the port is connected
407                 if( !(status & 1) )
408                 {
409                         // Tell the USB code it's gone.
410                         USB_DeviceDisconnected(Host->RootHub, i);
411                         continue;
412                 }
413                 else
414                 {
415                         LOG("Port %i has something", i);
416                         // Reset port (set bit 9)
417                         LOG("Reset");
418                         _OutWord(Host, port, 0x0200);
419                         Time_Delay(50); // 50ms delay
420                         _OutWord(Host, port, _InWord(Host, port) & ~0x0200);
421                         // Enable port
422                         LOG("Enable");
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);
427                 }
428         }
429 }
430
431 void UHCI_int_InterruptThread(void *Unused)
432 {
433         Threads_SetName("UHCI Interrupt Handler");
434         for( ;; )
435         {
436                 LOG("zzzzz....");
437                 // 0 = Take all
438                 Semaphore_Wait(&gUHCI_InterruptSempahore, 0);
439                 LOG("Huh?");
440         
441                 for( int i = 0; i < NUM_TDs; i ++ )
442                 {
443                          int    byte_count;
444                         tUHCI_ExtraTDInfo       *info;
445                         tUHCI_TD        *td;
446                         
447                         td = &gaUHCI_TDPool[i];
448                         info = td->_info.ExtraInfo;
449
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 ;
454                         // Skip ones that are waiting for ACK
455                         if( td->_info.bComplete == 1 )  continue ;
456
457                         // If no callback/alt buffer, mark as free and move on
458                         if( td->_info.ExtraInfo )
459                         {
460                                 // Get size of transfer
461                                 byte_count = (td->Control & 0x7FF)+1;
462                         
463                                 // Handle non page-aligned destination (with a > 32-bit paddr)
464                                 if(info->FirstPage)
465                                 {
466                                         char    *src, *dest;
467                                          int    src_ofs = td->BufferPointer & (PAGE_SIZE-1);
468                                         src = (void *) MM_MapTemp(td->BufferPointer);
469                                         dest = (void *) MM_MapTemp(info->FirstPage);
470                                         // Check for a single page transfer
471                                         if( byte_count + info->Offset <= PAGE_SIZE )
472                                         {
473                                                 LOG("Single page copy %P to %P of %p",
474                                                         td->BufferPointer, info->FirstPage, td);
475                                                 memcpy(dest + info->Offset, src + src_ofs, byte_count);
476                                         }
477                                         else
478                                         {
479                                                 // Multi-page
480                                                 LOG("Multi page copy %P to (%P,%P) of %p",
481                                                         td->BufferPointer, info->FirstPage, info->SecondPage, td);
482                                                  int    part_len = PAGE_SIZE - info->Offset;
483                                                 memcpy(dest + info->Offset, src + src_ofs, part_len);
484                                                 MM_FreeTemp( (tVAddr)dest );
485                                                 dest = (void *) MM_MapTemp(info->SecondPage);
486                                                 memcpy(dest, src + src_ofs + part_len, byte_count - part_len);
487                                         }
488                                         MM_FreeTemp( (tVAddr)src );
489                                         MM_FreeTemp( (tVAddr)dest );
490                                 }
491         
492                                 // Don't mark as inactive, the check should do that
493                                 if( info->Callback == INVLPTR )
494                                 {
495                                         LOG("Marking %p as complete", td);
496                                         td->_info.bComplete = 1;
497                                         free( info );
498                                         td->_info.ExtraInfo = NULL;
499                                         if( td->_info.bFreePointer )
500                                                 MM_DerefPhys( td->BufferPointer );                      
501                                         continue ;
502                                 }
503
504                                 // Callback
505                                 if( info->Callback != NULL )
506                                 {
507                                         LOG("Calling cb %p", info->Callback);
508                                         void    *ptr = (void *) MM_MapTemp( td->BufferPointer );
509                                         info->Callback( info->CallbackPtr, ptr, byte_count );
510                                         MM_FreeTemp( (tVAddr)ptr );
511                                 }
512                                 
513                                 // Clean up info
514                                 free( info );
515                                 td->_info.ExtraInfo = NULL;
516                         }
517
518                         if( td->_info.bFreePointer )
519                                 MM_DerefPhys( td->BufferPointer );                      
520         
521                         // Clean up
522                         td->_info.bActive = 0;
523                         LOG("Cleaned %p", td);
524                 }
525         }
526 }
527
528 void UHCI_InterruptHandler(int IRQ, void *Ptr)
529 {
530         tUHCI_Controller *Host = Ptr;
531 //       int    frame = (_InWord(Host, FRNUM) - 1) & 0x3FF;
532         Uint16  status = _InWord(Host, USBSTS);
533         
534         // Interrupt-on-completion
535         if( status & 1 )
536         {
537                 // TODO: Support isochronous transfers (will need updating the frame pointer)
538                 Semaphore_Signal(&gUHCI_InterruptSempahore, 1);
539         }
540
541         LOG("status = 0x%04x", status);
542         _OutWord(Host, USBSTS, status);
543 }
544
545 void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value)
546 {
547         if( Host->MemIOMap )
548                 ((Uint8*)Host->MemIOMap)[Reg] = Value;
549         else
550                 outb(Host->IOBase + Reg, Value);
551 }
552
553 void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value)
554 {
555         if( Host->MemIOMap )
556                 Host->MemIOMap[Reg/2] = Value;
557         else
558                 outw(Host->IOBase + Reg, Value);
559 }
560
561 void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value)
562 {
563         if( Host->MemIOMap )
564                 ((Uint32*)Host->MemIOMap)[Reg/4] = Value;
565         else
566                 outd(Host->IOBase + Reg, Value);
567 }
568
569 Uint16 _InWord(tUHCI_Controller *Host, int Reg)
570 {
571         if( Host->MemIOMap )
572                 return Host->MemIOMap[Reg/2];
573         else
574                 return inw(Host->IOBase + Reg);
575 }
576

UCC git Repository :: git.ucc.asn.au