Hello,
I'd like to post the incomplete patch I've written so far and see if
anybody has anything to say about it, and perhaps see if anyone can help
me with a couple little problems I'm having trouble with.
It's taken me a while to come back to this because I've been busy trying
to make sure that the code I've written so far isn't a complete mess.
I decided to post it in this thread since it already discusses the
problem - but for anyone who hasn't read the prior posts, here is a
summary:
On some OHCI controllers, the usbhid driver needs a second input URB to
prevent data being lost when one URB is being read on a frame boundary
and incoming interrupt data is learned about on the next frame. I'm
completely new to programming anything for the kernel and I'm trying to
write a patch to fix this.
Here's some thoughts about what I've coded so far:
It occurs to me that this only happens on "some" controllers. So, I
thought it'd be nice if I could find a way to test for the necessity of
a second (or maybe even a third? who knows) input URB instead of just
giving every usbhid device two of them.
wrt that:
1. Am I on the right track thinking like that?
2. Does the USB stuff in the kernel already have a way to test this?
3. If not, I'm really stumped about how I can do this, so any advice
would be great. :p
Aside from that - in hid_start_in(), would it have been better if I'd
used goto and a label instead of breaking from the for loop? I wouldn't
have to test if (rc == 0) to clear that HID_NO_BANDWIDTH bit that way,
but it seemed more messy.
Please let me know if I've made even the slightest mistake so I can
learn from this!
Regards,
Josef
--
diff -uprN -X linux-3.10-vanilla/Documentation/dontdiff
linux-3.10-vanilla/drivers/hid/usbhid/hid-core.c
linux-3.10-dev/drivers/hid/usbhid/hid-core.c
--- linux-3.10-vanilla/drivers/hid/usbhid/hid-core.c 2013-06-30
17:13:29.000000000 -0500
+++ linux-3.10-dev/drivers/hid/usbhid/hid-core.c 2013-07-22
16:00:24.785950521 -0500
@@ -80,20 +80,26 @@ static int hid_start_in(struct hid_devic
unsigned long flags;
int rc = 0;
struct usbhid_device *usbhid = hid->driver_data;
+ int i;
spin_lock_irqsave(&usbhid->lock, flags);
if (hid->open > 0 &&
!test_bit(HID_DISCONNECTED, &usbhid->iofl) &&
!test_bit(HID_SUSPENDED, &usbhid->iofl) &&
!test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) {
- rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC);
- if (rc != 0) {
- clear_bit(HID_IN_RUNNING, &usbhid->iofl);
- if (rc == -ENOSPC)
- set_bit(HID_NO_BANDWIDTH, &usbhid->iofl);
- } else {
- clear_bit(HID_NO_BANDWIDTH, &usbhid->iofl);
+ for (i = 0; i < usbhid->n_inurbs; i++) {
+ rc = usb_submit_urb(usbhid->inurbs[i], GFP_ATOMIC);
+ if (rc != 0) {
+ clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+ if (rc == -ENOSPC)
+ set_bit(HID_NO_BANDWIDTH,
&usbhid->iofl);
+
+ break;
+ }
}
+
+ if (rc == 0)
+ clear_bit(HID_NO_BANDWIDTH, &usbhid->iofl);
}
spin_unlock_irqrestore(&usbhid->lock, flags);
return rc;
@@ -120,7 +126,7 @@ static void hid_reset(struct work_struct
if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) {
dev_dbg(&usbhid->intf->dev, "clear halt\n");
- rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe);
+ rc = usb_clear_halt(hid_to_usb_dev(hid),
usbhid->inurbs[0]->pipe);
clear_bit(HID_CLEAR_HALT, &usbhid->iofl);
hid_start_in(hid);
}
@@ -780,6 +786,7 @@ done:
void usbhid_close(struct hid_device *hid)
{
struct usbhid_device *usbhid = hid->driver_data;
+ int i;
mutex_lock(&hid_open_mut);
@@ -791,7 +798,10 @@ void usbhid_close(struct hid_device *hid
if (!--hid->open) {
spin_unlock_irq(&usbhid->lock);
hid_cancel_delayed_stuff(usbhid);
- usb_kill_urb(usbhid->urbin);
+
+ for (i = 0; i < usbhid->n_inurbs; i++)
+ usb_kill_urb(usbhid->inurbs[i]);
+
usbhid->intf->needs_remote_wakeup = 0;
} else {
spin_unlock_irq(&usbhid->lock);
@@ -1087,6 +1097,7 @@ static int usbhid_start(struct hid_devic
struct usb_device *dev = interface_to_usbdev(intf);
struct usbhid_device *usbhid = hid->driver_data;
unsigned int n, insize = 0;
+ int i;
int ret;
clear_bit(HID_DISCONNECTED, &usbhid->iofl);
@@ -1133,16 +1144,33 @@ static int usbhid_start(struct hid_devic
interval = hid_mousepoll_interval;
ret = -ENOMEM;
+
+ /*
+ * Some controllers need more than one input URB to prevent
data from being lost
+ * while processing a prior completed URB.
+ */
+ usbhid->n_inurbs = 2;
+
+ if (usbhid->inurbs)
+ continue;
+
+ usbhid->inurbs = kmalloc(usbhid->n_inurbs * sizeof(struct urb
*), GFP_KERNEL);
+ if (!(usbhid->inurbs)) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
if (usb_endpoint_dir_in(endpoint)) {
- if (usbhid->urbin)
- continue;
- if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))
- goto fail;
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
- usb_fill_int_urb(usbhid->urbin, dev, pipe,
usbhid->inbuf, insize,
- hid_irq_in, hid, interval);
- usbhid->urbin->transfer_dma = usbhid->inbuf_dma;
- usbhid->urbin->transfer_flags |=
URB_NO_TRANSFER_DMA_MAP;
+
+ for (i = 0; i < usbhid->n_inurbs; i++) {
+ if (!(usbhid->inurbs[i] = usb_alloc_urb(0,
GFP_KERNEL)))
+ goto fail;
+ usb_fill_int_urb(usbhid->inurbs[i], dev, pipe,
usbhid->inbuf, insize,
+ hid_irq_in, hid, interval);
+ usbhid->inurbs[i]->transfer_dma =
usbhid->inbuf_dma;
+ usbhid->inurbs[i]->transfer_flags |=
URB_NO_TRANSFER_DMA_MAP;
+ }
} else {
if (usbhid->urbout)
continue;
@@ -1187,10 +1215,16 @@ static int usbhid_start(struct hid_devic
return 0;
fail:
- usb_free_urb(usbhid->urbin);
+ if (usbhid->inurbs) {
+ for (i = 0; i < usbhid->n_inurbs; i++)
+ usb_free_urb(usbhid->inurbs[i]);
+
+ kfree(usbhid->inurbs);
+ usbhid->inurbs = NULL;
+ }
+
usb_free_urb(usbhid->urbout);
usb_free_urb(usbhid->urbctrl);
- usbhid->urbin = NULL;
usbhid->urbout = NULL;
usbhid->urbctrl = NULL;
hid_free_buffers(dev, hid);
@@ -1200,6 +1234,7 @@ fail:
static void usbhid_stop(struct hid_device *hid)
{
struct usbhid_device *usbhid = hid->driver_data;
+ int i;
if (WARN_ON(!usbhid))
return;
@@ -1208,7 +1243,15 @@ static void usbhid_stop(struct hid_devic
spin_lock_irq(&usbhid->lock); /* Sync with error and led handlers */
set_bit(HID_DISCONNECTED, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock);
- usb_kill_urb(usbhid->urbin);
+
+ for (i = 0; i < usbhid->n_inurbs; i++) {
+ usb_kill_urb(usbhid->inurbs[i]);
+ usb_free_urb(usbhid->inurbs[i]);
+ }
+
+ kfree(usbhid->inurbs);
+ usbhid->inurbs = NULL;
+
usb_kill_urb(usbhid->urbout);
usb_kill_urb(usbhid->urbctrl);
@@ -1216,10 +1259,8 @@ static void usbhid_stop(struct hid_devic
hid->claimed = 0;
- usb_free_urb(usbhid->urbin);
usb_free_urb(usbhid->urbctrl);
usb_free_urb(usbhid->urbout);
- usbhid->urbin = NULL; /* don't mess up next start */
usbhid->urbctrl = NULL;
usbhid->urbout = NULL;
@@ -1407,8 +1448,13 @@ static void hid_cancel_delayed_stuff(str
static void hid_cease_io(struct usbhid_device *usbhid)
{
+ int i;
+
del_timer_sync(&usbhid->io_retry);
- usb_kill_urb(usbhid->urbin);
+
+ for (i = 0; i < usbhid->n_inurbs; i++)
+ usb_kill_urb(usbhid->inurbs[i]);
+
usb_kill_urb(usbhid->urbctrl);
usb_kill_urb(usbhid->urbout);
}
diff -uprN -X linux-3.10-vanilla/Documentation/dontdiff
linux-3.10-vanilla/drivers/hid/usbhid/usbhid.h
linux-3.10-dev/drivers/hid/usbhid/usbhid.h
--- linux-3.10-vanilla/drivers/hid/usbhid/usbhid.h 2013-06-30
17:13:29.000000000 -0500
+++ linux-3.10-dev/drivers/hid/usbhid/usbhid.h 2013-07-22 06:17:24.413184441
-0500
@@ -66,7 +66,8 @@ struct usbhid_device {
unsigned int bufsize; /* URB
buffer size */
- struct urb *urbin; /*
Input URB */
+ int n_inurbs; /*
Number of input URBs */
+ struct urb **inurbs; /*
Input URBs */
char *inbuf; /*
Input buffer */
dma_addr_t inbuf_dma; /*
Input buffer dma */
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html