Support USB interrupt schedules on OHCI and UHCI.
authorKevin O'Connor <kevin@koconnor.net>
Sun, 14 Feb 2010 02:51:47 +0000 (21:51 -0500)
committerKevin O'Connor <kevin@koconnor.net>
Sun, 14 Feb 2010 02:51:47 +0000 (21:51 -0500)
The existing code always checks for USB "interrupt in" events every
millisecond.  Although that's okay, it consumes extra bandwidth.  This
change interrupt checks to be scheduled according to their requested
interval time.

src/clock.c
src/usb-ohci.c
src/usb-ohci.h
src/usb-uhci.c
src/usb-uhci.h
src/usb.c
src/usb.h
src/util.h

index e32bf1b717a51a77c2bfb918368f6f4d47d2e59d..5a30e3523351032b842cbe3f490f8c66c6e086b7 100644 (file)
@@ -54,8 +54,6 @@
  * TSC timer
  ****************************************************************/
 
-#define PIT_TICK_RATE 1193180   // Underlying HZ of PIT
-#define PIT_TICK_INTERVAL 65536 // Default interval for 18.2Hz timer
 #define TICKS_PER_DAY (u32)((u64)60*60*24*PIT_TICK_RATE / PIT_TICK_INTERVAL)
 #define CALIBRATE_COUNT 0x800   // Approx 1.7ms
 
index 6ad1dbc142ea6d7232b18e1b40c0e52d666dc0dd..2d33fa98c2c5149521bc5faae7e92655ad888188 100644 (file)
@@ -149,12 +149,18 @@ ohci_init(void *data)
 
     // Allocate memory
     struct ohci_hcca *hcca = memalign_high(256, sizeof(*hcca));
+    struct ohci_ed *intr_ed = malloc_high(sizeof(*intr_ed));
     struct ohci_ed *control_ed = malloc_high(sizeof(*control_ed));
-    if (!hcca || !control_ed) {
+    if (!hcca || !intr_ed || !control_ed) {
         dprintf(1, "No ram for ohci init\n");
-        return;
+        goto free;
     }
     memset(hcca, 0, sizeof(*hcca));
+    memset(intr_ed, 0, sizeof(*intr_ed));
+    intr_ed->hwINFO = ED_SKIP;
+    int i;
+    for (i=0; i<ARRAY_SIZE(hcca->int_table); i++)
+        hcca->int_table[i] = (u32)intr_ed;
     memset(control_ed, 0, sizeof(*control_ed));
     control_ed->hwINFO = ED_SKIP;
     cntl->ohci.control_ed = control_ed;
@@ -170,7 +176,9 @@ ohci_init(void *data)
 
 err:
     stop_ohci(cntl);
+free:
     free(hcca);
+    free(intr_ed);
     free(control_ed);
 }
 
@@ -245,18 +253,21 @@ struct ohci_pipe {
 };
 
 struct usb_pipe *
-ohci_alloc_intr_pipe(u32 endp, int period)
+ohci_alloc_intr_pipe(u32 endp, int frameexp)
 {
     if (! CONFIG_USB_OHCI)
         return NULL;
 
-    dprintf(7, "ohci_alloc_intr_pipe %x %d\n", endp, period);
+    dprintf(7, "ohci_alloc_intr_pipe %x %d\n", endp, frameexp);
+    if (frameexp > 5)
+        frameexp = 5;
     struct usb_s *cntl = endp2cntl(endp);
     int maxpacket = endp2maxsize(endp);
     int lowspeed = endp2speed(endp);
     int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7);
-    // XXX - just grab 20 for now.
-    int count = 20;
+    // Determine number of entries needed for 2 timer ticks.
+    int ms = 1<<frameexp;
+    int count = DIV_ROUND_UP(PIT_TICK_INTERVAL * 1000 * 2, PIT_TICK_RATE * ms);
     struct ohci_pipe *pipe = malloc_low(sizeof(*pipe));
     struct ohci_td *tds = malloc_low(sizeof(*tds) * count);
     void *data = malloc_low(maxpacket * count);
@@ -267,7 +278,6 @@ ohci_alloc_intr_pipe(u32 endp, int period)
     ed->hwHeadP = (u32)&tds[0];
     ed->hwTailP = (u32)&tds[count-1];
     ed->hwINFO = devaddr | (maxpacket << 16) | (lowspeed ? ED_LOWSPEED : 0);
-    ed->hwNextED = 0;
 
     int i;
     for (i=0; i<count-1; i++) {
@@ -277,11 +287,20 @@ ohci_alloc_intr_pipe(u32 endp, int period)
         tds[i].hwBE = tds[i].hwCBP + maxpacket - 1;
     }
 
