diff options
Diffstat (limited to 'drivers/usb/gadget/f_hid.c')
| -rw-r--r-- | drivers/usb/gadget/f_hid.c | 267 |
1 files changed, 180 insertions, 87 deletions
diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c index 4f891eddd06..a95290a1289 100644 --- a/drivers/usb/gadget/f_hid.c +++ b/drivers/usb/gadget/f_hid.c @@ -7,35 +7,33 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/kernel.h> -#include <linux/utsname.h> #include <linux/module.h> #include <linux/hid.h> #include <linux/cdev.h> #include <linux/mutex.h> #include <linux/poll.h> -#include <linux/smp_lock.h> #include <linux/uaccess.h> #include <linux/wait.h> +#include <linux/sched.h> #include <linux/usb/g_hid.h> +#include "u_f.h" + static int major, minors; static struct class *hidg_class; /*-------------------------------------------------------------------------*/ /* HID gadget struct */ +struct f_hidg_req_list { + struct usb_request *req; + unsigned int pos; + struct list_head list; +}; + struct f_hidg { /* configuration */ unsigned char bInterfaceSubClass; @@ -45,10 +43,10 @@ struct f_hidg { unsigned short report_length; /* recv report */ - char *set_report_buff; - unsigned short set_report_length; + struct list_head completed_out_req; spinlock_t spinlock; wait_queue_head_t read_queue; + unsigned int qlen; /* send report */ struct mutex lock; @@ -59,9 +57,9 @@ struct f_hidg { int minor; struct cdev cdev; struct usb_function func; + struct usb_ep *in_ep; - struct usb_endpoint_descriptor *fs_in_ep_desc; - struct usb_endpoint_descriptor *hs_in_ep_desc; + struct usb_ep *out_ep; }; static inline struct f_hidg *func_to_hidg(struct usb_function *f) @@ -77,7 +75,7 @@ static struct usb_interface_descriptor hidg_interface_desc = { .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bAlternateSetting = 0, - .bNumEndpoints = 1, + .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_HID, /* .bInterfaceSubClass = DYNAMIC */ /* .bInterfaceProtocol = DYNAMIC */ @@ -108,10 +106,23 @@ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { */ }; +static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 4, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + static struct usb_descriptor_header *hidg_hs_descriptors[] = { (struct usb_descriptor_header *)&hidg_interface_desc, (struct usb_descriptor_header *)&hidg_desc, (struct usb_descriptor_header *)&hidg_hs_in_ep_desc, + (struct usb_descriptor_header *)&hidg_hs_out_ep_desc, NULL, }; @@ -129,10 +140,23 @@ static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { */ }; +static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 10, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + static struct usb_descriptor_header *hidg_fs_descriptors[] = { (struct usb_descriptor_header *)&hidg_interface_desc, (struct usb_descriptor_header *)&hidg_desc, (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, + (struct usb_descriptor_header *)&hidg_fs_out_ep_desc, NULL, }; @@ -142,9 +166,11 @@ static struct usb_descriptor_header *hidg_fs_descriptors[] = { static ssize_t f_hidg_read(struct file *file, char __user *buffer, size_t count, loff_t *ptr) { - struct f_hidg *hidg = file->private_data; - char *tmp_buff = NULL; - unsigned long flags; + struct f_hidg *hidg = file->private_data; + struct f_hidg_req_list *list; + struct usb_request *req; + unsigned long flags; + int ret; if (!count) return 0; @@ -154,8 +180,9 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->spinlock, flags); -#define READ_COND (hidg->set_report_buff != NULL) +#define READ_COND (!list_empty(&hidg->completed_out_req)) + /* wait for at least one buffer to complete */ while (!READ_COND) { spin_unlock_irqrestore(&hidg->spinlock, flags); if (file->f_flags & O_NONBLOCK) @@ -167,19 +194,34 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->spinlock, flags); } - - count = min_t(unsigned, count, hidg->set_report_length); - tmp_buff = hidg->set_report_buff; - hidg->set_report_buff = NULL; - + /* pick the first one */ + list = list_first_entry(&hidg->completed_out_req, + struct f_hidg_req_list, list); + req = list->req; + count = min_t(unsigned int, count, req->actual - list->pos); spin_unlock_irqrestore(&hidg->spinlock, flags); - if (tmp_buff != NULL) { - /* copy to user outside spinlock */ - count -= copy_to_user(buffer, tmp_buff, count); - kfree(tmp_buff); - } else - count = -ENOMEM; + /* copy to user outside spinlock */ + count -= copy_to_user(buffer, req->buf + list->pos, count); + list->pos += count; + + /* + * if this request is completely handled and transfered to + * userspace, remove its entry from the list and requeue it + * again. Otherwise, we will revisit it again upon the next + * call, taking into account its current read position. + */ + if (list->pos == req->actual) { + spin_lock_irqsave(&hidg->spinlock, flags); + list_del(&list->list); + kfree(list); + spin_unlock_irqrestore(&hidg->spinlock, flags); + + req->length = hidg->report_length; + ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL); + if (ret < 0) + return ret; + } return count; } @@ -294,28 +336,27 @@ static int f_hidg_open(struct inode *inode, struct file *fd) /*-------------------------------------------------------------------------*/ /* usb_function */ +static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, + unsigned length) +{ + return alloc_ep_req(ep, length, length); +} + static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) { - struct f_hidg *hidg = (struct f_hidg *)req->context; + struct f_hidg *hidg = (struct f_hidg *) req->context; + struct f_hidg_req_list *req_list; + unsigned long flags; - if (req->status != 0 || req->buf == NULL || req->actual == 0) { - ERROR(hidg->func.config->cdev, "%s FAILED\n", __func__); + req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC); + if (!req_list) return; - } - - spin_lock(&hidg->spinlock); - - hidg->set_report_buff = krealloc(hidg->set_report_buff, - req->actual, GFP_ATOMIC); - if (hidg->set_report_buff == NULL) { - spin_unlock(&hidg->spinlock); - return; - } - hidg->set_report_length = req->actual; - memcpy(hidg->set_report_buff, req->buf, req->actual); + req_list->req = req; - spin_unlock(&hidg->spinlock); + spin_lock_irqsave(&hidg->spinlock, flags); + list_add_tail(&req_list->list, &hidg->completed_out_req); + spin_unlock_irqrestore(&hidg->spinlock, flags); wake_up(&hidg->read_queue); } @@ -356,9 +397,7 @@ static int hidg_setup(struct usb_function *f, case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_SET_REPORT): VDBG(cdev, "set_report | wLenght=%d\n", ctrl->wLength); - req->context = hidg; - req->complete = hidg_set_report_complete; - goto respond; + goto stall; break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 @@ -370,6 +409,13 @@ static int hidg_setup(struct usb_function *f, case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8 | USB_REQ_GET_DESCRIPTOR): switch (value >> 8) { + case HID_DT_HID: + VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n"); + length = min_t(unsigned short, length, + hidg_desc.bLength); + memcpy(req->buf, &hidg_desc, length); + goto respond; + break; case HID_DT_REPORT: VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n"); length = min_t(unsigned short, length, @@ -379,7 +425,7 @@ static int hidg_setup(struct usb_function *f, break; default: - VDBG(cdev, "Unknown decriptor request 0x%x\n", + VDBG(cdev, "Unknown descriptor request 0x%x\n", value >> 8); goto stall; break; @@ -408,17 +454,25 @@ respond: static void hidg_disable(struct usb_function *f) { struct f_hidg *hidg = func_to_hidg(f); + struct f_hidg_req_list *list, *next; usb_ep_disable(hidg->in_ep); hidg->in_ep->driver_data = NULL; + + usb_ep_disable(hidg->out_ep); + hidg->out_ep->driver_data = NULL; + + list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { + list_del(&list->list); + kfree(list); + } } static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = f->config->cdev; struct f_hidg *hidg = func_to_hidg(f); - const struct usb_endpoint_descriptor *ep_desc; - int status = 0; + int i, status = 0; VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); @@ -427,15 +481,63 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (hidg->in_ep->driver_data != NULL) usb_ep_disable(hidg->in_ep); - ep_desc = ep_choose(f->config->cdev->gadget, - hidg->hs_in_ep_desc, hidg->fs_in_ep_desc); - status = usb_ep_enable(hidg->in_ep, ep_desc); + status = config_ep_by_speed(f->config->cdev->gadget, f, + hidg->in_ep); + if (status) { + ERROR(cdev, "config_ep_by_speed FAILED!\n"); + goto fail; + } + status = usb_ep_enable(hidg->in_ep); if (status < 0) { - ERROR(cdev, "Enable endpoint FAILED!\n"); + ERROR(cdev, "Enable IN endpoint FAILED!\n"); goto fail; } hidg->in_ep->driver_data = hidg; } + + + if (hidg->out_ep != NULL) { + /* restart endpoint */ + if (hidg->out_ep->driver_data != NULL) + usb_ep_disable(hidg->out_ep); + + status = config_ep_by_speed(f->config->cdev->gadget, f, + hidg->out_ep); + if (status) { + ERROR(cdev, "config_ep_by_speed FAILED!\n"); + goto fail; + } + status = usb_ep_enable(hidg->out_ep); + if (status < 0) { + ERROR(cdev, "Enable IN endpoint FAILED!\n"); + goto fail; + } + hidg->out_ep->driver_data = hidg; + + /* + * allocate a bunch of read buffers and queue them all at once. + */ + for (i = 0; i < hidg->qlen && status == 0; i++) { + struct usb_request *req = + hidg_alloc_ep_req(hidg->out_ep, + hidg->report_length); + if (req) { + req->complete = hidg_set_report_complete; + req->context = hidg; + status = usb_ep_queue(hidg->out_ep, req, + GFP_ATOMIC); + if (status) + ERROR(cdev, "%s queue req --> %d\n", + hidg->out_ep->name, status); + } else { + usb_ep_disable(hidg->out_ep); + hidg->out_ep->driver_data = NULL; + status = -ENOMEM; + goto fail; + } + } + } + fail: return status; } @@ -463,7 +565,6 @@ static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) goto fail; hidg_interface_desc.bInterfaceNumber = status; - /* allocate instance-specific endpoints */ status = -ENODEV; ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc); @@ -472,13 +573,18 @@ static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) ep->driver_data = c->cdev; /* claim */ hidg->in_ep = ep; + ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); + if (!ep) + goto fail; + ep->driver_data = c->cdev; /* claim */ + hidg->out_ep = ep; + /* preallocate request and buffer */ status = -ENOMEM; hidg->req = usb_ep_alloc_request(hidg->in_ep, GFP_KERNEL); if (!hidg->req) goto fail; - hidg->req->buf = kmalloc(hidg->report_length, GFP_KERNEL); if (!hidg->req->buf) goto fail; @@ -488,38 +594,27 @@ static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT; hidg_desc.desc[0].wDescriptorLength = cpu_to_le16(hidg->report_desc_length); - hidg->set_report_buff = NULL; + hidg_hs_in_ep_desc.bEndpointAddress = + hidg_fs_in_ep_desc.bEndpointAddress; + hidg_hs_out_ep_desc.bEndpointAddress = + hidg_fs_out_ep_desc.bEndpointAddress; - /* copy descriptors */ - f->descriptors = usb_copy_descriptors(hidg_fs_descriptors); - if (!f->descriptors) + status = usb_assign_descriptors(f, hidg_fs_descriptors, + hidg_hs_descriptors, NULL); + if (status) goto fail; - hidg->fs_in_ep_desc = usb_find_endpoint(hidg_fs_descriptors, - f->descriptors, - &hidg_fs_in_ep_desc); - - if (gadget_is_dualspeed(c->cdev->gadget)) { - hidg_hs_in_ep_desc.bEndpointAddress = - hidg_fs_in_ep_desc.bEndpointAddress; - f->hs_descriptors = usb_copy_descriptors(hidg_hs_descriptors); - if (!f->hs_descriptors) - goto fail; - hidg->hs_in_ep_desc = usb_find_endpoint(hidg_hs_descriptors, - f->hs_descriptors, - &hidg_hs_in_ep_desc); - } else { - hidg->hs_in_ep_desc = NULL; - } - mutex_init(&hidg->lock); spin_lock_init(&hidg->spinlock); init_waitqueue_head(&hidg->write_queue); init_waitqueue_head(&hidg->read_queue); + INIT_LIST_HEAD(&hidg->completed_out_req); /* create char device */ cdev_init(&hidg->cdev, &f_hidg_fops); @@ -540,9 +635,7 @@ fail: usb_ep_free_request(hidg->in_ep, hidg->req); } - usb_free_descriptors(f->hs_descriptors); - usb_free_descriptors(f->descriptors); - + usb_free_all_descriptors(f); return status; } @@ -559,12 +652,9 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) kfree(hidg->req->buf); usb_ep_free_request(hidg->in_ep, hidg->req); - /* free descriptors copies */ - usb_free_descriptors(f->hs_descriptors); - usb_free_descriptors(f->descriptors); + usb_free_all_descriptors(f); kfree(hidg->report_desc); - kfree(hidg->set_report_buff); kfree(hidg); } @@ -635,6 +725,9 @@ int __init hidg_bind_config(struct usb_configuration *c, hidg->func.disable = hidg_disable; hidg->func.setup = hidg_setup; + /* this could me made configurable at some point */ + hidg->qlen = 4; + status = usb_add_function(c, &hidg->func); if (status) kfree(hidg); |
