023d28cf42b0322d5b8fb8f1fa43d60a0a9bd80c
[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         #endif
194
195         Mutex_Release(&lock);
196 }
197
198 /**
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
203  */
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)
207 {
208         tUHCI_TD        *td;
209         tUHCI_ExtraTDInfo       *info = NULL;
210
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
214         }
215
216         td = UHCI_int_AllocateTD(Cont);
217
218         if( !td ) {
219                 // TODO: Wait for one to free?
220                 Log_Error("UHCI", "No avaliable TDs, transaction dropped");
221                 return NULL;
222         }
223
224         LOG("TD %p %i bytes, Type %x to 0x%x",
225                 td, Length, Type, Addr);
226
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;
233         td->Token |= Type;
234
235         if(
236                 ((tVAddr)Data & (PAGE_SIZE-1)) + Length > PAGE_SIZE
237         #if PHYS_BITS > 32
238                 || MM_GetPhysAddr( (tVAddr)Data ) >> 32
239         #endif
240                 )
241         {
242                 td->BufferPointer = MM_AllocPhysRange(1, 32);
243
244                 LOG("Allocated page %x", td->BufferPointer);            
245
246                 if( Type == 0x69 )      // IN token
247                 {
248                         LOG("Relocated IN");
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 );
253                 }
254                 else
255                 {
256                         LOG("Relocated OUT/SETUP");
257                         tVAddr  ptr = MM_MapTemp(td->BufferPointer);
258                         memcpy( (void*)ptr, Data, Length );
259                         MM_FreeTemp(ptr);
260                         td->Control |= (1 << 24);
261                 }
262                 td->_info.bFreePointer = 1;
263         }
264         else
265         {
266                 td->BufferPointer = MM_GetPhysAddr( (tVAddr)Data );
267                 td->_info.bFreePointer = 0;
268         }
269
270         // Interrupt on completion
271         if( Cb )
272         {
273                 if( !info )
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
277                 info->Callback = Cb;
278                 info->CallbackPtr = CbData;
279         }
280         
281         if( info ) {
282                 LOG("info = %p", info);
283                 td->Control |= (1 << 24);
284                 td->_info.ExtraInfo = info;
285         }
286
287         UHCI_int_AppendTD(Cont, td);
288
289         return td;
290 }
291
292 void *UHCI_DataIN(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
293 {
294         return UHCI_int_SendTransaction(Ptr, Dest, 0x69, DataTgl, Cb, CbData, Buf, Length);
295 }
296
297 void *UHCI_DataOUT(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
298 {
299         return UHCI_int_SendTransaction(Ptr, Dest, 0xE1, DataTgl, Cb, CbData, Buf, Length);
300 }
301
302 void *UHCI_SendSetup(void *Ptr, int Dest, int DataTgl, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
303 {
304         return UHCI_int_SendTransaction(Ptr, Dest, 0x2D, DataTgl, Cb, CbData, Buf, Length);
305 }
306
307 int UHCI_IsTransferComplete(void *Ptr, void *Handle)
308 {
309         tUHCI_TD        *td = Handle;
310         #if DEBUG
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);
315         #endif
316         if(td->Control & (1 << 23)) {
317                 return 0;
318         }
319 //      LOG("inactive, waiting for completion");
320         if(td->_info.bComplete)
321         {
322                 td->_info.bActive = 0;
323                 td->_info.bComplete = 0;
324                 td->Link = 0;
325                 return 1;
326         }
327         else
328         {
329                 return 0;
330         }
331 }
332
333 // === INTERNAL FUNCTIONS ===
334 /**
335  * \fn int UHCI_Int_InitHost(tUCHI_Controller *Host)
336  * \brief Initialises a UHCI host controller
337  * \param Host  Pointer - Host to initialise
338  */
339 int UHCI_Int_InitHost(tUHCI_Controller *Host)
340 {
341         ENTER("pHost", Host);
342
343         _OutWord( Host, USBCMD, 4 );    // GRESET
344         Time_Delay(10);
345         _OutWord( Host, USBCMD, 0 );    // GRESET
346         
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");
353                 LEAVE('i', -1);
354                 return -1;
355         }
356
357         // TODO: Handle QHs not being in a 32-bit paddr range
358         // Need another page, probably get some more TDs from it too
359
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;
367
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;
371         
372         // Set frame length to 1 ms
373         _OutByte( Host, SOFMOD, 64 );
374         
375         // Set Frame List
376         _OutDWord( Host, FLBASEADD, Host->PhysFrameList );
377         _OutWord( Host, FRNUM, 0 );
378         
379         // Enable Interrupts
380         _OutWord( Host, USBINTR, 0x000F );
381         PCI_ConfigWrite( Host->PciId, 0xC0, 2, 0x2000 );
382
383         // Enable processing
384         _OutWord( Host, USBCMD, 0x0001 );
385
386         LEAVE('i', 0);
387         return 0;
388 }
389
390 void UHCI_CheckPortUpdate(void *Ptr)
391 {
392         tUHCI_Controller        *Host = Ptr;
393         // Enable ports
394         for( int i = 0; i < 2; i ++ )
395         {
396                  int    port = PORTSC1 + i*2;
397                 Uint16  status;
398         
399                 status = _InWord(Host, port);
400                 // Check for port change
401                 if( !(status & 0x0002) )        continue;
402                 _OutWord(Host, port, 0x0002);
403                 
404                 // Check if the port is connected
405                 if( !(status & 1) )
406                 {
407                         // Tell the USB code it's gone.
408                         USB_DeviceDisconnected(Host->RootHub, i);
409                         continue;
410                 }
411                 else
412                 {
413                         LOG("Port %i has something", i);
414                         // Reset port (set bit 9)
415                         LOG("Reset");
416                         _OutWord(Host, port, 0x0200);
417                         Time_Delay(50); // 50ms delay
418                         _OutWord(Host, port, _InWord(Host, port) & ~0x0200);
419                         // Enable port
420                         LOG("Enable");
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);
425                 }
426         }
427 }
428
429 void UHCI_int_InterruptThread(void *Unused)
430 {
431         Threads_SetName("UHCI Interrupt Handler");
432         for( ;; )
433         {
434                 LOG("zzzzz....");
435                 // 0 = Take all
436                 Semaphore_Wait(&gUHCI_InterruptSempahore, 0);
437                 LOG("Huh?");
438         
439                 for( int i = 0; i < NUM_TDs; i ++ )
440                 {
441                          int    byte_count;
442                         tUHCI_ExtraTDInfo       *info;
443                         tUHCI_TD        *td;
444                         
445                         td = &gaUHCI_TDPool[i];
446                         info = td->_info.ExtraInfo;
447
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 ;
452
453                         // If no callback/alt buffer, mark as free and move on
454                         if( td->_info.ExtraInfo )
455                         {
456                                 // Get size of transfer
457                                 byte_count = (td->Control & 0x7FF)+1;
458                         
459                                 // Handle non page-aligned destination (with a > 32-bit paddr)
460                                 if(info->FirstPage)
461                                 {
462                                         char    *src, *dest;
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 )
468                                         {
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);
472                                         }
473                                         else
474                                         {
475                                                 // Multi-page
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);
483                                         }
484                                         MM_FreeTemp( (tVAddr)src );
485                                         MM_FreeTemp( (tVAddr)dest );
486                                 }
487         
488                                 // Don't mark as inactive, the check should do that
489                                 if( info->Callback == INVLPTR )
490                                 {
491                                         LOG("Marking %p as complete", td);
492                                         td->_info.bComplete = 1;
493                                         free( info );
494                                         td->_info.ExtraInfo = NULL;
495                                         if( td->_info.bFreePointer )
496                                                 MM_DerefPhys( td->BufferPointer );                      
497                                         continue ;
498                                 }
499
500                                 // Callback
501                                 if( info->Callback != NULL )
502                                 {
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 );
507                                 }
508                                 
509                                 // Clean up info
510                                 free( info );
511                                 td->_info.ExtraInfo = NULL;
512                         }
513
514                         if( td->_info.bFreePointer )
515                                 MM_DerefPhys( td->BufferPointer );                      
516         
517                         // Clean up
518                         td->_info.bActive = 0;
519                         LOG("Cleaned %p", td);
520                 }
521         }
522 }
523
524 void UHCI_InterruptHandler(int IRQ, void *Ptr)
525 {
526         tUHCI_Controller *Host = Ptr;
527 //       int    frame = (_InWord(Host, FRNUM) - 1) & 0x3FF;
528         Uint16  status = _InWord(Host, USBSTS);
529         
530         // Interrupt-on-completion
531         if( status & 1 )
532         {
533                 // TODO: Support isochronous transfers (will need updating the frame pointer)
534                 Semaphore_Signal(&gUHCI_InterruptSempahore, 1);
535         }
536
537         LOG("status = 0x%04x", status);
538         _OutWord(Host, USBSTS, status);
539 }
540
541 void _OutByte(tUHCI_Controller *Host, int Reg, Uint8 Value)
542 {
543         if( Host->MemIOMap )
544                 ((Uint8*)Host->MemIOMap)[Reg] = Value;
545         else
546                 outb(Host->IOBase + Reg, Value);
547 }
548
549 void _OutWord(tUHCI_Controller *Host, int Reg, Uint16 Value)
550 {
551         if( Host->MemIOMap )
552                 Host->MemIOMap[Reg/2] = Value;
553         else
554                 outw(Host->IOBase + Reg, Value);
555 }
556
557 void _OutDWord(tUHCI_Controller *Host, int Reg, Uint32 Value)
558 {
559         if( Host->MemIOMap )
560                 ((Uint32*)Host->MemIOMap)[Reg/4] = Value;
561         else
562                 outd(Host->IOBase + Reg, Value);
563 }
564
565 Uint16 _InWord(tUHCI_Controller *Host, int Reg)
566 {
567         if( Host->MemIOMap )
568                 return Host->MemIOMap[Reg/2];
569         else
570                 return inw(Host->IOBase + Reg);
571 }
572

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