1f73cf6f070274466c27a53401ed6536724bccc9
[tpg/acess2.git] / KernelLand / Modules / USB / EHCI / ehci.c
1 /*
2  * Acess2 EHCI Driver
3  * - By John Hodge (thePowersGang)
4  * 
5  * ehci.c
6  * - Driver Core
7  */
8 #define DEBUG   1
9 #define VERSION VER2(0,1)
10 #include <acess.h>
11 #include <modules.h>
12 #include <usb_host.h>
13 #include "ehci.h"
14 #include <drv_pci.h>
15 #include <limits.h>
16 #include <events.h>
17 #include <timers.h>
18
19 // === CONSTANTS ===
20 #define EHCI_MAX_CONTROLLERS    4
21 #define EHCI_THREADEVENT_IOC    THREAD_EVENT_USER1
22 #define EHCI_THREADEVENT_PORTSC THREAD_EVENT_USER2
23
24 // === PROTOTYPES ===
25  int    EHCI_Initialise(char **Arguments);
26  int    EHCI_Cleanup(void);
27  int    EHCI_InitController(tPAddr BaseAddress, Uint8 InterruptNum);
28 void    EHCI_InterruptHandler(int IRQ, void *Ptr);
29 // -- API ---
30 void    *EHCI_InitInterrupt(void *Ptr, int Endpoint, int bInput, int Period, tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
31 void    *EHCI_InitIsoch  (void *Ptr, int Endpoint, size_t MaxPacketSize);
32 void    *EHCI_InitControl(void *Ptr, int Endpoint, size_t MaxPacketSize);
33 void    *EHCI_InitBulk   (void *Ptr, int Endpoint, size_t MaxPacketSize);
34 void    EHCI_RemEndpoint(void *Ptr, void *Handle);
35 void    *EHCI_SendControl(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData,
36         int isOutbound,
37         const void *SetupData, size_t SetupLength,
38         const void *OutData, size_t OutLength,
39         void *InData, size_t InLength
40         );
41 void    *EHCI_SendBulk(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length);
42 void    EHCI_FreeOp(void *Ptr, void *Handle);
43 Uint32  EHCI_int_RootHub_FeatToMask(int Feat);
44 void    EHCI_RootHub_SetPortFeature(void *Ptr, int Port, int Feat);
45 void    EHCI_RootHub_ClearPortFeature(void *Ptr, int Port, int Feat);
46  int    EHCI_RootHub_GetPortStatus(void *Ptr, int Port, int Flag);
47 // --- Internals ---
48 tEHCI_qTD       *EHCI_int_AllocateTD(tEHCI_Controller *Cont, int PID, void *Data, size_t Length, tUSBHostCb Cb, void *CbData);
49 void    EHCI_int_DeallocateTD(tEHCI_Controller *Cont, tEHCI_qTD *TD);
50 void    EHCI_int_AppendTD(tEHCI_QH *QH, tEHCI_qTD *TD);
51 tEHCI_QH        *EHCI_int_AllocateQH(tEHCI_Controller *Cont, int Endpoint, size_t MaxPacketSize);
52 void    EHCI_int_DeallocateQH(tEHCI_Controller *Cont, tEHCI_QH *QH);
53 void    EHCI_int_InterruptThread(void *ControllerPtr);
54
55 // === GLOBALS ===
56 MODULE_DEFINE(0, VERSION, USB_EHCI, EHCI_Initialise, NULL, "USB_Core", NULL);
57 tEHCI_Controller        gaEHCI_Controllers[EHCI_MAX_CONTROLLERS];
58 tUSBHostDef     gEHCI_HostDef = {
59         .InitInterrupt = EHCI_InitInterrupt,
60         .InitIsoch     = EHCI_InitIsoch,
61         .InitControl   = EHCI_InitControl,
62         .InitBulk      = EHCI_InitBulk,
63         .RemEndpoint   = EHCI_RemEndpoint,
64         .SendIsoch   = NULL,
65         .SendControl = EHCI_SendControl,
66         .SendBulk    = EHCI_SendBulk,
67         .FreeOp      = EHCI_FreeOp,
68         
69         .CheckPorts = NULL,     // No need
70         .SetPortFeature   = EHCI_RootHub_SetPortFeature,
71         .ClearPortFeature = EHCI_RootHub_ClearPortFeature,
72         .GetPortStatus    = EHCI_RootHub_GetPortStatus,
73         };
74
75 // === CODE ===
76 int EHCI_Initialise(char **Arguments)
77 {
78         for( int id = -1; (id = PCI_GetDeviceByClass(0x0C0320, 0xFFFFFF, id)) >= 0;  )
79         {
80                 Uint32  addr = PCI_GetBAR(id, 0);
81                 if( addr == 0 ) {
82                         // Oops, PCI BIOS emulation time
83                 }
84                 Uint8   irq = PCI_GetIRQ(id);
85                 if( irq == 0 ) {
86                         // TODO: The same
87                 }
88
89                 Log_Log("ECHI", "Controller at PCI %i 0x%x IRQ 0x%x",
90                         id, addr, irq);
91
92                 if( EHCI_InitController(addr, irq) )
93                 {
94                         // TODO: Detect other forms of failure than "out of slots"
95                         break ;
96                 }
97
98                 // TODO: Register with the USB stack
99         }
100         return 0;
101 }
102
103 int EHCI_Cleanup(void)
104 {
105         return 0;
106 }
107
108 // --- Driver Init ---
109 int EHCI_InitController(tPAddr BaseAddress, Uint8 InterruptNum)
110 {
111         tEHCI_Controller        *cont = NULL;
112
113         for( int i = 0; i < EHCI_MAX_CONTROLLERS; i ++ )
114         {
115                 if( gaEHCI_Controllers[i].PhysBase == 0 ) {
116                         cont = &gaEHCI_Controllers[i];
117                         cont->PhysBase = BaseAddress;
118                         break;
119                 }
120         }
121         if(!cont) {
122                 Log_Notice("EHCI", "Too many controllers (EHCI_MAX_CONTROLLERS=%i)",
123                         EHCI_MAX_CONTROLLERS);
124                 return 1;
125         }
126
127         // - Nuke a couple of fields so error handling code doesn't derp
128         cont->CapRegs = NULL;
129         cont->PeriodicQueue = NULL;
130
131         // -- Build up structure --
132         cont->CapRegs = (void*)MM_MapHWPages(BaseAddress, 1);
133         if( !cont->CapRegs ) {
134                 Log_Warning("EHCI", "Can't map 1 page at %P into kernel space", BaseAddress);
135                 goto _error;
136         }
137         // TODO: Error check
138         if( (cont->CapRegs->CapLength & 3) ) {
139                 Log_Warning("EHCI", "Controller at %P non-aligned op regs", BaseAddress);
140                 goto _error;
141         }
142         cont->OpRegs = (void*)( (Uint32*)cont->CapRegs + cont->CapRegs->CapLength / 4 );
143         // - Allocate periodic queue
144         tPAddr  unused;
145         cont->PeriodicQueue = (void*)MM_AllocDMA(1, 32, &unused);
146         if( !cont->PeriodicQueue ) {
147                 Log_Warning("ECHI", "Can't allocate 1 32-bit page for periodic queue");
148                 goto _error;
149         }
150         // TODO: Error check
151         //  > Populate queue
152
153         // Get port count
154         cont->nPorts = cont->CapRegs->HCSParams & 0xF;
155
156
157         // -- Bind IRQ --
158         IRQ_AddHandler(InterruptNum, EHCI_InterruptHandler, cont);
159         cont->InterruptThread = Proc_SpawnWorker(EHCI_int_InterruptThread, cont);
160         if( !cont->InterruptThread ) {
161                 Log_Warning("EHCI", "Can't spawn interrupt worker thread");
162                 goto _error;
163         }
164         LOG("cont->InterruptThread = %p", cont->InterruptThread);
165
166         // -- Initialisation procedure (from ehci-r10) --
167         // - Reset controller
168         cont->OpRegs->USBCmd = USBCMD_HCReset;
169         // - Set CTRLDSSEGMENT (TODO: 64-bit support)
170         // - Set USBINTR
171         cont->OpRegs->USBIntr = USBINTR_IOC|USBINTR_PortChange|USBINTR_FrameRollover;
172         // - Set PERIODICLIST BASE
173         cont->OpRegs->PeridocListBase = MM_GetPhysAddr( cont->PeriodicQueue );
174         // - Enable controller
175         cont->OpRegs->USBCmd = (0x40 << 16) | USBCMD_PeriodicEnable | USBCMD_Run;
176         // - Route all ports
177         cont->OpRegs->ConfigFlag = 1;
178
179         // -- Register with USB Core --
180         cont->RootHub = USB_RegisterHost(&gEHCI_HostDef, cont, cont->nPorts);
181
182         return 0;
183 _error:
184         cont->PhysBase = 0;
185         if( cont->CapRegs )
186                 MM_Deallocate( (tVAddr)cont->CapRegs );
187         if( cont->PeriodicQueue )
188                 MM_Deallocate( (tVAddr)cont->PeriodicQueue );
189         return 2;
190 }
191
192 void EHCI_InterruptHandler(int IRQ, void *Ptr)
193 {
194         tEHCI_Controller *Cont = Ptr;
195         Uint32  sts = Cont->OpRegs->USBSts;
196         
197         // Clear interrupts
198         Cont->OpRegs->USBSts = sts;     
199
200         if( sts & 0xFFFF0FC0 ) {
201                 LOG("Oops, reserved bits set (%08x), funny hardware?", sts);
202                 sts &= ~0xFFFF0FFC0;
203         }
204
205         // Unmask read-only bits
206         sts &= ~(0xF000);
207
208         if( sts & USBINTR_IOC ) {
209                 // IOC
210                 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IOC);
211                 sts &= ~USBINTR_IOC;
212         }
213
214         if( sts & USBINTR_PortChange ) {
215                 // Port change, determine what port and poke helper thread
216                 LOG("Port status change");
217                 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_PORTSC);
218                 sts &= ~USBINTR_PortChange;
219         }
220         
221         if( sts & USBINTR_FrameRollover ) {
222                 // Frame rollover, used to aid timing (trigger per-second operations)
223                 LOG("Frame rollover");
224                 sts &= ~USBINTR_FrameRollover;
225         }
226
227         if( sts ) {
228                 // Unhandled interupt bits
229                 // TODO: Warn
230                 LOG("WARN - Bitmask %x unhandled", sts);
231         }
232
233
234 }
235
236 // --------------------------------------------------------------------
237 // USB API
238 // --------------------------------------------------------------------
239 void *EHCI_InitInterrupt(void *Ptr, int Endpoint, int bOutbound, int Period,
240         tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
241 {
242         tEHCI_Controller        *Cont = Ptr;
243          int    pow2period, period_pow;
244         
245         if( Endpoint >= 256*16 )
246                 return NULL;
247         if( Period <= 0 )
248                 return NULL;
249         if( Period > 256 )
250                 Period = 256;
251
252         // Round the period to the closest power of two
253         pow2period = 1;
254         period_pow = 0;
255         // - Find the first power above the period
256         while( pow2period < Period )
257         {
258                 pow2period *= 2;
259                 period_pow ++;
260         }
261         // - Check which is closest
262         if( Period - pow2period / 2 > pow2period - Period )
263                 Period = pow2period;
264         else {
265                 Period = pow2period/2;
266                 period_pow --;
267         }
268         
269         // Allocate a QH
270         tEHCI_QH *qh = EHCI_int_AllocateQH(Cont, Endpoint, Length);
271         qh->Impl.IntPeriodPow = period_pow;
272
273         Mutex_Acquire(&Cont->PeriodicListLock);
274
275         // Choose an interrupt slot to use      
276         int minslot = 0, minslot_load = INT_MAX;
277         for( int slot = 0; slot < Period; slot ++ )
278         {
279                  int    load = 0;
280                 for( int i = 0; i < PERIODIC_SIZE; i += Period )
281                         load += Cont->InterruptLoad[i+slot];
282                 if( load == 0 ) break;
283                 if( load < minslot_load ) {
284                         minslot = slot;
285                         minslot_load = load;
286                 }
287         }
288         // Increase loading on the selected slot
289         for( int i = minslot; i < PERIODIC_SIZE; i += Period )
290                 Cont->InterruptLoad[i] += Length;
291         qh->Impl.IntOfs = minslot;
292
293         // Allocate TD for the data
294         tEHCI_qTD *td = EHCI_int_AllocateTD(Cont, (bOutbound ? PID_OUT : PID_IN), Buf, Length, Cb, CbData);
295         EHCI_int_AppendTD(qh, td);
296
297         // Insert into the periodic list
298         for( int i = 0; i < PERIODIC_SIZE; i += Period )
299         {
300                 // Walk list until
301                 // - the end is reached
302                 // - this QH is found
303                 // - A QH with a lower period is encountered
304                 tEHCI_QH        *pqh = NULL;
305                 tEHCI_QH        *nqh;
306                 for( nqh = Cont->PeriodicQueueV[i]; nqh; pqh = nqh, nqh = nqh->Impl.Next )
307                 {
308                         if( nqh == qh )
309                                 break;
310                         if( nqh->Impl.IntPeriodPow < period_pow )
311                                 break;
312                 }
313
314                 // Somehow, we've already been added to this queue.
315                 if( nqh && nqh == qh )
316                         continue ;
317
318                 if( qh->Impl.Next && qh->Impl.Next != nqh ) {
319                         Log_Warning("EHCI", "Suspected bookkeeping error on %p - int list %i+%i overlap",
320                                 Cont, period_pow, minslot);
321                         break;
322                 }
323
324                 if( nqh ) {
325                         qh->Impl.Next = nqh;
326                         qh->HLink = MM_GetPhysAddr(nqh) | 2;
327                 }
328                 else {
329                         qh->Impl.Next = NULL;
330                         qh->HLink = 2|1;        // QH, Terminate
331                 }
332
333                 if( pqh ) {
334                         pqh->Impl.Next = qh;
335                         pqh->HLink = MM_GetPhysAddr(qh) | 2;
336                 }
337                 else {
338                         Cont->PeriodicQueueV[i] = qh;
339                         Cont->PeriodicQueue[i] = MM_GetPhysAddr(qh) | 2;
340                 }
341         }
342         Mutex_Release(&Cont->PeriodicListLock);
343
344         return qh;
345 }
346
347 void *EHCI_InitIsoch(void *Ptr, int Endpoint, size_t MaxPacketSize)
348 {
349         return (void*)(tVAddr)(Endpoint + 1);
350 }
351 void *EHCI_InitControl(void *Ptr, int Endpoint, size_t MaxPacketSize)
352 {
353         tEHCI_Controller *Cont = Ptr;
354         
355         // Allocate a QH
356         tEHCI_QH *qh = EHCI_int_AllocateQH(Cont, Endpoint, MaxPacketSize);
357
358         // Append to async list 
359         if( Cont->LastAsyncHead ) {
360                 Cont->LastAsyncHead->HLink = MM_GetPhysAddr(qh)|2;
361                 Cont->LastAsyncHead->Impl.Next = qh;
362         }
363         else
364                 Cont->OpRegs->AsyncListAddr = MM_GetPhysAddr(qh)|2;
365         Cont->LastAsyncHead = qh;
366
367         LOG("Created %p for %p Ep 0x%x - %i bytes MPS", qh, Ptr, Endpoint, MaxPacketSize);
368
369         return qh;
370 }
371 void *EHCI_InitBulk(void *Ptr, int Endpoint, size_t MaxPacketSize)
372 {
373         return EHCI_InitControl(Ptr, Endpoint, MaxPacketSize);
374 }
375 void EHCI_RemEndpoint(void *Ptr, void *Handle)
376 {
377         if( Handle == NULL )
378                 return ;
379         else if( (tVAddr)Handle <= 256*16 )
380                 return ;        // Isoch
381         else {
382                 tEHCI_QH        *qh = Ptr;
383
384                 // Remove QH from list
385                 // - If it's a polling endpoint, need to remove from all periodic lists
386                 if( qh->Impl.IntPeriodPow != 0xFF) {
387                         // Poll
388                 }
389                 else {
390                         // GP
391                 }
392                 
393                 // Deallocate QH
394                 EHCI_int_DeallocateQH(Ptr, Handle);
395         }
396 }
397
398 void *EHCI_SendControl(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData,
399         int isOutbound,
400         const void *SetupData, size_t SetupLength,
401         const void *OutData, size_t OutLength,
402         void *InData, size_t InLength
403         )
404 {
405         tEHCI_Controller *Cont = Ptr;
406         tEHCI_qTD       *td_setup, *td_data, *td_status;
407
408         // Sanity checks
409         if( (tVAddr)Dest <= 256*16 )
410                 return NULL;
411
412         // Check size of SETUP and status
413         
414         // Allocate TDs
415         td_setup = EHCI_int_AllocateTD(Cont, PID_SETUP, (void*)SetupData, SetupLength, NULL, NULL);
416         if( isOutbound )
417         {
418                 td_data = OutData ? EHCI_int_AllocateTD(Cont, PID_OUT, (void*)OutData, OutLength, NULL, NULL) : NULL;
419                 td_status = EHCI_int_AllocateTD(Cont, PID_IN, InData, InLength, Cb, CbData);
420         }
421         else
422         {
423                 td_data = InData ? EHCI_int_AllocateTD(Cont, PID_IN, InData, InLength, NULL, NULL) : NULL;
424                 td_status = EHCI_int_AllocateTD(Cont, PID_OUT, (void*)OutData, OutLength, Cb, CbData);
425         }
426
427         // Append TDs
428         EHCI_int_AppendTD(Dest, td_setup);
429         if( td_data )
430                 EHCI_int_AppendTD(Dest, td_data);
431         EHCI_int_AppendTD(Dest, td_status);
432
433         return td_status;
434 }
435
436 void *EHCI_SendBulk(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length)
437 {
438         tEHCI_Controller        *Cont = Ptr;
439         
440         // Sanity check the pointer
441         // - Can't be NULL or an isoch
442         if( (tVAddr)Dest <= 256*16 )
443                 return NULL;
444         
445         // Allocate single TD
446         tEHCI_qTD       *td = EHCI_int_AllocateTD(Cont, (Dir ? PID_OUT : PID_IN), Data, Length, Cb, CbData);
447         EHCI_int_AppendTD(Dest, td);    
448
449         return td;
450 }
451
452 void EHCI_FreeOp(void *Ptr, void *Handle)
453 {
454         tEHCI_Controller        *Cont = Ptr;
455
456         EHCI_int_DeallocateTD(Cont, Handle);
457 }
458
459 Uint32 EHCI_int_RootHub_FeatToMask(int Feat)
460 {
461         switch(Feat)
462         {
463         case PORT_RESET:        return PORTSC_PortReset;
464         case PORT_ENABLE:       return PORTSC_PortEnabled;
465         default:
466                 Log_Warning("EHCI", "Unknown root hub port feature %i", Feat);
467                 return 0;
468         }
469 }
470
471 void EHCI_RootHub_SetPortFeature(void *Ptr, int Port, int Feat)
472 {
473         tEHCI_Controller        *Cont = Ptr;
474         if(Port >= Cont->nPorts)        return;
475
476         Cont->OpRegs->PortSC[Port] |= EHCI_int_RootHub_FeatToMask(Feat);
477 }
478
479 void EHCI_RootHub_ClearPortFeature(void *Ptr, int Port, int Feat)
480 {
481         tEHCI_Controller        *Cont = Ptr;
482         if(Port >= Cont->nPorts)        return;
483
484         Cont->OpRegs->PortSC[Port] &= ~EHCI_int_RootHub_FeatToMask(Feat);
485 }
486
487 int EHCI_RootHub_GetPortStatus(void *Ptr, int Port, int Flag)
488 {
489         tEHCI_Controller        *Cont = Ptr;
490         if(Port >= Cont->nPorts)        return 0;
491
492         return !!(Cont->OpRegs->PortSC[Port] & EHCI_int_RootHub_FeatToMask(Flag));
493 }
494
495 // --------------------------------------------------------------------
496 // Internals
497 // --------------------------------------------------------------------
498 tEHCI_qTD *EHCI_int_AllocateTD(tEHCI_Controller *Cont, int PID, void *Data, size_t Length, tUSBHostCb Cb, void *CbData)
499 {
500         UNIMPLEMENTED();
501         return NULL;
502 }
503
504 void EHCI_int_DeallocateTD(tEHCI_Controller *Cont, tEHCI_qTD *TD)
505 {
506         UNIMPLEMENTED();
507 }
508
509 void EHCI_int_AppendTD(tEHCI_QH *QH, tEHCI_qTD *TD)
510 {
511         UNIMPLEMENTED();
512 }
513
514 tEHCI_QH *EHCI_int_AllocateQH(tEHCI_Controller *Cont, int Endpoint, size_t MaxPacketSize)
515 {
516         UNIMPLEMENTED();
517         return NULL;
518 }
519
520 void EHCI_int_DeallocateQH(tEHCI_Controller *Cont, tEHCI_QH *QH)
521 {
522         UNIMPLEMENTED();
523 }
524
525 void EHCI_int_HandlePortConnectChange(tEHCI_Controller *Cont, int Port)
526 {
527         // Connect Event
528         if( Cont->OpRegs->PortSC[Port] & PORTSC_CurrentConnectStatus )
529         {
530                 // Is the device low-speed?
531                 if( (Cont->OpRegs->PortSC[Port] & PORTSC_LineStatus_MASK) == PORTSC_LineStatus_Kstate )
532                 {
533                         LOG("Low speed device on %p Port %i, giving to companion", Cont, Port);
534                         Cont->OpRegs->PortSC[Port] |= PORTSC_PortOwner;
535                 }
536                 // not a low-speed device, EHCI reset
537                 else
538                 {
539                         LOG("Device connected on %p #%i", Cont, Port);
540                         // Reset procedure.
541                         USB_PortCtl_BeginReset(Cont->RootHub, Port);
542                 }
543         }
544         // Disconnect Event
545         else
546         {
547                 if( Cont->OpRegs->PortSC[Port] & PORTSC_PortOwner ) {
548                         LOG("Companion port %i disconnected, taking it back", Port);
549                         Cont->OpRegs->PortSC[Port] &= ~PORTSC_PortOwner;
550                 }
551                 else {
552                         LOG("Port %i disconnected", Port);
553                         USB_DeviceDisconnected(Cont->RootHub, Port);
554                 }
555         }
556 }
557
558 void EHCI_int_InterruptThread(void *ControllerPtr)
559 {
560         tEHCI_Controller        *Cont = ControllerPtr;
561         while(Cont->OpRegs)
562         {
563                 Uint32  events;
564                 
565                 LOG("sleeping for events");
566                 events = Threads_WaitEvents(EHCI_THREADEVENT_IOC|EHCI_THREADEVENT_PORTSC);
567                 if( !events ) {
568                         // TODO: Should this cause a termination?
569                 }
570                 LOG("events = 0x%x", events);
571
572                 if( events & EHCI_THREADEVENT_IOC )
573                 {
574                         // IOC, Do whatever it is you do
575                 }
576
577                 // Port status change interrupt
578                 if( events & EHCI_THREADEVENT_PORTSC )
579                 {
580                         // Check for port status changes
581                         for(int i = 0; i < Cont->nPorts; i ++ )
582                         {
583                                 Uint32  sts = Cont->OpRegs->PortSC[i];
584                                 LOG("Port %i: sts = %x", i, sts);
585                                 Cont->OpRegs->PortSC[i] = sts;
586                                 if( sts & PORTSC_ConnectStatusChange )
587                                         EHCI_int_HandlePortConnectChange(Cont, i);
588
589                                 if( sts & PORTSC_PortEnableChange )
590                                 {
591                                         // Handle enable/disable
592                                 }
593
594                                 if( sts & PORTSC_OvercurrentChange )
595                                 {
596                                         // Handle over-current change
597                                 }
598                         }
599                 }
600
601                 LOG("Going back to sleep");
602         }
603 }

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