diff options
Diffstat (limited to 'drivers/usb/gadget/legacy')
-rw-r--r-- | drivers/usb/gadget/legacy/Makefile | 8 | ||||
-rw-r--r-- | drivers/usb/gadget/legacy/multi.c | 336 | ||||
-rw-r--r-- | drivers/usb/gadget/legacy/serial.c | 282 |
3 files changed, 626 insertions, 0 deletions
diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile new file mode 100644 index 0000000000..5d5382a673 --- /dev/null +++ b/drivers/usb/gadget/legacy/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +ccflags-y := -I$(srctree)/drivers/usb/gadget/ +ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/ +ccflags-y += -I$(srctree)/drivers/usb/gadget/function/ + +obj-$(CONFIG_USB_GADGET_SERIAL) += serial.o +obj-$(CONFIG_USB_GADGET) += multi.o diff --git a/drivers/usb/gadget/legacy/multi.c b/drivers/usb/gadget/legacy/multi.c new file mode 100644 index 0000000000..7046a529b1 --- /dev/null +++ b/drivers/usb/gadget/legacy/multi.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * multi.c -- Multifunction Composite driver + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + */ + +#include <common.h> +#include <linux/usb/gadget-multi.h> +#include <linux/err.h> + +#include "u_serial.h" + +#define DRIVER_DESC "Multifunction Composite Gadget" + +/***************************** Device Descriptor ****************************/ + +#define MULTI_VENDOR_NUM 0x1d6b /* Linux Foundation */ +#define MULTI_PRODUCT_NUM 0x0104 /* Multifunction Composite Gadget */ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + + .bDeviceClass = USB_CLASS_MISC /* 0xEF */, + .bDeviceSubClass = 2, + .bDeviceProtocol = 1, +}; + +#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = "", + [USB_GADGET_SERIAL_IDX].s = "", + [STRING_DESCRIPTION_IDX].s = "Multifunction Composite Gadget", + { } /* end of list */ +}; + +static struct usb_gadget_strings *dev_strings[] = { + &(struct usb_gadget_strings){ + .language = 0x0409, /* en-us */ + .strings = strings_dev, + }, + NULL, +}; + +static struct usb_function_instance *fi_acm; +static struct usb_function *f_acm; +static struct usb_function_instance *fi_dfu; +static struct usb_function *f_dfu; +static struct usb_function_instance *fi_fastboot; +static struct usb_function *f_fastboot; +static struct usb_function_instance *fi_ums; +static struct usb_function *f_ums; + +static struct usb_configuration config = { + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +struct f_multi_opts *gadget_multi_opts; + +static int multi_bind_acm(struct usb_composite_dev *cdev) +{ + int ret; + + fi_acm = usb_get_function_instance("acm"); + if (IS_ERR(fi_acm)) { + ret = PTR_ERR(fi_acm); + fi_acm = NULL; + return ret; + } + + f_acm = usb_get_function(fi_acm); + if (IS_ERR(f_acm)) { + ret = PTR_ERR(f_acm); + f_acm = NULL; + return ret; + } + + return usb_add_function(&config, f_acm); +} + +static int multi_bind_dfu(struct usb_composite_dev *cdev) +{ + int ret; + struct f_dfu_opts *opts; + + fi_dfu = usb_get_function_instance("dfu"); + if (IS_ERR(fi_dfu)) { + ret = PTR_ERR(fi_dfu); + fi_dfu = NULL; + return ret; + } + + opts = container_of(fi_dfu, struct f_dfu_opts, func_inst); + opts->files = gadget_multi_opts->dfu_opts.files; + + f_dfu = usb_get_function(fi_dfu); + if (IS_ERR(f_dfu)) { + ret = PTR_ERR(f_dfu); + f_dfu = NULL; + return ret; + } + + return usb_add_function(&config, f_dfu); +} + +static int multi_bind_fastboot(struct usb_composite_dev *cdev) +{ + int ret; + struct f_fastboot_opts *opts; + + fi_fastboot = usb_get_function_instance("fastboot"); + if (IS_ERR(fi_fastboot)) { + ret = PTR_ERR(fi_fastboot); + fi_fastboot = NULL; + return ret; + } + + opts = container_of(fi_fastboot, struct f_fastboot_opts, func_inst); + opts->common = gadget_multi_opts->fastboot_opts; + + f_fastboot = usb_get_function(fi_fastboot); + if (IS_ERR(f_fastboot)) { + ret = PTR_ERR(f_fastboot); + f_fastboot = NULL; + return ret; + } + + return usb_add_function(&config, f_fastboot); +} + +static bool fastboot_has_exports(struct f_multi_opts *opts) +{ + return !file_list_empty(opts->fastboot_opts.files) || + opts->fastboot_opts.export_bbu; +} + +static int multi_bind_ums(struct usb_composite_dev *cdev) +{ + int ret; + struct f_ums_opts *opts; + + fi_ums = usb_get_function_instance("ums"); + if (IS_ERR(fi_ums)) { + ret = PTR_ERR(fi_ums); + fi_ums = NULL; + return ret; + } + + opts = container_of(fi_ums, struct f_ums_opts, func_inst); + opts->files = gadget_multi_opts->ums_opts.files; + + f_ums = usb_get_function(fi_ums); + if (IS_ERR(f_ums)) { + ret = PTR_ERR(f_ums); + f_ums = NULL; + return ret; + } + + return usb_add_function(&config, f_ums); +} + +static int multi_unbind(struct usb_composite_dev *cdev) +{ + if (gadget_multi_opts->create_acm) { + usb_put_function(f_acm); + usb_put_function_instance(fi_acm); + } + + if (gadget_multi_opts->ums_opts.files) { + usb_put_function(f_ums); + usb_put_function_instance(fi_ums); + } + + if (gadget_multi_opts->dfu_opts.files) { + usb_put_function(f_dfu); + usb_put_function_instance(fi_dfu); + } + + if (fastboot_has_exports(gadget_multi_opts)) { + usb_put_function(f_fastboot); + usb_put_function_instance(fi_fastboot); + } + + return 0; +} + +static int multi_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int ret; + + /* allocate string IDs */ + ret = usb_string_ids_tab(cdev, strings_dev); + if (ret < 0) + return ret; + + if (gadget->vendor_id && gadget->product_id) { + device_desc.idVendor = cpu_to_le16(gadget->vendor_id); + device_desc.idProduct = cpu_to_le16(gadget->product_id); + } else { + device_desc.idVendor = cpu_to_le16(MULTI_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(MULTI_PRODUCT_NUM); + } + + strings_dev[USB_GADGET_MANUFACTURER_IDX].s = gadget->manufacturer; + strings_dev[USB_GADGET_PRODUCT_IDX].s = gadget->productname; + strings_dev[USB_GADGET_SERIAL_IDX].s = gadget->serialnumber; + + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id; + + config.label = strings_dev[STRING_DESCRIPTION_IDX].s; + config.iConfiguration = strings_dev[STRING_DESCRIPTION_IDX].id; + + ret = usb_add_config_only(cdev, &config); + if (ret) + return ret; + + if (fastboot_has_exports(gadget_multi_opts)) { + printf("%s: creating Fastboot function\n", __func__); + ret = multi_bind_fastboot(cdev); + if (ret) + return ret; + } + + if (gadget_multi_opts->dfu_opts.files) { + printf("%s: creating DFU function\n", __func__); + ret = multi_bind_dfu(cdev); + if (ret) + goto unbind_fastboot; + } + + if (gadget_multi_opts->ums_opts.files) { + printf("%s: creating USB Mass Storage function\n", __func__); + ret = multi_bind_ums(cdev); + if (ret) + goto unbind_dfu; + } + + if (gadget_multi_opts->create_acm) { + printf("%s: creating ACM function\n", __func__); + ret = multi_bind_acm(cdev); + if (ret) + goto unbind_ums; + } + + usb_ep_autoconfig_reset(cdev->gadget); + + dev_info(&gadget->dev, DRIVER_DESC "\n"); + + return 0; +unbind_ums: + if (gadget_multi_opts->ums_opts.files) + usb_put_function_instance(fi_ums); +unbind_dfu: + if (gadget_multi_opts->dfu_opts.files) + usb_put_function_instance(fi_dfu); +unbind_fastboot: + if (fastboot_has_exports(gadget_multi_opts)) + usb_put_function_instance(fi_fastboot); + + return ret; +} + +static struct usb_composite_driver multi_driver = { + .name = "g_multi", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = multi_bind, + .unbind = multi_unbind, + .needs_serial = 1, +}; + + +int usb_multi_register(struct f_multi_opts *opts) +{ + int ret; + + if (gadget_multi_opts) { + pr_err("USB multi gadget already registered\n"); + return -EBUSY; + } + + gadget_multi_opts = opts; + + ret = usb_composite_probe(&multi_driver); + if (ret) { + usb_composite_unregister(&multi_driver); + gadget_multi_opts = NULL; + } + + return ret; +} + +void usb_multi_unregister(void) +{ + if (!gadget_multi_opts) + return; + + usb_composite_unregister(&multi_driver); + if (gadget_multi_opts->release) + gadget_multi_opts->release(gadget_multi_opts); + + gadget_multi_opts = NULL; +} + +unsigned usb_multi_count_functions(struct f_multi_opts *opts) +{ + unsigned count = 0; + + count += fastboot_has_exports(opts); + count += !file_list_empty(opts->dfu_opts.files); + count += !file_list_empty(opts->ums_opts.files); + count += opts->create_acm; + + return count; +} + +void usb_multi_opts_release(struct f_multi_opts *opts) +{ + file_list_free(opts->fastboot_opts.files); + file_list_free(opts->dfu_opts.files); + file_list_free(opts->ums_opts.files); + + free(opts); +} diff --git a/drivers/usb/gadget/legacy/serial.c b/drivers/usb/gadget/legacy/serial.c new file mode 100644 index 0000000000..46c41f6dea --- /dev/null +++ b/drivers/usb/gadget/legacy/serial.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * serial.c -- USB gadget serial driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + */ + +#include <common.h> +#include <errno.h> +#include <init.h> +#include <linux/err.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> +#include <linux/usb/usbserial.h> +#include <asm/byteorder.h> + +#include "u_serial.h" + + +/* Defines */ + +#define GS_VERSION_STR "v2.4" +#define GS_VERSION_NUM 0x2400 + +#define GS_LONG_NAME "Gadget Serial" +#define GS_VERSION_NAME GS_LONG_NAME " " GS_VERSION_STR + +/*-------------------------------------------------------------------------*/ +static struct usb_composite_overwrite coverwrite; + +/* Thanks to NetChip Technologies for donating this product ID. +* +* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! +* Instead: allocate your own, using normal USB-IF procedures. +*/ +#define GS_VENDOR_ID 0x0525 /* NetChip */ +#define GS_PRODUCT_ID 0xa4a6 /* Linux-USB Serial Gadget */ +#define GS_CDC_PRODUCT_ID 0xa4a7 /* ... as CDC-ACM */ +#define GS_CDC_OBEX_PRODUCT_ID 0xa4a9 /* ... as CDC-OBEX */ + +/* string IDs are assigned dynamically */ + +#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = GS_VERSION_NAME, + [USB_GADGET_SERIAL_IDX].s = "", + [STRING_DESCRIPTION_IDX].s = NULL /* updated; f(use_acm) */, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + /* .bDeviceClass = f(use_acm) */ + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + /* .idProduct = f(use_acm) */ + .bcdDevice = cpu_to_le16(GS_VERSION_NUM), + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/* Module */ +MODULE_DESCRIPTION(GS_VERSION_NAME); +MODULE_AUTHOR("Al Borchers"); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); + +static bool use_acm = true; + +static bool use_obex = false; + +static unsigned n_ports = 1; + +/*-------------------------------------------------------------------------*/ + +static struct usb_configuration serial_config_driver = { + /* .label = f(use_acm) */ + /* .bConfigurationValue = f(use_acm) */ + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +static struct usb_function_instance *fi_serial[MAX_U_SERIAL_PORTS]; +static struct usb_function *f_serial[MAX_U_SERIAL_PORTS]; + +static int serial_register_ports(struct usb_composite_dev *cdev, + struct usb_configuration *c, const char *f_name) +{ + int i; + int ret; + + ret = usb_add_config_only(cdev, c); + if (ret) + goto out; + + for (i = 0; i < n_ports; i++) { + + fi_serial[i] = usb_get_function_instance(f_name); + if (IS_ERR(fi_serial[i])) { + ret = PTR_ERR(fi_serial[i]); + goto fail; + } + + f_serial[i] = usb_get_function(fi_serial[i]); + if (IS_ERR(f_serial[i])) { + ret = PTR_ERR(f_serial[i]); + goto err_get_func; + } + + ret = usb_add_function(c, f_serial[i]); + if (ret) + goto err_add_func; + } + + return 0; + +err_add_func: + usb_put_function(f_serial[i]); +err_get_func: + usb_put_function_instance(fi_serial[i]); + +fail: + i--; + while (i >= 0) { + usb_remove_function(c, f_serial[i]); + usb_put_function(f_serial[i]); + usb_put_function_instance(fi_serial[i]); + i--; + } +out: + return ret; +} + +static int __init gs_bind(struct usb_composite_dev *cdev) +{ + int status; + struct usb_gadget *gadget = cdev->gadget; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + if (gadget->vendor_id && gadget->product_id) { + device_desc.idVendor = cpu_to_le16(gadget->vendor_id); + device_desc.idProduct = cpu_to_le16(gadget->product_id); + } else { + device_desc.idVendor = cpu_to_le16(GS_VENDOR_ID); + if (use_acm) + device_desc.idProduct = cpu_to_le16(GS_CDC_PRODUCT_ID); + else + device_desc.idProduct = cpu_to_le16(GS_PRODUCT_ID); + } + + strings_dev[USB_GADGET_MANUFACTURER_IDX].s = gadget->manufacturer; + strings_dev[USB_GADGET_PRODUCT_IDX].s = gadget->productname; + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + status = strings_dev[STRING_DESCRIPTION_IDX].id; + serial_config_driver.iConfiguration = status; + + if (gadget_is_otg(cdev->gadget)) { + serial_config_driver.descriptors = otg_desc; + serial_config_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + /* register our configuration */ + if (use_acm) { + status = serial_register_ports(cdev, &serial_config_driver, + "acm"); + usb_ep_autoconfig_reset(cdev->gadget); + } else if (use_obex) + status = serial_register_ports(cdev, &serial_config_driver, + "obex"); + else { + status = serial_register_ports(cdev, &serial_config_driver, + "gser"); + } + if (status < 0) + goto fail; + + usb_composite_overwrite_options(cdev, &coverwrite); + INFO(cdev, "%s\n", GS_VERSION_NAME); + + return 0; + +fail: + return status; +} + +static int gs_unbind(struct usb_composite_dev *cdev) +{ + int i; + + for (i = 0; i < n_ports; i++) { + usb_put_function(f_serial[i]); + usb_put_function_instance(fi_serial[i]); + } + return 0; +} + +static struct usb_composite_driver gserial_driver = { + .name = "g_serial", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = gs_bind, + .unbind = gs_unbind, +}; + +int usb_serial_register(struct usb_serial_pdata *pdata) +{ + /* We *could* export two configs; that'd be much cleaner... + * but neither of these product IDs was defined that way. + */ + + use_acm = pdata->acm; + + /* + * PXA CPU suffer a silicon bug which prevents them from being a + * compound device, forbiding the ACM configurations. + */ + + if (IS_ENABLED(CONFIG_ARCH_PXA2XX)) + use_acm = 0; + + if (use_acm) { + serial_config_driver.label = "CDC ACM config"; + serial_config_driver.bConfigurationValue = 2; + device_desc.bDeviceClass = USB_CLASS_COMM; + } else { + serial_config_driver.label = "Generic Serial config"; + serial_config_driver.bConfigurationValue = 1; + device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC; + } + + return usb_composite_probe(&gserial_driver); +} + +void usb_serial_unregister(void) +{ + usb_composite_unregister(&gserial_driver); +} |