-    // XXX - need schedule - just add to primary list for now.
+    // Add to interrupt schedule.
     barrier();
     struct ohci_hcca *hcca = (void*)cntl->ohci.regs->hcca;
-    for (i=0; i<ARRAY_SIZE(hcca->int_table); i++)
-        hcca->int_table[i] = (u32)ed;
+    if (frameexp == 0) {
+        // Add to existing interrupt entry.
+        struct ohci_ed *intr_ed = (void*)hcca->int_table[0];
+        ed->hwNextED = intr_ed->hwNextED;
+        intr_ed->hwNextED = (u32)ed;
+    } else {
+        int startpos = 1<<(frameexp-1);
+        ed->hwNextED = hcca->int_table[startpos];
+        for (i=startpos; i<ARRAY_SIZE(hcca->int_table); i+=ms)
+            hcca->int_table[i] = (u32)ed;
+    }
 
     pipe->data = data;
     pipe->count = count;
index 5a4f735a3a646e46994452181e5143ddfb27cd21..7ff84f6b22d732bf10d6e226e4d9b28afa092bff 100644 (file)
@@ -6,7 +6,7 @@ struct usb_s;
 void ohci_init(void *data);
 int ohci_control(u32 endp, int dir, const void *cmd, int cmdsize
                  , void *data, int datasize);
-struct usb_pipe *ohci_alloc_intr_pipe(u32 endp, int period);
+struct usb_pipe *ohci_alloc_intr_pipe(u32 endp, int frameexp);
 int ohci_poll_intr(struct usb_pipe *pipe, void *data);
 
 
index d98c08b0820b7ada26852613a968f26d8d99ae25..950ec6a3b116f5e649eae65449990249e4049029 100644 (file)
@@ -36,9 +36,15 @@ configure_uhci(struct usb_s *cntl)
     // Allocate ram for schedule storage
     struct uhci_td *term_td = malloc_high(sizeof(*term_td));
     struct uhci_framelist *fl = memalign_high(sizeof(*fl), sizeof(*fl));
-    struct uhci_qh *data_qh = malloc_low(sizeof(*data_qh));
+    struct uhci_qh *intr_qh = malloc_high(sizeof(*intr_qh));
+    struct uhci_qh *data_qh = malloc_high(sizeof(*data_qh));
     struct uhci_qh *term_qh = malloc_high(sizeof(*term_qh));
-    if (!term_td || !fl || !data_qh || !term_qh) {
+    if (!term_td || !fl || !intr_qh || !data_qh || !term_qh) {
+        free(term_td);
+        free(fl);
+        free(intr_qh);
+        free(data_qh);
+        free(term_qh);
         dprintf(1, "No ram for uhci init\n");
         return;
     }
@@ -58,11 +64,14 @@ configure_uhci(struct usb_s *cntl)
     data_qh->link = (u32)term_qh | UHCI_PTR_QH;
     cntl->uhci.qh = data_qh;
 
-    // Set schedule to point to primary queue head
+    // Set schedule to point to primary intr queue head
+    memset(intr_qh, 0, sizeof(*intr_qh));
+    intr_qh->element = UHCI_PTR_TERM;
+    intr_qh->link = (u32)data_qh | UHCI_PTR_QH;
     int i;
-    for (i=0; i<ARRAY_SIZE(fl->links); i++) {
-        fl->links[i] = (u32)data_qh | UHCI_PTR_QH;
-    }
+    for (i=0; i<ARRAY_SIZE(fl->links); i++)
+        fl->links[i] = (u32)intr_qh | UHCI_PTR_QH;
+    cntl->uhci.framelist = fl;
 
     // Set the frame length to the default: 1 ms exactly
     outb(USBSOF_DEFAULT, cntl->uhci.iobase + USBSOF);
@@ -229,26 +238,28 @@ uhci_control(u32 endp, int dir, const void *cmd, int cmdsize
 }
 
 struct usb_pipe *
-uhci_alloc_intr_pipe(u32 endp, int period)
+uhci_alloc_intr_pipe(u32 endp, int frameexp)
 {
     if (! CONFIG_USB_UHCI)
         return NULL;
 
-    dprintf(7, "uhci_alloc_intr_pipe %x %d\n", endp, period);
+    dprintf(7, "uhci_alloc_intr_pipe %x %d\n", endp, frameexp);
+    if (frameexp > 10)
+        frameexp = 10;
     struct usb_s *cntl = endp2cntl(endp);
     int maxpacket = endp2maxsize(endp);
     int lowspeed = endp2speed(endp);
     int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7);
-    // XXX - just grab 20 for now.
-    int count = 20;
+    // Determine number of entries needed for 2 timer ticks.
+    int ms = 1<<frameexp;
+    int count = DIV_ROUND_UP(PIT_TICK_INTERVAL * 1000 * 2, PIT_TICK_RATE * ms);
     struct uhci_qh *qh = malloc_low(sizeof(*qh));
     struct uhci_td *tds = malloc_low(sizeof(*tds) * count);
-    if (!qh || !tds)
-        return NULL;
-    if (maxpacket > sizeof(tds[0].data))
-        // XXX - free qh/tds
+    if (!qh || !tds || maxpacket > sizeof(tds[0].data)) {
+        free(qh);
+        free(tds);
         return NULL;
-
+    }
     qh->element = (u32)tds;
     int toggle = 0;
     int i;
@@ -266,10 +277,19 @@ uhci_alloc_intr_pipe(u32 endp, int period)
     qh->next_td = &tds[0];
     qh->pipe.endp = endp;
 
-    // XXX - need schedule - just add to primary list for now.
-    struct uhci_qh *data_qh = cntl->uhci.qh;
-    qh->link = data_qh->link;
-    data_qh->link = (u32)qh | UHCI_PTR_QH;
+    // Add to interrupt schedule.
+    struct uhci_framelist *fl = cntl->uhci.framelist;
+    if (frameexp == 0) {
+        // Add to existing interrupt entry.
+        struct uhci_qh *intr_qh = (void*)(fl->links[0] & ~UHCI_PTR_BITS);
+        qh->link = intr_qh->link;
+        intr_qh->link = (u32)qh | UHCI_PTR_QH;
+    } else {
+        int startpos = 1<<(frameexp-1);
+        qh->link = fl->links[startpos];
+        for (i=startpos; i<ARRAY_SIZE(fl->links); i+=ms)
+            fl->links[i] = (u32)qh | UHCI_PTR_QH;
+    }
 
     return &qh->pipe;
 }
