Skip to content

Commit

Permalink
Input: HID - handle multi-transascion reports
Browse files Browse the repository at this point in the history
Fixes handling of multi-transaction reports for HID devices. New
function hid_size_buffers() that calculates the longest report
for each endpoint and stores the result in the hid_device object.
These lengths are used to allocate buffers that are large enough
to store any report on the endpoint. For compatibility, the minimum
size for an endpoint buffer set to HID_BUFFER_SIZE rather than the
known optimal case (the longest report length).

It fixes bug #3063 in bugzilla.

Signed-off-by: Michael Haboustak <mike-@cinci.rr.com>

I simplified the patch a bit to use just a single buffer size.

Signed-off-by: Vojtech Pavlik <vojtech@suse.cz>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
  • Loading branch information
Michael Haboustak authored and Dmitry Torokhov committed Sep 5, 2005
1 parent 903b126 commit bf0964d
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 19 deletions.
62 changes: 44 additions & 18 deletions drivers/usb/input/hid-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* USB HID support for Linux
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2001 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
*/

/*
Expand Down Expand Up @@ -38,7 +39,7 @@
* Version Information
*/

#define DRIVER_VERSION "v2.01"
#define DRIVER_VERSION "v2.6"
#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik"
#define DRIVER_DESC "USB HID core driver"
#define DRIVER_LICENSE "GPL"
Expand Down Expand Up @@ -1058,8 +1059,8 @@ static int hid_submit_ctrl(struct hid_device *hid)
if (maxpacket > 0) {
padlen = (len + maxpacket - 1) / maxpacket;
padlen *= maxpacket;
if (padlen > HID_BUFFER_SIZE)
padlen = HID_BUFFER_SIZE;
if (padlen > hid->bufsize)
padlen = hid->bufsize;
} else
padlen = 0;
hid->urbctrl->transfer_buffer_length = padlen;
Expand Down Expand Up @@ -1284,13 +1285,8 @@ void hid_init_reports(struct hid_device *hid)
struct hid_report *report;
int err, ret;

list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list) {
int size = ((report->size - 1) >> 3) + 1 + hid->report_enum[HID_INPUT_REPORT].numbered;
if (size > HID_BUFFER_SIZE) size = HID_BUFFER_SIZE;
if (size > hid->urbin->transfer_buffer_length)
hid->urbin->transfer_buffer_length = size;
list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
hid_submit_report(hid, report, USB_DIR_IN);
}

list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
hid_submit_report(hid, report, USB_DIR_IN);
Expand Down Expand Up @@ -1564,15 +1560,32 @@ static struct hid_blacklist {
{ 0, 0 }
};

/*
* Traverse the supplied list of reports and find the longest
*/
static void hid_find_max_report(struct hid_device *hid, unsigned int type, int *max)
{
struct hid_report *report;
int size;

list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
size = ((report->size - 1) >> 3) + 1;
if (type == HID_INPUT_REPORT && hid->report_enum[type].numbered)
size++;
if (*max < size)
*max = size;
}
}

static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid)
{
if (!(hid->inbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->inbuf_dma)))
if (!(hid->inbuf = usb_buffer_alloc(dev, hid->bufsize, SLAB_ATOMIC, &hid->inbuf_dma)))
return -1;
if (!(hid->outbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->outbuf_dma)))
if (!(hid->outbuf = usb_buffer_alloc(dev, hid->bufsize, SLAB_ATOMIC, &hid->outbuf_dma)))
return -1;
if (!(hid->cr = usb_buffer_alloc(dev, sizeof(*(hid->cr)), SLAB_ATOMIC, &hid->cr_dma)))
return -1;
if (!(hid->ctrlbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->ctrlbuf_dma)))
if (!(hid->ctrlbuf = usb_buffer_alloc(dev, hid->bufsize, SLAB_ATOMIC, &hid->ctrlbuf_dma)))
return -1;

