diff --git a/arch/arm64/configs/gki_defconfig b/arch/arm64/configs/gki_defconfig index ff68b3135a38..f3af57f9736d 100644 --- a/arch/arm64/configs/gki_defconfig +++ b/arch/arm64/configs/gki_defconfig @@ -540,6 +540,7 @@ CONFIG_USB_CONFIGFS_F_UAC2=y CONFIG_USB_CONFIGFS_F_MIDI=y CONFIG_USB_CONFIGFS_F_HID=y CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_ANDROID_USB_CONFIGFS_F_ACC=y CONFIG_TYPEC=y CONFIG_TYPEC_TCPM=y CONFIG_TYPEC_TCPCI=y diff --git a/arch/x86/configs/gki_defconfig b/arch/x86/configs/gki_defconfig index c72e44255e9d..c1975529a9db 100644 --- a/arch/x86/configs/gki_defconfig +++ b/arch/x86/configs/gki_defconfig @@ -502,6 +502,7 @@ CONFIG_USB_CONFIGFS_F_UAC2=y CONFIG_USB_CONFIGFS_F_MIDI=y CONFIG_USB_CONFIGFS_F_HID=y CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_ANDROID_USB_CONFIGFS_F_ACC=y CONFIG_TYPEC=y CONFIG_TYPEC_TCPM=y CONFIG_TYPEC_TCPCI=y diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 3ce0f547719d..3a4f08ce4113 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -221,6 +221,10 @@ config USB_F_PRINTER config USB_F_TCM tristate +# due to coupling with composite driver, can not be built as a module. +config ANDROID_USB_F_ACC + bool + # this first set of drivers all depend on bulk-capable hardware. config USB_CONFIGFS @@ -517,6 +521,18 @@ config USB_CONFIGFS_F_TCM Both protocols can work on USB2.0 and USB3.0. UAS utilizes the USB 3.0 feature called streams support. +config ANDROID_USB_CONFIGFS_F_ACC + bool "Accessory Gadget" + depends on USB_CONFIGFS + depends on HID=y + select ANDROID_USB_F_ACC + help + This Accessory function adds support for the Android Open Accessory + (AOA) protocol v2. Devices which support this protocol send vendor + specific control_requests to describe what they are and what + userspace application should handle their traffic. This function + enables the data exchange with usersapce. + source "drivers/usb/gadget/legacy/Kconfig" endif # USB_GADGET diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index a95e63f96c16..1d9f800d184b 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -2241,6 +2242,23 @@ unknown: } f = NULL; + /* + * Android: The accessory function can handle some control + * requests despite not being allocated to a config. Therefore, + * the upstream logic for checking req_match will not work until + * the attached device issues an ACCESSORY_START command and + * userspace tears down the gadget, adds the accessory function + * to the config, and binds the config to the UDC again. + * + * To workaround the existing userspace limitiations, check to + * see if the f_accessory driver can handle the ctrl request, + * and if so, pass it along. + */ + if (android_acc_req_match_composite(cdev, ctrl)) { + value = android_acc_setup_composite(cdev, ctrl); + goto done; + } + switch (ctrl->bRequestType & USB_RECIP_MASK) { case USB_RECIP_INTERFACE: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) @@ -2316,6 +2334,12 @@ static void __composite_disconnect(struct usb_gadget *gadget) struct usb_composite_dev *cdev = get_gadget_data(gadget); unsigned long flags; + /* + * Android: f_accessory can handle HID packets without being bound to a + * config so we unfortunately require this hook to clean it up. + */ + android_acc_disconnect(); + android_set_disconnected(&cdev->android_opts); /* REVISIT: should we have config and device level diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index 87917a7d4a9b..46435e42845d 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -52,3 +52,5 @@ usb_f_printer-y := f_printer.o obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o usb_f_tcm-y := f_tcm.o obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o +usb_f_acc-y := android_f_accessory.o +obj-$(CONFIG_ANDROID_USB_F_ACC) += usb_f_acc.o diff --git a/drivers/usb/gadget/function/android_f_accessory.c b/drivers/usb/gadget/function/android_f_accessory.c new file mode 100644 index 000000000000..85241f1beadf --- /dev/null +++ b/drivers/usb/gadget/function/android_f_accessory.c @@ -0,0 +1,1605 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright 2011-2024 Google LLC + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MAX_INST_NAME_LEN 40 +#define BULK_BUFFER_SIZE 16384 + +#define PROTOCOL_VERSION 2 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 + +struct acc_hid_dev { + struct list_head list; + struct hid_device *hid; + struct acc_dev *dev; + /* accessory defined ID */ + int id; + /* HID report descriptor */ + u8 *report_desc; + /* length of HID report descriptor */ + int report_desc_len; + /* number of bytes of report_desc we have received so far */ + int report_desc_offset; +}; + +struct acc_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + struct kref kref; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + /* + * online indicates state of function_set_alt & function_unbind + * set to true when we connect + */ + bool online; + + /* + * disconnected indicates state of open & release + * Set to true when we disconnect. + */ + bool disconnected; + + /* strings sent by the host */ + char manufacturer[ACC_STRING_SIZE]; + char model[ACC_STRING_SIZE]; + char description[ACC_STRING_SIZE]; + char version[ACC_STRING_SIZE]; + char uri[ACC_STRING_SIZE]; + char serial[ACC_STRING_SIZE]; + + /* for acc_complete_set_string */ + int string_index; + + /* set to 1 if we have a pending start request */ + int start_requested; + + int audio_mode; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* delayed work for handling ACCESSORY_START */ + struct delayed_work start_work; + + /* work for handling ACCESSORY GET PROTOCOL */ + struct work_struct getprotocol_work; + + /* work for handling ACCESSORY SEND STRING */ + struct work_struct sendstring_work; + + /* worker for registering and unregistering hid devices */ + struct work_struct hid_work; + + /* list of active HID devices */ + struct list_head hid_list; + + /* list of new HID devices to register */ + struct list_head new_hid_list; + + /* list of dead HID devices to unregister */ + struct list_head dead_hid_list; +}; + +static struct usb_interface_descriptor acc_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_endpoint_descriptor acc_superspeedplus_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor acc_superspeedplus_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor acc_superspeedplus_comp_desc = { + .bLength = sizeof(acc_superspeedplus_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor acc_superspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor acc_superspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor acc_superspeed_comp_desc = { + .bLength = sizeof(acc_superspeed_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor acc_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acc_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_fullspeed_in_desc, + (struct usb_descriptor_header *) &acc_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_highspeed_in_desc, + (struct usb_descriptor_header *) &acc_highspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_superspeed_in_desc, + (struct usb_descriptor_header *) &acc_superspeed_comp_desc, + (struct usb_descriptor_header *) &acc_superspeed_out_desc, + (struct usb_descriptor_header *) &acc_superspeed_comp_desc, + NULL, +}; + +static struct usb_descriptor_header *ssp_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_superspeedplus_in_desc, + (struct usb_descriptor_header *) &acc_superspeedplus_comp_desc, + (struct usb_descriptor_header *) &acc_superspeedplus_out_desc, + (struct usb_descriptor_header *) &acc_superspeedplus_comp_desc, + NULL, +}; + +static struct usb_string acc_string_defs[] = { + [INTERFACE_STRING_INDEX].s = "Android Accessory Interface", + { }, /* end of list */ +}; + +static struct usb_gadget_strings acc_string_table = { + .language = 0x0409, /* en-US */ + .strings = acc_string_defs, +}; + +static struct usb_gadget_strings *acc_strings[] = { + &acc_string_table, + NULL, +}; + +static DEFINE_MUTEX(acc_dev_instance_lock); +static struct acc_dev *acc_dev_instance; + +struct acc_instance { + struct usb_function_instance func_inst; + const char *name; +}; + +static struct acc_dev *get_acc_dev(void) +{ + mutex_lock(&acc_dev_instance_lock); + if (acc_dev_instance) + kref_get(&acc_dev_instance->kref); + mutex_unlock(&acc_dev_instance_lock); + + return acc_dev_instance; +} + +static void __acc_dev_instance_release(struct kref *kref) +{ + struct acc_dev *dev = container_of(kref, struct acc_dev, kref); + + acc_dev_instance = NULL; + + /* Cancel any async work */ + cancel_delayed_work_sync(&dev->start_work); + cancel_work_sync(&dev->getprotocol_work); + cancel_work_sync(&dev->sendstring_work); + cancel_work_sync(&dev->hid_work); + + kfree(dev); +} + +static void put_acc_dev(struct acc_dev *dev) +{ + kref_put_mutex(&acc_dev_instance->kref, __acc_dev_instance_release, + &acc_dev_instance_lock); +} + +static inline struct acc_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct acc_dev, function); +} + +static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void acc_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/* add a request to the tail of a list */ +static void req_put(struct acc_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void acc_free_all_requests(struct acc_dev *dev) +{ + struct usb_request *req; + int i; + + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) { + acc_request_free(dev->rx_req[i], dev->ep_out); + dev->rx_req[i] = NULL; + } +} + +static void acc_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = get_acc_dev(); + + if (!dev) + return; + + if (req->status == -ESHUTDOWN) { + pr_debug("set disconnected\n"); + dev->disconnected = true; + } + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); + put_acc_dev(dev); +} + +static void acc_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = get_acc_dev(); + + if (!dev) + return; + + dev->rx_done = 1; + if (req->status == -ESHUTDOWN) { + pr_debug("set disconnected\n"); + dev->disconnected = true; + } + + wake_up(&dev->read_wq); + put_acc_dev(dev); +} + +static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = ep->driver_data; + char *string_dest = NULL; + int length = req->actual; + + if (req->status != 0) { + pr_err("err %d\n", req->status); + return; + } + + switch (dev->string_index) { + case ACCESSORY_STRING_MANUFACTURER: + string_dest = dev->manufacturer; + break; + case ACCESSORY_STRING_MODEL: + string_dest = dev->model; + break; + case ACCESSORY_STRING_DESCRIPTION: + string_dest = dev->description; + break; + case ACCESSORY_STRING_VERSION: + string_dest = dev->version; + break; + case ACCESSORY_STRING_URI: + string_dest = dev->uri; + break; + case ACCESSORY_STRING_SERIAL: + string_dest = dev->serial; + break; + } + if (string_dest) { + unsigned long flags; + + if (length >= ACC_STRING_SIZE) + length = ACC_STRING_SIZE - 1; + + spin_lock_irqsave(&dev->lock, flags); + memcpy(string_dest, req->buf, length); + /* ensure zero termination */ + string_dest[length] = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + pr_err("unknown accessory string index %d\n", + dev->string_index); + } +} + +static void acc_complete_set_hid_report_desc(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + struct acc_dev *dev = hid->dev; + int length = req->actual; + + if (req->status != 0) { + pr_err("err %d\n", req->status); + return; + } + + memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length); + hid->report_desc_offset += length; + if (hid->report_desc_offset == hid->report_desc_len) { + /* After we have received the entire report descriptor + * we schedule work to initialize the HID device + */ + schedule_work(&dev->hid_work); + } +} + +static void acc_complete_send_hid_event(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + int length = req->actual; + + if (req->status != 0) { + pr_err("err %d\n", req->status); + return; + } + + hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1); +} + +static int acc_hid_parse(struct hid_device *hid) +{ + struct acc_hid_dev *hdev = hid->driver_data; + + hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len); + return 0; +} + +// Required by the hid_ll_driver, so do nothing. +static int acc_hid_start(struct hid_device *hid) +{ + return 0; +} + +// Required by the hid_ll_driver, so do nothing. +static void acc_hid_stop(struct hid_device *hid) +{ +} + +// Required by the hid_ll_driver, so do nothing. +static int acc_hid_open(struct hid_device *hid) +{ + return 0; +} + +// Required by the hid_ll_driver, so do nothing. +static void acc_hid_close(struct hid_device *hid) +{ +} + +static int acc_hid_raw_request(struct hid_device *hid, unsigned char reportnum, + __u8 *buf, size_t len, unsigned char rtype, int reqtype) +{ + return 0; +} + +static struct hid_ll_driver acc_hid_ll_driver = { + .parse = acc_hid_parse, + .start = acc_hid_start, + .stop = acc_hid_stop, + .open = acc_hid_open, + .close = acc_hid_close, + .raw_request = acc_hid_raw_request, +}; + +static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, + int id, int desc_len) +{ + struct acc_hid_dev *hdev; + + hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC); + if (!hdev) + return NULL; + hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC); + if (!hdev->report_desc) { + kfree(hdev); + return NULL; + } + hdev->dev = dev; + hdev->id = id; + hdev->report_desc_len = desc_len; + + return hdev; +} + +/** acc_hid_get_locked - a helper function to walk a list of hid devices and + * return a pointer to the acc_hid_dev which matches the id argument which + * matches the id argument. + * + * @list - the list of devices to search through + * @id - the id of the acc_hid_dev to find + * + * The caller of this function must protect the list by locking the + * acc_dev->lock prior to calling this function. + * + * Returns: a pointer to the acc_hid_dev with the specified id or NULL if that + * id is not found in the list. + */ +static struct acc_hid_dev *acc_hid_get_locked(struct list_head *list, int id) +{ + struct acc_hid_dev *hid; + + list_for_each_entry(hid, list, list) { + if (hid->id == id) + return hid; + } + return NULL; +} + +static int acc_register_hid(struct acc_dev *dev, int id, int desc_length) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + /* report descriptor length must be > 0 */ + if (desc_length <= 0) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + /* replace HID if one already exists with this ID */ + hid = acc_hid_get_locked(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get_locked(&dev->new_hid_list, id); + if (hid) + list_move(&hid->list, &dev->dead_hid_list); + + hid = acc_hid_new(dev, id, desc_length); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + list_add(&hid->list, &dev->new_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + /* schedule work to register the HID device */ + schedule_work(&dev->hid_work); + return 0; +} + +static int acc_unregister_hid(struct acc_dev *dev, int id) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get_locked(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get_locked(&dev->new_hid_list, id); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + + list_move(&hid->list, &dev->dead_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); + return 0; +} + +static int create_bulk_endpoints(struct acc_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_out; + dev->rx_req[i] = req; + } + + return 0; + +fail: + pr_err("could not allocate requests\n"); + acc_free_all_requests(dev); + + return -1; +} + +static ssize_t acc_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req; + ssize_t r = count; + ssize_t data_length; + unsigned int xfer; + int ret = 0; + + if (dev->disconnected) { + pr_debug("disconnected\n"); + return -ENODEV; + } + + if (count > BULK_BUFFER_SIZE) + count = BULK_BUFFER_SIZE; + + /* we will block until we're online */ + pr_debug("waiting for online\n"); + ret = wait_event_interruptible(dev->read_wq, dev->online); + if (ret < 0) { + r = ret; + goto done; + } + + if (!dev->rx_req[0]) { + pr_debug("USB request already handled/freed\n"); + r = -EINVAL; + goto done; + } + + /* + * Calculate the data length by considering termination character. + * Then compansite the difference of rounding up to + * integer multiple of maxpacket size. + */ + data_length = count; + data_length += dev->ep_out->maxpacket - 1; + data_length -= data_length % dev->ep_out->maxpacket; + + if (dev->rx_done) { + // last req cancelled. try to get it. + req = dev->rx_req[0]; + goto copy_data; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = data_length; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + ret = usb_ep_dequeue(dev->ep_out, req); + if (ret != 0) { + // cancel failed. There can be a data already received. + // it will be retrieved in the next read. + pr_debug("cancelling failed %d\n", ret); + } + goto done; + } + +copy_data: + dev->rx_done = 0; + if (dev->online) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %u\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + pr_debug("returning %zd\n", r); + return r; +} + +static ssize_t acc_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req = 0; + ssize_t r = count; + unsigned int xfer; + int ret; + + if (!dev->online || dev->disconnected) { + pr_debug("disconnected or not online\n"); + return -ENODEV; + } + + while (count > 0) { + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) || !dev->online)); + if (!dev->online || dev->disconnected) { + pr_debug("dev->error\n"); + r = -EIO; + break; + } + + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) { + xfer = BULK_BUFFER_SIZE; + /* ZLP, They will be more TX requests so not yet. */ + req->zero = 0; + } else { + xfer = count; + /* If the data length is a multiple of the + * maxpacket size then send a zero length packet(ZLP). + */ + req->zero = ((xfer % dev->ep_in->maxpacket) == 0); + } + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + pr_debug("returning %zd\n", r); + return r; +} + +static long acc_ioctl(struct file *fp, unsigned int code, unsigned long value) +{ + struct acc_dev *dev = fp->private_data; + char *src = NULL; + int ret; + + switch (code) { + case ACCESSORY_GET_STRING_MANUFACTURER: + src = dev->manufacturer; + break; + case ACCESSORY_GET_STRING_MODEL: + src = dev->model; + break; + case ACCESSORY_GET_STRING_DESCRIPTION: + src = dev->description; + break; + case ACCESSORY_GET_STRING_VERSION: + src = dev->version; + break; + case ACCESSORY_GET_STRING_URI: + src = dev->uri; + break; + case ACCESSORY_GET_STRING_SERIAL: + src = dev->serial; + break; + case ACCESSORY_IS_START_REQUESTED: + return dev->start_requested; + case ACCESSORY_GET_AUDIO_MODE: + return dev->audio_mode; + } + if (!src) + return -ENOTTY; + + ret = strlen(src) + 1; + if (copy_to_user((void __user *)value, src, ret)) + ret = -EFAULT; + return ret; +} + +static int acc_open(struct inode *ip, struct file *fp) +{ + struct acc_dev *dev = get_acc_dev(); + + if (!dev) + return -ENODEV; + + dev->disconnected = false; + fp->private_data = dev; + return 0; +} + +static int acc_release(struct inode *ip, struct file *fp) +{ + struct acc_dev *dev = fp->private_data; + + if (!dev) + return -ENOENT; + + /* indicate that we are disconnected + * still could be online so don't touch online flag + */ + dev->disconnected = true; + + fp->private_data = NULL; + put_acc_dev(dev); + return 0; +} + +/* file operations for /dev/usb_accessory */ +static const struct file_operations acc_fops = { + .owner = THIS_MODULE, + .read = acc_read, + .write = acc_write, + .unlocked_ioctl = acc_ioctl, + .compat_ioctl = acc_ioctl, + .open = acc_open, + .release = acc_release, +}; + +static int acc_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) + return ret; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static struct miscdevice acc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "usb_accessory", + .fops = &acc_fops, +}; + +static const struct hid_device_id acc_hid_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver acc_hid_driver = { + .name = "USB accessory", + .id_table = acc_hid_table, + .probe = acc_hid_probe, +}; + +static void acc_complete_setup_noop(struct usb_ep *ep, struct usb_request *req) +{ + /* + * Default no-op function when nothing needs to be done for the + * setup request + */ +} + +static int __acc_function_bind(struct usb_configuration *c, + struct usb_function *f, bool configfs) +{ + struct usb_composite_dev *cdev = c->cdev; + struct usb_string *us; + struct acc_dev *dev = func_to_dev(f); + int id; + int ret; + + if (configfs) { + us = usb_gstrings_attach(cdev, acc_strings, + ARRAY_SIZE(acc_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + ret = us[INTERFACE_STRING_INDEX].id; + acc_interface_desc.iInterface = ret; + dev->cdev = c->cdev; + } + dev->start_requested = 0; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + acc_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc, + &acc_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + acc_highspeed_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_highspeed_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + + /* support super speed hardware */ + acc_superspeed_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_superspeed_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + + /* support super speed plus hardware */ + acc_superspeedplus_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_superspeedplus_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + + ret = hid_register_driver(&acc_hid_driver); + if (ret) { + /* Cleanup requests allocated in create_bulk_endpoints() */ + acc_free_all_requests(dev); + return ret; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + + return 0; +} + +static int acc_function_bind_configfs(struct usb_configuration *c, + struct usb_function *f) +{ + return __acc_function_bind(c, f, true); +} + +static void kill_all_hid_devices(struct acc_dev *dev) +{ + struct acc_hid_dev *hid; + struct list_head *entry, *temp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(entry, temp, &dev->hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); +} + +static void acc_hid_unbind(struct acc_dev *dev) +{ + hid_unregister_driver(&acc_hid_driver); + kill_all_hid_devices(dev); +} + +static void acc_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + + dev->online = false; /* clear online flag */ + wake_up(&dev->read_wq); /* unblock reads on closure */ + wake_up(&dev->write_wq); /* likewise for writes */ + + acc_free_all_requests(dev); + + acc_hid_unbind(dev); +} + +static void acc_getprotocol_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=GETPROTOCOL", NULL }; + + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static void acc_sendstring_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=SENDSTRING", NULL }; + + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static void acc_start_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=START", NULL }; + + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static int acc_hid_init(struct acc_hid_dev *hdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &acc_hid_ll_driver; + hid->dev.parent = acc_device.this_device; + + hid->bus = BUS_USB; + hid->vendor = HID_ANY_ID; + hid->product = HID_ANY_ID; + hid->driver_data = hdev; + ret = hid_add_device(hid); + if (ret) { + pr_err("can't add hid device: %d\n", ret); + hid_destroy_device(hid); + return ret; + } + + hdev->hid = hid; + return 0; +} + +static void acc_hid_delete(struct acc_hid_dev *hid) +{ + kfree(hid->report_desc); + kfree(hid); +} + +static void acc_hid_work(struct work_struct *data) +{ + struct acc_dev *dev = get_acc_dev(); + struct list_head *entry, *temp; + struct acc_hid_dev *hid; + struct list_head new_list, dead_list; + unsigned long flags; + + if (!dev) + return; + + INIT_LIST_HEAD(&new_list); + + spin_lock_irqsave(&dev->lock, flags); + + /* copy hids that are ready for initialization to new_list */ + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (hid->report_desc_offset == hid->report_desc_len) + list_move(&hid->list, &new_list); + } + + if (list_empty(&dev->dead_hid_list)) { + INIT_LIST_HEAD(&dead_list); + } else { + /* move all of dev->dead_hid_list to dead_list */ + dead_list.prev = dev->dead_hid_list.prev; + dead_list.next = dev->dead_hid_list.next; + dead_list.next->prev = &dead_list; + dead_list.prev->next = &dead_list; + INIT_LIST_HEAD(&dev->dead_hid_list); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + /* register new HID devices */ + list_for_each_safe(entry, temp, &new_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (acc_hid_init(hid)) { + pr_err("can't add HID device %p\n", hid); + acc_hid_delete(hid); + } else { + spin_lock_irqsave(&dev->lock, flags); + list_move(&hid->list, &dev->hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + } + } + + /* remove dead HID devices */ + list_for_each_safe(entry, temp, &dead_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + if (hid->hid) + hid_destroy_device(hid->hid); + acc_hid_delete(hid); + } + + put_acc_dev(dev); +} + +static int acc_function_set_alt(struct usb_function *f, + unsigned int intf, unsigned int alt) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + dev->online = true; + dev->disconnected = false; /* if online then not disconnected */ + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void acc_function_disable(struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + dev->disconnected = true; + dev->online = false; /* so now need to clear online flag here too */ + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + DBG(cdev, "%s disabled\n", dev->function.name); +} + +static int acc_init(void) +{ + struct acc_dev *dev; + int ret; + + mutex_lock(&acc_dev_instance_lock); + if (acc_dev_instance) { + mutex_unlock(&acc_dev_instance_lock); + return -EBUSY; + } + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + mutex_unlock(&acc_dev_instance_lock); + return -ENOMEM; + } + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->hid_list); + INIT_LIST_HEAD(&dev->new_hid_list); + INIT_LIST_HEAD(&dev->dead_hid_list); + INIT_DELAYED_WORK(&dev->start_work, acc_start_work); + INIT_WORK(&dev->hid_work, acc_hid_work); + INIT_WORK(&dev->getprotocol_work, acc_getprotocol_work); + INIT_WORK(&dev->sendstring_work, acc_sendstring_work); + + ret = misc_register(&acc_device); + if (ret) + goto err_free_dev; + + kref_init(&dev->kref); + acc_dev_instance = dev; + mutex_unlock(&acc_dev_instance_lock); + return 0; + +err_free_dev: + kfree(dev); + mutex_unlock(&acc_dev_instance_lock); + pr_err("USB accessory gadget driver failed to initialize\n"); + return ret; +} + +void android_acc_disconnect(void) +{ + struct acc_dev *dev = get_acc_dev(); + + if (!dev) + return; + + /* unregister all HID devices if USB is disconnected */ + kill_all_hid_devices(dev); + put_acc_dev(dev); +} +EXPORT_SYMBOL_GPL(android_acc_disconnect); + +static void acc_cleanup(void) +{ + struct acc_dev *dev = get_acc_dev(); + + misc_deregister(&acc_device); + put_acc_dev(dev); + put_acc_dev(dev); /* Pairs with kref_init() in acc_init() */ +} +static struct acc_instance *to_acc_instance(struct config_item *item) +{ + return container_of(to_config_group(item), struct acc_instance, + func_inst.group); +} + +static void acc_attr_release(struct config_item *item) +{ + struct acc_instance *fi_acc = to_acc_instance(item); + + usb_put_function_instance(&fi_acc->func_inst); +} + +static struct configfs_item_operations acc_item_ops = { + .release = acc_attr_release, +}; + +static struct config_item_type acc_func_type = { + .ct_item_ops = &acc_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct acc_instance *to_fi_acc(struct usb_function_instance *fi) +{ + return container_of(fi, struct acc_instance, func_inst); +} + +static int acc_set_inst_name(struct usb_function_instance *fi, + const char *name) +{ + struct acc_instance *fi_acc; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + fi_acc = to_fi_acc(fi); + fi_acc->name = ptr; + return 0; +} + +static void acc_free_inst(struct usb_function_instance *fi) +{ + struct acc_instance *fi_acc; + + fi_acc = to_fi_acc(fi); + kfree(fi_acc->name); + acc_cleanup(); +} + +static struct usb_function_instance *acc_alloc_inst(void) +{ + struct acc_instance *fi_acc; + int err; + + fi_acc = kzalloc(sizeof(*fi_acc), GFP_KERNEL); + if (!fi_acc) + return ERR_PTR(-ENOMEM); + fi_acc->func_inst.set_inst_name = acc_set_inst_name; + fi_acc->func_inst.free_func_inst = acc_free_inst; + + err = acc_init(); + if (err) { + kfree(fi_acc); + return ERR_PTR(err); + } + + config_group_init_type_name(&fi_acc->func_inst.group, + "", &acc_func_type); + return &fi_acc->func_inst; +} + +static void acc_free(struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + + put_acc_dev(dev); +} + +bool __acc_req_match(const struct usb_ctrlrequest *ctrl) +{ + struct acc_dev *dev = get_acc_dev(); + u8 bRequestType = ctrl->bRequestType; + u8 bRequest = ctrl->bRequest; + bool ret = false; + + /* + * If instance is not created which is the case in power off charging + * mode, dev will be NULL. Hence return error if it is the case. + */ + if (!dev) + return false; + + if (bRequestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) { + switch (bRequest) { + case ACCESSORY_START: + case ACCESSORY_SEND_STRING: + case ACCESSORY_SET_AUDIO_MODE: + case ACCESSORY_REGISTER_HID: + case ACCESSORY_UNREGISTER_HID: + case ACCESSORY_SET_HID_REPORT_DESC: + case ACCESSORY_SEND_HID_EVENT: + ret = true; + break; + default: + ret = false; + } + } else if ((bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR)) && + (bRequest == ACCESSORY_GET_PROTOCOL)) + ret = true; + + put_acc_dev(dev); + return ret; +} + +static bool acc_req_match(struct usb_function *f, + const struct usb_ctrlrequest *ctrl, bool config) +{ + return __acc_req_match(ctrl); +} + +bool android_acc_req_match_composite(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + return __acc_req_match(ctrl); +} +EXPORT_SYMBOL_GPL(android_acc_req_match_composite); + +static int __acc_setup(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct acc_dev *dev = get_acc_dev(); + int value = -EOPNOTSUPP; + struct acc_hid_dev *hid; + int offset; + u8 bRequestType = ctrl->bRequestType; + u8 bRequest = ctrl->bRequest; + u16 wIndex = le16_to_cpu(ctrl->wIndex); + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + unsigned long flags; + + /* + * If instance is not created which is the case in power off charging + * mode, dev will be NULL. Hence return error if it is the case. + */ + if (!dev) + return -ENODEV; + + if (bRequestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) { + if (bRequest == ACCESSORY_START) { + dev->start_requested = 1; + schedule_delayed_work( + &dev->start_work, msecs_to_jiffies(10)); + value = 0; + cdev->req->complete = acc_complete_setup_noop; + } else if (bRequest == ACCESSORY_SEND_STRING) { + schedule_work(&dev->sendstring_work); + dev->string_index = wIndex; + cdev->gadget->ep0->driver_data = dev; + cdev->req->complete = acc_complete_set_string; + value = wLength; + } else if (bRequest == ACCESSORY_SET_AUDIO_MODE && + wIndex == 0 && wLength == 0) { + dev->audio_mode = wValue; + cdev->req->complete = acc_complete_setup_noop; + value = 0; + } else if (bRequest == ACCESSORY_REGISTER_HID) { + cdev->req->complete = acc_complete_setup_noop; + value = acc_register_hid(dev, wValue, wIndex); + } else if (bRequest == ACCESSORY_UNREGISTER_HID) { + cdev->req->complete = acc_complete_setup_noop; + value = acc_unregister_hid(dev, wValue); + } else if (bRequest == ACCESSORY_SET_HID_REPORT_DESC) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get_locked(&dev->new_hid_list, wValue); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + offset = wIndex; + if (offset != hid->report_desc_offset + || offset + wLength > hid->report_desc_len) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_set_hid_report_desc; + value = wLength; + } else if (bRequest == ACCESSORY_SEND_HID_EVENT) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get_locked(&dev->hid_list, wValue); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_send_hid_event; + value = wLength; + } + } else if (bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { + if (bRequest == ACCESSORY_GET_PROTOCOL) { + schedule_work(&dev->getprotocol_work); + *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; + value = sizeof(u16); + cdev->req->complete = acc_complete_setup_noop; + /* clear strings left over from a previous session */ + memset(dev->manufacturer, 0, + sizeof(dev->manufacturer)); + memset(dev->model, 0, sizeof(dev->model)); + memset(dev->description, 0, sizeof(dev->description)); + memset(dev->version, 0, sizeof(dev->version)); + memset(dev->uri, 0, sizeof(dev->uri)); + memset(dev->serial, 0, sizeof(dev->serial)); + dev->start_requested = 0; + dev->audio_mode = 0; + } + } + + if (value >= 0) { + cdev->req->zero = 0; + cdev->req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "setup response queue error\n"); + } + +err: + if (value == -EOPNOTSUPP) + DBG(cdev, + "unknown class-specific ctrl req %02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + wValue, wIndex, wLength); + put_acc_dev(dev); + return value; +} + +int android_acc_setup_composite(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (w_length > USB_COMP_EP0_BUFSIZ) { + if (ctrl->bRequestType & USB_DIR_IN) { + /* + * Cast away the const, we are going to overwrite on + * purpose. + */ + __le16 *temp = (__le16 *)&ctrl->wLength; + + *temp = cpu_to_le16(USB_COMP_EP0_BUFSIZ); + w_length = USB_COMP_EP0_BUFSIZ; + } else { + return -EINVAL; + } + } + return __acc_setup(cdev, ctrl); +} +EXPORT_SYMBOL_GPL(android_acc_setup_composite); + +static int acc_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + if (f->config != NULL && f->config->cdev != NULL) + return __acc_setup(f->config->cdev, ctrl); + else + return -1; +} + +static struct usb_function *acc_alloc(struct usb_function_instance *fi) +{ + struct acc_dev *dev = get_acc_dev(); + + dev->function.name = "accessory"; + dev->function.strings = acc_strings; + dev->function.fs_descriptors = fs_acc_descs; + dev->function.hs_descriptors = hs_acc_descs; + dev->function.ss_descriptors = ss_acc_descs; + dev->function.ssp_descriptors = ssp_acc_descs; + dev->function.bind = acc_function_bind_configfs; + dev->function.unbind = acc_function_unbind; + dev->function.set_alt = acc_function_set_alt; + dev->function.disable = acc_function_disable; + dev->function.free_func = acc_free; + dev->function.req_match = acc_req_match; + dev->function.setup = acc_setup; + + return &dev->function; +} +DECLARE_USB_FUNCTION_INIT(accessory, acc_alloc_inst, acc_alloc); +MODULE_LICENSE("GPL"); diff --git a/include/linux/usb/android_accessory.h b/include/linux/usb/android_accessory.h new file mode 100644 index 000000000000..384ec3d02b48 --- /dev/null +++ b/include/linux/usb/android_accessory.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2024 Google LLC + */ +#ifndef __ANDROID_ACCESSORY_H +#define __ANDROID_ACCESSORY_H + +#include +#include + +#ifdef CONFIG_ANDROID_USB_CONFIGFS_F_ACC + +/** + * android_acc_req_match_composite - used to check if the android accessory + * driver can handle a usb_ctrlrequest + * @cdev - the usb_composite_dev instance associated with the incoming ctrl + * request + * @ctrl - a usb_ctrlrequest for the accessory driver to check + * + * This function should be called in composite_setup() after other req_match + * checks have failed and the usb_ctrlrequest is still unhandled. + * + * The reason this must be implemented instead of the standard req_match + * interface is that the accessory function does not get bound to a config + * by userspace until a connected device sends the ACCESSORY_START control + * request, and therefore the composite driver does not know about f_accessory + * yet we need to check for the control requests. + * + * Returns: true if the accessory driver can handle the request, false if not + */ +bool android_acc_req_match_composite(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl); + +/** + * android_acc_setup_composite - function for the f_accessory driver to handle + * usb_ctrlrequests + * @cdev - the usb_composite_dev instance associated with the incoming ctrl + * request. + * @ctrl - a usb_ctrlrequest to be handled by the f_accessory driver. + * + * This function should be called in composite_setup() after successfully + * checking for ctrl request support in android_acc_req_match_composite(). + * + * The reason this additional api must be defined is due to the fact that + * userspace does not bind the f_accessory instance to a gadget config until + * after receiving an ACCESSORY_START control request from a connected + * accessory device, and therefore we have a circular dependency. The addition + * of this allows compatibility with the existing Android userspace, but is + * not ideal and should be refactored in the future. + * + * Returns: Negative error value upon failure, >=0 upon successful handdling + * of the usb_ctrlrequest aligned with the standard composite function driver + * setup() api. + */ +int android_acc_setup_composite(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl); + +/** + * android_acc_disconnect - used to cleanup the accessory function + * and update the connection state on device disconnection. + * + * This should be called in the composite driver's __composite_disconnect() + * path to notify the accessory function of device disconnect. This is + * required because the accessory function exists outside of a gadget config + * and therefore the composite driver's standard cleanup paths may not apply. + */ +void android_acc_disconnect(void); + +#else + +static inline bool android_acc_req_match_composite(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + return false; +} + +static inline int android_acc_setup_composite(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + return 0; +} + +static inline void android_acc_disconnect(void) +{ +} +#endif /* CONFIG_ANDROID_USB_CONFIGFS_F_ACC */ +#endif /* __ANDROID_ACCESSORY_H */ diff --git a/include/uapi/linux/usb/android_accessory.h b/include/uapi/linux/usb/android_accessory.h new file mode 100644 index 000000000000..ea48138cc763 --- /dev/null +++ b/include/uapi/linux/usb/android_accessory.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright 2011-2024 Google LLC + * + */ + +#ifndef _UAPI_ANDROID_ACCESSORY_H +#define _UAPI_ANDROID_ACCESSORY_H + +/* Use Google Vendor ID when in accessory mode */ +#define USB_ACCESSORY_VENDOR_ID 0x18D1 + +/* Product ID to use when in accessory mode */ +#define USB_ACCESSORY_PRODUCT_ID 0x2D00 + +/* Product ID to use when in accessory mode and adb is enabled */ +#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01 + +/* Indexes for strings sent by the host via ACCESSORY_SEND_STRING */ +#define ACCESSORY_STRING_MANUFACTURER 0 +#define ACCESSORY_STRING_MODEL 1 +#define ACCESSORY_STRING_DESCRIPTION 2 +#define ACCESSORY_STRING_VERSION 3 +#define ACCESSORY_STRING_URI 4 +#define ACCESSORY_STRING_SERIAL 5 + +#define ACC_STRING_SIZE 256 + +/* Control request for retrieving device's protocol version + * + * requestType: USB_DIR_IN | USB_TYPE_VENDOR + * request: ACCESSORY_GET_PROTOCOL + * value: 0 + * index: 0 + * data: version number (16 bits little endian) + * 1 for original accessory support + * 2 adds HID and device to host audio support + */ +#define ACCESSORY_GET_PROTOCOL 51 + +/* Control request for host to send a string to the device + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SEND_STRING + * value: 0 + * index: string ID + * data: zero terminated UTF8 string + * + * The device can later retrieve these strings via the + * ACCESSORY_GET_STRING_* ioctls + */ +#define ACCESSORY_SEND_STRING 52 + +/* Control request for starting device in accessory mode. + * The host sends this after setting all its strings to the device. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_START + * value: 0 + * index: 0 + * data: none + */ +#define ACCESSORY_START 53 + +/* Control request for registering a HID device. + * Upon registering, a unique ID is sent by the accessory in the + * value parameter. This ID will be used for future commands for + * the device + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_REGISTER_HID_DEVICE + * value: Accessory assigned ID for the HID device + * index: total length of the HID report descriptor + * data: none + */ +#define ACCESSORY_REGISTER_HID 54 + +/* Control request for unregistering a HID device. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_REGISTER_HID + * value: Accessory assigned ID for the HID device + * index: 0 + * data: none + */ +#define ACCESSORY_UNREGISTER_HID 55 + +/* Control request for sending the HID report descriptor. + * If the HID descriptor is longer than the endpoint zero max packet size, + * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC + * commands. The data for the descriptor must be sent sequentially + * if multiple packets are needed. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SET_HID_REPORT_DESC + * value: Accessory assigned ID for the HID device + * index: offset of data in descriptor + * (needed when HID descriptor is too big for one packet) + * data: the HID report descriptor + */ +#define ACCESSORY_SET_HID_REPORT_DESC 56 + +/* Control request for sending HID events. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SEND_HID_EVENT + * value: Accessory assigned ID for the HID device + * index: 0 + * data: the HID report for the event + */ +#define ACCESSORY_SEND_HID_EVENT 57 + +/* Control request for setting the audio mode. + * + * DEPRECATED! Do not implement a new device which uses this. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SET_AUDIO_MODE + * value: 0 - no audio + * 1 - device to host, 44100 16-bit stereo PCM + * index: 0 + * data: none + */ +#define ACCESSORY_SET_AUDIO_MODE 58 + +/* ioctls for retrieving strings set by the host */ +#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[ACC_STRING_SIZE]) +#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[ACC_STRING_SIZE]) +#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[ACC_STRING_SIZE]) +#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[ACC_STRING_SIZE]) +#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[ACC_STRING_SIZE]) +#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[ACC_STRING_SIZE]) +/* returns 1 if there is a start request pending */ +#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) +/* returns audio mode (set via the ACCESSORY_SET_AUDIO_MODE control request) */ +#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) + +#endif /* _UAPI_ANDROID__ACCESSORY_H */