index 5de7da0d262586a0d22527cdf88d0502364a3e0a..03ac9cd858df93381917d90b575e6f0ff0fbd817 100644 (file)
@@ -8,7 +8,7 @@ struct usb_s;
 void uhci_init(void *data);
 int uhci_control(u32 endp, int dir, const void *cmd, int cmdsize
                  , void *data, int datasize);
-struct usb_pipe *uhci_alloc_intr_pipe(u32 endp, int period);
+struct usb_pipe *uhci_alloc_intr_pipe(u32 endp, int frameexp);
 int uhci_poll_intr(struct usb_pipe *pipe, void *data);
 
 
index 9a61f126eb74f3626ba8354a5d9112d47b227519..3d59faf64a64b0a1390625ae3542c8d494ad7e2e 100644 (file)
--- a/src/usb.c
+++ b/src/usb.c
@@ -35,12 +35,16 @@ struct usb_pipe *
 alloc_intr_pipe(u32 endp, int period)
 {
     struct usb_s *cntl = endp2cntl(endp);
+    // Find the exponential period of the requested time.
+    if (period <= 0)
+        period = 1;
+    int frameexp = __fls(period);
     switch (cntl->type) {
     default:
     case USB_TYPE_UHCI:
-        return uhci_alloc_intr_pipe(endp, period);
+        return uhci_alloc_intr_pipe(endp, frameexp);
     case USB_TYPE_OHCI:
-        return ohci_alloc_intr_pipe(endp, period);
+        return ohci_alloc_intr_pipe(endp, frameexp);
     }
 }
 
index cc71c31a5de5a366c9dc23a245150b1105ffa094..8b9c5f1ecacba1b95d74ebae9e48d296ca7ce09c 100644 (file)
--- a/src/usb.h
+++ b/src/usb.h
@@ -11,7 +11,7 @@ struct usb_s {
     union {
         struct {
             u16 iobase;
-            void *qh;
+            void *qh, *framelist;
         } uhci;
         struct {
             struct ohci_regs *regs;
index 60a72599a1457c4e00613f18a6d6943b44bcae3d..429590ce928eb8aacdb262d7e802fd837db2f79a 100644 (file)
@@ -245,6 +245,8 @@ void serial_setup(void);
 void lpt_setup(void);
 
 // clock.c
+#define PIT_TICK_RATE 1193180   // Underlying HZ of PIT
+#define PIT_TICK_INTERVAL 65536 // Default interval for 18.2Hz timer
 static inline int check_time(u64 end) {
     return (s64)(rdtscll() - end) > 0;
 }