return 0;
Expand All @@ -1581,13 +1594,13 @@ static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid)
static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
{
if (hid->inbuf)
usb_buffer_free(dev, HID_BUFFER_SIZE, hid->inbuf, hid->inbuf_dma);
usb_buffer_free(dev, hid->bufsize, hid->inbuf, hid->inbuf_dma);
if (hid->outbuf)
usb_buffer_free(dev, HID_BUFFER_SIZE, hid->outbuf, hid->outbuf_dma);
usb_buffer_free(dev, hid->bufsize, hid->outbuf, hid->outbuf_dma);
if (hid->cr)
usb_buffer_free(dev, sizeof(*(hid->cr)), hid->cr, hid->cr_dma);
if (hid->ctrlbuf)
usb_buffer_free(dev, HID_BUFFER_SIZE, hid->ctrlbuf, hid->ctrlbuf_dma);
usb_buffer_free(dev, hid->bufsize, hid->ctrlbuf, hid->ctrlbuf_dma);
}

static struct hid_device *usb_hid_configure(struct usb_interface *intf)
Expand All @@ -1598,7 +1611,7 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf)
struct hid_device *hid;
unsigned quirks = 0, rsize = 0;
char *buf, *rdesc;
int n;
int n, insize = 0;

for (n = 0; hid_blacklist[n].idVendor; n++)
if ((hid_blacklist[n].idVendor == le16_to_cpu(dev->descriptor.idVendor)) &&
Expand Down Expand Up @@ -1652,6 +1665,19 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf)
kfree(rdesc);
hid->quirks = quirks;

hid->bufsize = HID_MIN_BUFFER_SIZE;
hid_find_max_report(hid, HID_INPUT_REPORT, &hid->bufsize);
hid_find_max_report(hid, HID_OUTPUT_REPORT, &hid->bufsize);
hid_find_max_report(hid, HID_FEATURE_REPORT, &hid->bufsize);

if (hid->bufsize > HID_MAX_BUFFER_SIZE)
hid->bufsize = HID_MAX_BUFFER_SIZE;

hid_find_max_report(hid, HID_INPUT_REPORT, &insize);

if (insize > HID_MAX_BUFFER_SIZE)
insize = HID_MAX_BUFFER_SIZE;

if (hid_alloc_buffers(dev, hid)) {
hid_free_buffers(dev, hid);
goto fail;
Expand Down Expand Up @@ -1682,7 +1708,7 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf)
if (!(hid->urbin = usb_alloc_urb(0, GFP_KERNEL)))
goto fail;
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
usb_fill_int_urb(hid->urbin, dev, pipe, hid->inbuf, 0,
usb_fill_int_urb(hid->urbin, dev, pipe, hid->inbuf, insize,
hid_irq_in, hid, interval);
hid->urbin->transfer_dma = hid->inbuf_dma;
hid->urbin->transfer_flags |=(URB_NO_TRANSFER_DMA_MAP | URB_ASYNC_UNLINK);
Expand Down
5 changes: 4 additions & 1 deletion drivers/usb/input/hid.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,8 @@ struct hid_report_enum {

#define HID_REPORT_TYPES 3

#define HID_BUFFER_SIZE 64 /* use 64 for compatibility with all possible packetlen */
#define HID_MIN_BUFFER_SIZE 64 /* make sure there is at least a packet size of space */
#define HID_MAX_BUFFER_SIZE 4096 /* 4kb */
#define HID_CONTROL_FIFO_SIZE 256 /* to init devices with >100 reports */
#define HID_OUTPUT_FIFO_SIZE 64

Expand Down Expand Up @@ -389,6 +390,8 @@ struct hid_device { /* device report descriptor */

unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */

unsigned int bufsize; /* URB buffer size */

struct urb *urbin; /* Input URB */
char *inbuf; /* Input buffer */
dma_addr_t inbuf_dma; /* Input buffer dma */
Expand Down

0 comments on commit bf0964d

Please sign in to comment.