--- /dev/null
+/*
+ * f_charger.c -- USB HID function driver
+ *
+ * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com>
+ * Copyright (C) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * 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.
+ *
+ * The Linux Foundation chooses to take subject only to the GPLv2 license
+ * terms, and distributes only under these terms.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/hid.h>
+
+struct f_charger {
+ struct usb_ep *in_ep;
+ struct usb_function func;
+};
+
+static inline struct f_charger *func_to_charger(struct usb_function *f)
+{
+ return container_of(f, struct f_charger, func);
+}
+
+static const uint8_t the_report_descriptor[] = {
+ 0x06, 0xA0, 0xFF, 0x09, 0xA5, 0xA1, 0x01, 0x09,
+ 0xA6, 0x09, 0xA7, 0x15, 0x80, 0x25, 0x7F, 0x75,
+ 0x08, 0x95, 0x02, 0x81, 0x02, 0x09, 0xA9, 0x15,
+ 0x80, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x02, 0x91,
+ 0x02, 0xC0,
+};
+
+
+static struct usb_interface_descriptor charger_interface_desc = {
+ .bLength = sizeof(charger_interface_desc),
+ .bDescriptorType = USB_DT_INTERFACE,
+ /* .bInterfaceNumber = DYNAMIC */
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct hid_descriptor charger_hid_desc = {
+ .bLength = sizeof(charger_hid_desc),
+ .bDescriptorType = 0x21,
+ .bcdHID = 0x0111,
+ .bCountryCode = 0x00,
+ .bNumDescriptors = 0x1,
+ .desc[0].bDescriptorType = 0x22,
+ .desc[0].wDescriptorLength = sizeof(the_report_descriptor),
+};
+
+/* Super-Speed Support */
+
+static struct usb_endpoint_descriptor charger_ss_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 1 ,
+ .bInterval = 16,
+};
+
+static struct usb_descriptor_header *charger_ss_descriptors[] = {
+ (struct usb_descriptor_header *)&charger_interface_desc,
+ (struct usb_descriptor_header *)&charger_hid_desc,
+ (struct usb_descriptor_header *)&charger_ss_in_ep_desc,
+ NULL,
+};
+
+/* High-Speed Support */
+static struct usb_endpoint_descriptor charger_hs_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 1 ,
+ .bInterval = 16,
+};
+
+static struct usb_descriptor_header *charger_hs_descriptors[] = {
+ (struct usb_descriptor_header *)&charger_interface_desc,
+ (struct usb_descriptor_header *)&charger_hid_desc,
+ (struct usb_descriptor_header *)&charger_hs_in_ep_desc,
+ NULL,
+};
+
+/* Full-Speed Support */
+
+static struct usb_endpoint_descriptor charger_fs_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 1,
+ .bInterval = 16,
+};
+
+static struct usb_descriptor_header *charger_fs_descriptors[] = {
+ (struct usb_descriptor_header *)&charger_interface_desc,
+ (struct usb_descriptor_header *)&charger_hid_desc,
+ (struct usb_descriptor_header *)&charger_fs_in_ep_desc,
+ NULL,
+};
+
+/* Strings */
+
+#define CT_FUNC_HID_IDX 0
+
+static struct usb_string ct_func_string_defs[] = {
+ [CT_FUNC_HID_IDX].s = "HID Interface",
+ {}, /* end of list */
+};
+
+static struct usb_gadget_strings ct_func_string_table = {
+ .language = 0x0409, /* en-US */
+ .strings = ct_func_string_defs,
+};
+
+static struct usb_gadget_strings *ct_func_strings[] = {
+ &ct_func_string_table,
+ NULL,
+};
+
+
+static void charger_disable(struct usb_function *f)
+{
+ struct f_charger *charger = func_to_charger(f);
+ usb_ep_disable(charger->in_ep);
+ charger->in_ep->driver_data = NULL;
+}
+
+static int hid_setup(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int status = 0;
+ __u16 value, length;
+ bool resp_stall = false;
+
+ value = le16_to_cpu(ctrl->wValue);
+ length = le16_to_cpu(ctrl->wLength);
+
+ VDBG(cdev,
+ "hid_setup crtl_request : bRequestType:0x%x bRequest:0x%x Value:0x%x\n",
+ ctrl->bRequestType, ctrl->bRequest, value);
+
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_GET_REPORT):
+ VDBG(cdev, "get_report\n");
+
+ /* send an empty report */
+ length = min_t(unsigned, length,
+ charger_hid_desc.desc[0].wDescriptorLength);
+ memset(req->buf, 0x0, length);
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_GET_PROTOCOL):
+ VDBG(cdev, "get_protocol\n");
+ resp_stall = true;
+ break;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_SET_REPORT):
+ VDBG(cdev, "set_report | wLenght=%d\n", ctrl->wLength);
+ resp_stall = true;
+ break;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_SET_PROTOCOL):
+ VDBG(cdev, "set_protocol\n");
+ resp_stall = true;
+ break;
+
+ 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,
+ charger_hid_desc.bLength);
+ memcpy(req->buf, &charger_hid_desc, length);
+ break;
+
+ case HID_DT_REPORT:
+ VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n");
+ length = min_t(unsigned short, length,
+ charger_hid_desc.desc[0].wDescriptorLength);
+ memcpy(req->buf, &the_report_descriptor, length);
+ break;
+
+ default:
+ VDBG(cdev, "Unknown descriptor request 0x%x\n",
+ value >> 8);
+ resp_stall = true;
+ break;
+ }
+ break;
+
+ default:
+ VDBG(cdev, "Unknown request 0x%x\n",
+ ctrl->bRequest);
+ resp_stall = true;
+ break;
+ }
+
+ if (resp_stall) {
+ ERROR(cdev, "usb ep stall\n");
+ return -EOPNOTSUPP;
+ }
+
+ req->zero = 0;
+ req->length = length;
+ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (status < 0)
+ ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value);
+ return status;
+}
+
+static int charger_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct f_charger *charger = func_to_charger(f);
+ int status = 0;
+
+ VDBG(cdev, "charger_set_alt intf:%d alt:%d\n", intf, alt);
+
+ if (charger->in_ep != NULL) {
+ /* restart endpoint */
+ if (charger->in_ep->driver_data != NULL)
+ usb_ep_disable(charger->in_ep);
+
+ status = config_ep_by_speed(f->config->cdev->gadget, f,
+ charger->in_ep);
+ if (status) {
+ charger->in_ep->desc = NULL;
+ ERROR(cdev, "config_ep_by_speed FAILED!\n");
+ goto fail;
+ }
+ status = usb_ep_enable(charger->in_ep);
+ if (status < 0) {
+ ERROR(cdev, "Enable IN endpoint FAILED!\n");
+ goto fail;
+ }
+ charger->in_ep->driver_data = charger;
+ }
+fail:
+ return status;
+}
+
+
+
+static int charger_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_ep *ep;
+ struct f_charger *charger = func_to_charger(f);
+ int status;
+
+ /* allocate instance-specific interface IDs, and patch descriptors */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+
+ charger_interface_desc.bInterfaceNumber = status;
+
+ /* allocate instance-specific endpoints */
+ status = -ENODEV;
+ ep = usb_ep_autoconfig(c->cdev->gadget, &charger_fs_in_ep_desc);
+ if (!ep)
+ goto fail;
+ ep->driver_data = c->cdev; /* claim */
+ charger->in_ep = ep;
+
+ /* copy descriptors */
+ f->fs_descriptors = usb_copy_descriptors(charger_fs_descriptors);
+ if (!f->fs_descriptors)
+ goto fail;
+
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ charger_hs_in_ep_desc.bEndpointAddress =
+ charger_fs_in_ep_desc.bEndpointAddress;
+
+ f->hs_descriptors =
+ usb_copy_descriptors(charger_hs_descriptors);
+ if (!f->hs_descriptors)
+ goto fail;
+ }
+
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ charger_ss_in_ep_desc.bEndpointAddress =
+ charger_fs_in_ep_desc.bEndpointAddress;
+
+ f->ss_descriptors =
+ usb_copy_descriptors(charger_ss_descriptors);
+ if (!f->ss_descriptors)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ ERROR(f->config->cdev, "charger_bind FAILED\n");
+
+ if (f->ss_descriptors)
+ usb_free_descriptors(f->ss_descriptors);
+ if (f->hs_descriptors)
+ usb_free_descriptors(f->hs_descriptors);
+ if (f->fs_descriptors)
+ usb_free_descriptors(f->fs_descriptors);
+
+ return status;
+}
+
+static void charger_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_charger *charger = func_to_charger(f);
+
+ /* disable/free request and end point */
+ usb_ep_disable(charger->in_ep);
+
+ /* free descriptors copies */
+ if (gadget_is_superspeed(c->cdev->gadget))
+ usb_free_descriptors(f->ss_descriptors);
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+
+ usb_free_descriptors(f->fs_descriptors);
+
+ kfree(charger);
+}
+
+static int charger_bind_config(struct usb_configuration *c)
+{
+ struct f_charger *charger;
+ int status;
+
+ /* allocate and initialize one new instance */
+ charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ charger->func.name = "charging";
+ charger->func.strings = ct_func_strings;
+ charger->func.bind = charger_bind;
+ charger->func.unbind = charger_unbind;
+ charger->func.set_alt = charger_set_alt;
+ charger->func.disable = charger_disable;
+ charger->func.setup = hid_setup;
+
+ status = usb_add_function(c, &charger->func);
+ if (status)
+ kfree(charger);
+
+ return status;
+}
struct usb_request *ep0req; /* P: mutex */
struct completion ep0req_completion; /* P: mutex */
int ep0req_status; /* P: mutex */
+ struct completion epin_completion;
+ struct completion epout_completion;
/* reference counter */
atomic_t ref;
/* Protects ep->ep and ep->req. */
struct mutex mutex;
wait_queue_head_t wait;
+ atomic_t error;
struct ffs_data *ffs;
struct ffs_ep *ep; /* P: ffs->eps_lock */
static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req)
{
+ struct ffs_ep *ep = _ep->driver_data;
ENTER();
- if (likely(req->context)) {
+ /* req may be freed during unbind */
+ if (ep && ep->req && likely(req->context)) {
struct ffs_ep *ep = _ep->driver_data;
ep->status = req->status ? req->status : req->actual;
complete(req->context);
{
struct ffs_epfile *epfile = file->private_data;
struct ffs_ep *ep;
+ struct ffs_data *ffs = epfile->ffs;
char *data = NULL;
ssize_t ret;
int halt;
- int buffer_len = 0;
+ int buffer_len = !read ? len : round_up(len, 1024);
+
+ pr_debug("%s: len %d, buffer_len %d, read %d\n", __func__, len, buffer_len, read);
+
+ if (atomic_read(&epfile->error))
+ return -ENODEV;
pr_debug("%s: len %lld, read %d\n", __func__, (u64)len, read);
goto error;
}
- if (wait_event_interruptible(epfile->wait,
- (ep = epfile->ep))) {
- ret = -EINTR;
+ /* Don't wait on write if device is offline */
+ if (!read) {
+ ret = -ENODEV;
+ goto error;
+ }
+
+ /*
+ * if ep is disabled, this fails all current IOs
+ * and wait for next epfile open to happen
+ */
+ if (!atomic_read(&epfile->error)) {
+ ret = wait_event_interruptible(epfile->wait,
+ (ep = epfile->ep));
+ if (ret < 0)
+ goto error;
+ }
+ if (!ep) {
+ ret = -ENODEV;
goto error;
}
}
ret = -EBADMSG;
} else {
/* Fire the request */
- DECLARE_COMPLETION_ONSTACK(done);
+ struct completion *done;
struct usb_request *req = ep->req;
- req->context = &done;
req->complete = ffs_epfile_io_complete;
req->buf = data;
req->length = buffer_len;
+ if (read) {
+ INIT_COMPLETION(ffs->epout_completion);
+ req->context = done = &ffs->epout_completion;
+ } else {
+ INIT_COMPLETION(ffs->epin_completion);
+ req->context = done = &ffs->epin_completion;
+ }
ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC);
spin_unlock_irq(&epfile->ffs->eps_lock);
if (unlikely(ret < 0)) {
- /* nop */
- } else if (unlikely(wait_for_completion_interruptible(&done))) {
+ ret = -EIO;
+ } else if (unlikely(wait_for_completion_interruptible(done))) {
+ spin_lock_irq(&epfile->ffs->eps_lock);
+ /*
+ * While we were acquiring lock endpoint got disabled
+ * (disconnect) or changed (composition switch) ?
+ */
+ if (epfile->ep == ep)
+ usb_ep_dequeue(ep->ep, req);
+ spin_unlock_irq(&epfile->ffs->eps_lock);
ret = -EINTR;
- usb_ep_dequeue(ep->ep, req);
} else {
- ret = ep->status;
+ spin_lock_irq(&epfile->ffs->eps_lock);
+ /*
+ * While we were acquiring lock endpoint got disabled
+ * (disconnect) or changed (composition switch) ?
+ */
+ if (epfile->ep == ep)
+ ret = ep->status;
+ else
+ ret = -ENODEV;
+ spin_unlock_irq(&epfile->ffs->eps_lock);
if (read && ret > 0) {
if (ret > len)
ret = -EOVERFLOW;
file->private_data = epfile;
ffs_data_opened(epfile->ffs);
+ atomic_set(&epfile->error, 0);
return 0;
}
ENTER();
+ atomic_set(&epfile->error, 1);
ffs_data_closed(epfile->ffs);
+ file->private_data = NULL;
return 0;
}
spin_lock_init(&ffs->eps_lock);
init_waitqueue_head(&ffs->ev.waitq);
init_completion(&ffs->ep0req_completion);
+ init_completion(&ffs->epout_completion);
+ init_completion(&ffs->epin_completion);
/* XXX REVISIT need to update it in some places, or do we? */
ffs->ev.can_stall = 1;
if (ep->ep && ep->req)
usb_ep_free_request(ep->ep, ep->req);
ep->req = NULL;
+ ep->ep = NULL;
++ep;
} while (--count);
spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
spin_lock_irqsave(&func->ffs->eps_lock, flags);
do {
+ atomic_set(&epfile->error, 1);
/* pending requests get nuked */
- if (likely(ep->ep))
+ if (likely(ep->ep)) {
usb_ep_disable(ep->ep);
+ ep->ep->driver_data = NULL;
+ }
epfile->ep = NULL;
++ep;