root/drivers/usb/gadget/legacy/hid.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * hid.c -- HID Composite driver
 *
 * Based on multi.c
 *
 * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com>
 */


#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb/composite.h>
#include <linux/usb/g_hid.h>

#define DRIVER_DESC             "HID Gadget"
#define DRIVER_VERSION          "2010/03/16"

#include "u_hid.h"

/*-------------------------------------------------------------------------*/

#define HIDG_VENDOR_NUM         0x0525  /* XXX NetChip */
#define HIDG_PRODUCT_NUM        0xa4ac  /* Linux-USB HID gadget */

/*-------------------------------------------------------------------------*/

struct hidg_func_node {
        struct usb_function_instance *fi;
        struct usb_function *f;
        struct list_head node;
        struct hidg_func_descriptor *func;
};

static LIST_HEAD(hidg_func_list);

/*-------------------------------------------------------------------------*/
USB_GADGET_COMPOSITE_OPTIONS();

static struct usb_device_descriptor device_desc = {
        .bLength =              sizeof device_desc,
        .bDescriptorType =      USB_DT_DEVICE,

        /* .bcdUSB = DYNAMIC */

        /* .bDeviceClass =              USB_CLASS_COMM, */
        /* .bDeviceSubClass =   0, */
        /* .bDeviceProtocol =   0, */
        .bDeviceClass =         USB_CLASS_PER_INTERFACE,
        .bDeviceSubClass =      0,
        .bDeviceProtocol =      0,
        /* .bMaxPacketSize0 = f(hardware) */

        /* Vendor and product id can be overridden by module parameters.  */
        .idVendor =             cpu_to_le16(HIDG_VENDOR_NUM),
        .idProduct =            cpu_to_le16(HIDG_PRODUCT_NUM),
        /* .bcdDevice = f(hardware) */
        /* .iManufacturer = DYNAMIC */
        /* .iProduct = DYNAMIC */
        /* NO SERIAL NUMBER */
        .bNumConfigurations =   1,
};

static const struct usb_descriptor_header *otg_desc[2];

