Hi, I'm trying to have this host controller up and running on an ARM system (S3C2440). The driver in the vanilla kernel (checked up to 2.6.37) seems broken (I tried USB mass storage, an usbnet dongle and a composite HID keybord/mouse). I had a look and somehow managed to have the USB mass storage running (it transfered some hundreds of megabytes in both directions correctly). I'm attaching the patch. I'm really not satisfied with it because it just goes in an infinite loop if I attach a HID/usbnet device and cannot report failures in the lower level to the upper ones. With usbnet dongle I see too many URBs being queued to the device ( __oxu_urb_enqueue returns -ENOMEM at one point because there are to many transactions not being finished: I don't know yet if this is a problem with transactions not being correctly executed (and not being cleared for timeout either) or it's by design of the usbnet driver). I tried to throttle this number by testing the pending and pendingl number I introduces and returning -ENOMEM if they are filling the OXU210 on-chip memory. It somehow works for a while (I can even do pings). Unfortunately the CPU load is very high because usbnet is constantly submitting new URBs and after a while everything blows up. This second problem is similar to what I see with a HID device: interrupt transactions fail with a ENOSPC error. I really don't have any idea why this happens. Anyway I'm going to investigate the problem now that I have the data-sheet available. Any idea, advice or "Read That Fine Mailing List Post" is really appreciated, thanks! Just another question: I saw in this mailing list's archive that another driver "ehci-oxu210hp.c" is mentioned in many places. Should I have a look at this one or Rodolfo's (which lives in mainline) is the right one to work with? Thanks! -- Christian Pellegrin, see http://www.evolware.org/chri/ "Real Programmers don't play tennis, or any other sport which requires you to change clothes. Mountain climbing is OK, and Real Programmers wear their climbing boots to work in case a mountain should suddenly spring up in the middle of the computer room."
diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 50f57f4..c9719b2 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -2858,46 +2858,72 @@ static int __oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, } } -/* This function is responsible for breaking URBs with big data size +/* These functions are responsible for breaking URBs with big data size * into smaller size and processing small urbs in sequence. */ -static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - struct oxu_hcd *oxu = hcd_to_oxu(hcd); + +#define LOOP_START() \ + do { \ + unsigned long start = jiffies; \ + do { \ + while (0) + +#define LOOP_END(COND) \ + if (COND) schedule(); \ + if (jiffies > (start + HZ)) { \ + printk("%s %d %p/%p stuck %d %d %d\n", __FUNCTION__, __LINE__, urb, murb, COND, atomic_read(&oxu->pending), atomic_read(&oxu->pendingl)); \ + start = jiffies; \ + } \ + } while(COND); \ + } while(0) + +static void oxu_splitter(struct work_struct *w) +{ + struct oxu_splitter_work_s *ws = container_of(w, struct oxu_splitter_work_s, w); + struct urb *murb = NULL; + int i, ret; int num, rem; + struct urb *urb = ws->urb; + struct usb_hcd *hcd = ws->hcd; + struct oxu_hcd *oxu = hcd_to_oxu(hcd); int transfer_buffer_length; void *transfer_buffer; - struct urb *murb; - int i, ret; - - /* If not bulk pipe just enqueue the URB */ - if (!usb_pipebulk(urb->pipe)) - return __oxu_urb_enqueue(hcd, urb, mem_flags); + gfp_t mem_flags = ws->memflags; - /* Otherwise we should verify the USB transfer buffer size! */ transfer_buffer = urb->transfer_buffer; transfer_buffer_length = urb->transfer_buffer_length; + atomic_sub(1, &oxu->pending) ; + atomic_sub(transfer_buffer_length, &oxu->pendingl); + + /* If not bulk pipe just enqueue the URB */ + if (!usb_pipebulk(urb->pipe)) { + LOOP_START(); + ret = __oxu_urb_enqueue(hcd, urb, mem_flags); + LOOP_END(ret); + return; + } + num = urb->transfer_buffer_length / 4096; rem = urb->transfer_buffer_length % 4096; if (rem != 0) num++; /* If URB is smaller than 4096 bytes just enqueue it! */ - if (num == 1) - return __oxu_urb_enqueue(hcd, urb, mem_flags); - - /* Ok, we have more job to do! :) */ + if (num == 1) { + LOOP_START(); + ret = __oxu_urb_enqueue(hcd, urb, mem_flags); + LOOP_END(ret); + return; + } + /* Ok, we have more work to do! :) */ for (i = 0; i < num - 1; i++) { /* Get free micro URB poll till a free urb is recieved */ - do { - murb = (struct urb *) oxu_murb_alloc(oxu); - if (!murb) - schedule(); - } while (!murb); + LOOP_START(); + murb = (struct urb *) oxu_murb_alloc(oxu); + LOOP_END(!murb); /* Coping the urb */ memcpy(murb, urb, sizeof(struct urb)); @@ -2914,21 +2940,18 @@ static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, /* This loop is to guarantee urb to be processed when there's * not enough resources at a particular time by retrying. */ - do { - ret = __oxu_urb_enqueue(hcd, murb, mem_flags); - if (ret) - schedule(); - } while (ret); + LOOP_START(); + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + LOOP_END(ret); + murb = NULL; } /* Last urb requires special handling */ /* Get free micro URB poll till a free urb is recieved */ - do { - murb = (struct urb *) oxu_murb_alloc(oxu); - if (!murb) - schedule(); - } while (!murb); + LOOP_START(); + murb = (struct urb *) oxu_murb_alloc(oxu); + LOOP_END(!murb); /* Coping the urb */ memcpy(murb, urb, sizeof(struct urb)); @@ -2942,13 +2965,31 @@ static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, ((struct oxu_murb *) murb)->main = urb; ((struct oxu_murb *) murb)->last = 1; - do { - ret = __oxu_urb_enqueue(hcd, murb, mem_flags); - if (ret) - schedule(); - } while (ret); + LOOP_START(); + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + LOOP_END(ret); - return ret; + kfree(ws); +} + +static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct oxu_splitter_work_s *ws; + + ws = kmalloc(sizeof(*ws), GFP_ATOMIC); + if (!ws) + return -ENOMEM; + ws->urb = urb; + ws->hcd = hcd; + ws->memflags = mem_flags; + atomic_add(1, &oxu->pending); + atomic_add(urb->transfer_buffer_length, &oxu->pendingl); + INIT_WORK(&ws->w, oxu_splitter); + queue_work(oxu->splitter_wq, &ws->w); + + return 0; } /* Remove from hardware lists. @@ -3751,6 +3792,9 @@ static struct usb_hcd *oxu_create(struct platform_device *pdev, oxu = hcd_to_oxu(hcd); oxu->is_otg = otg; + oxu->splitter_wq = create_workqueue(hcd->product_desc); + atomic_set(&oxu->pending, 0); + atomic_set(&oxu->pendingl, 0); ret = usb_add_hcd(hcd, irq, IRQF_SHARED); if (ret < 0) @@ -3898,6 +3942,11 @@ error_ioremap: static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd) { + struct oxu_hcd *oxu; + + oxu = hcd_to_oxu(hcd); + flush_workqueue(oxu->splitter_wq); + destroy_workqueue(oxu->splitter_wq); usb_remove_hcd(hcd); usb_put_hcd(hcd); } diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h index 1c216ad..8d45b7e 100644 --- a/drivers/usb/host/oxu210hp.h +++ b/drivers/usb/host/oxu210hp.h @@ -369,6 +369,15 @@ struct oxu_murb { u8 last; }; +struct oxu_splitter_work_s { + struct work_struct w; + struct urb *urb; + struct usb_hcd *hcd; + gfp_t memflags; +}; + +static void oxu_splitter(struct work_struct *w); + struct oxu_hcd { /* one per controller */ unsigned int is_otg:1; @@ -428,6 +437,9 @@ struct oxu_hcd { /* one per controller */ */ struct oxu_murb *murb_pool; /* murb per split big urb */ unsigned urb_len; + struct workqueue_struct *splitter_wq; /* split urbs in murbs */ + atomic_t pending; + atomic_t pendingl; u8 sbrn; /* packed release number */ };