/* string IDs are assigned dynamically */
static struct usb_string strings_dev[] = {
        [USB_GADGET_MANUFACTURER_IDX].s = "",
        [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC,
        [USB_GADGET_SERIAL_IDX].s = "",
        {  } /* 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,
};



/****************************** Configurations ******************************/

static int do_config(struct usb_configuration *c)
{
        struct hidg_func_node *e, *n;
        int status = 0;

        if (gadget_is_otg(c->cdev->gadget)) {
                c->descriptors = otg_desc;
                c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
        }

        list_for_each_entry(e, &hidg_func_list, node) {
                e->f = usb_get_function(e->fi);
                if (IS_ERR(e->f)) {
                        status = PTR_ERR(e->f);
                        goto put;
                }
                status = usb_add_function(c, e->f);
                if (status < 0) {
                        usb_put_function(e->f);
                        goto put;
                }
        }

        return 0;
put:
        list_for_each_entry(n, &hidg_func_list, node) {
                if (n == e)
                        break;
                usb_remove_function(c, n->f);
                usb_put_function(n->f);
        }
        return status;
}

static struct usb_configuration config_driver = {
        .label                  = "HID Gadget",
        .bConfigurationValue    = 1,
        /* .iConfiguration = DYNAMIC */
        .bmAttributes           = USB_CONFIG_ATT_SELFPOWER,
};

/****************************** Gadget Bind ******************************/

static int hid_bind(struct usb_composite_dev *cdev)
{
        struct usb_gadget *gadget = cdev->gadget;
        struct hidg_func_node *n = NULL, *m, *iter_n;
        struct f_hid_opts *hid_opts;
        int status, funcs;

        funcs = list_count_nodes(&hidg_func_list);
        if (!funcs)
                return -ENODEV;

        list_for_each_entry(iter_n, &hidg_func_list, node) {
                iter_n->fi = usb_get_function_instance("hid");
                if (IS_ERR(iter_n->fi)) {
                        status = PTR_ERR(iter_n->fi);
                        n = iter_n;
                        goto put;
                }
                hid_opts = container_of(iter_n->fi, struct f_hid_opts, func_inst);
                hid_opts->subclass = iter_n->func->subclass;
                hid_opts->protocol = iter_n->func->protocol;
                hid_opts->report_length = iter_n->func->report_length;
                hid_opts->report_desc_length = iter_n->func->report_desc_length;
                hid_opts->report_desc = iter_n->func->report_desc;
        }


        /* Allocate string descriptor numbers ... note that string
         * contents can be overridden by the composite_dev glue.
         */

        status = usb_string_ids_tab(cdev, strings_dev);
        if (status < 0)
                goto put;
        device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;
        device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;

        if (gadget_is_otg(gadget) && !otg_desc[0]) {
                struct usb_descriptor_header *usb_desc;

                usb_desc = usb_otg_descriptor_alloc(gadget);
                if (!usb_desc) {
                        status = -ENOMEM;
                        goto put;
                }
                usb_otg_descriptor_init(gadget, usb_desc);
                otg_desc[0] = usb_desc;
                otg_desc[1] = NULL;
        }

        /* register our configuration */
        status = usb_add_config(cdev, &config_driver, do_config);
        if (status < 0)
                goto free_otg_desc;

        usb_composite_overwrite_options(cdev, &coverwrite);
        dev_info(&gadget->dev, DRIVER_DESC ", version: " DRIVER_VERSION "\n");

        return 0;

free_otg_desc:
        kfree(otg_desc[0]);
        otg_desc[0] = NULL;
put:
        list_for_each_entry(m, &hidg_func_list, node) {
                if (m == n)
                        break;
                usb_put_function_instance(m->fi);
        }
        return status;
}

static int hid_unbind(struct usb_composite_dev *cdev)
{
        struct hidg_func_node *n;

        list_for_each_entry(n, &hidg_func_list, node) {
                usb_put_function(n->f);
                usb_put_function_instance(n->fi);
        }

        kfree(otg_desc[0]);
        otg_desc[0] = NULL;

        return 0;
}

static int hidg_plat_driver_probe(struct platform_device *pdev)
{
        struct hidg_func_descriptor *func = dev_get_platdata(&pdev->dev);
        struct hidg_func_node *entry;

        if (!func) {
                dev_err(&pdev->dev, "Platform data missing\n");
                return -ENODEV;
        }

        entry = kzalloc_obj(*entry);
        if (!entry)
                return -ENOMEM;

        entry->func = func;
        list_add_tail(&entry->node, &hidg_func_list);

        return 0;
}

static void hidg_plat_driver_remove(struct platform_device *pdev)
{
        struct hidg_func_node *e, *n;

        list_for_each_entry_safe(e, n, &hidg_func_list, node) {
                list_del(&e->node);
                kfree(e);
        }
}


/****************************** Some noise ******************************/


static struct usb_composite_driver hidg_driver = {
        .name           = "g_hid",
        .dev            = &device_desc,
        .strings        = dev_strings,
        .max_speed      = USB_SPEED_HIGH,
        .bind           = hid_bind,
        .unbind         = hid_unbind,
};

static struct platform_driver hidg_plat_driver = {
        .remove         = hidg_plat_driver_remove,
        .driver         = {
                .name   = "hidg",
        },
};


MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR("Fabien Chouteau, Peter Korsgaard");
MODULE_LICENSE("GPL");

static int __init hidg_init(void)
{
        int status;

        status = platform_driver_probe(&hidg_plat_driver,
                                hidg_plat_driver_probe);
        if (status < 0)
                return status;

        status = usb_composite_probe(&hidg_driver);
        if (status < 0)
                platform_driver_unregister(&hidg_plat_driver);

        return status;
}
module_init(hidg_init);

static void __exit hidg_cleanup(void)
{
        usb_composite_unregister(&hidg_driver);
        platform_driver_unregister(&hidg_plat_driver);
}
module_exit(hidg_cleanup);