usb: gadget: hid: add configfs support
authorAndrzej Pietrasiewicz <andrzej.p@samsung.com>
Thu, 6 Nov 2014 10:12:03 +0000 (11:12 +0100)
committerFelipe Balbi <balbi@ti.com>
Thu, 6 Nov 2014 22:18:19 +0000 (16:18 -0600)
Make the hid function available for gadgets composed with configfs.

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
Documentation/ABI/testing/configfs-usb-gadget-hid [new file with mode: 0644]
Documentation/usb/gadget_hid.txt
drivers/usb/gadget/Kconfig
drivers/usb/gadget/function/f_hid.c
drivers/usb/gadget/function/u_hid.h

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-hid b/Documentation/ABI/testing/configfs-usb-gadget-hid
new file mode 100644 (file)
index 0000000..f12e00e
--- /dev/null
@@ -0,0 +1,11 @@
+What:          /config/usb-gadget/gadget/functions/hid.name
+Date:          Nov 2014
+KernelVersion: 3.19
+Description:
+               The attributes:
+
+               protocol        - HID protocol to use
+               report_desc     - blob corresponding to HID report descriptors
+                               except the data passed through /dev/hidg<N>
+               report_length   - HID report length
+               subclass        - HID device subclass to use
index 12696c2e43fb1391b52b09d4ca405cfd1d7bce9d..7a0fb8e16e27508d6b16baf8196f7575ae3b7b9f 100644 (file)
@@ -74,6 +74,13 @@ static struct platform_device my_hid = {
        You can add as many HID functions as you want, only limited by
        the amount of interrupt endpoints your gadget driver supports.
 
+Configuration with configfs
+
+       Instead of adding fake platform devices and drivers in order to pass
+       some data to the kernel, if HID is a part of a gadget composed with
+       configfs the hidg_func_descriptor.report_desc is passed to the kernel
+       by writing the appropriate stream of bytes to a configfs attribute.
+
 Send and receive HID reports
 
        HID reports can be sent/received using read/write on the
index ea2d7706db6cff1632fa811640a2483fb0c9f6ed..747ef53bda14ecb4392e9c295f698806b583341c 100644 (file)
@@ -413,6 +413,16 @@ config USB_CONFIGFS_F_MIDI
          connections can then be made on the gadget system, using
          ALSA's aconnect utility etc.
 
+config USB_CONFIGFS_F_HID
+       boolean "HID function"
+       depends on USB_CONFIGFS
+       select USB_F_HID
+       help
+         The HID function driver provides generic emulation of USB
+         Human Interface Devices (HID).
+
+         For more information, see Documentation/usb/gadget_hid.txt.
+
 source "drivers/usb/gadget/legacy/Kconfig"
 
 endchoice
index dfdb4327ef3ec5d6129297828ef1203d0cede2b0..56ca3fc815558c5df89ad797218622d2f62107b4 100644 (file)
@@ -690,6 +690,136 @@ static inline int hidg_get_minor(void)
        return ret;
 }
 
+static inline struct f_hid_opts *to_f_hid_opts(struct config_item *item)
+{
+       return container_of(to_config_group(item), struct f_hid_opts,
+                           func_inst.group);
+}
+
+CONFIGFS_ATTR_STRUCT(f_hid_opts);
+CONFIGFS_ATTR_OPS(f_hid_opts);
+
+static void hid_attr_release(struct config_item *item)
+{
+       struct f_hid_opts *opts = to_f_hid_opts(item);
+
+       usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations hidg_item_ops = {
+       .release        = hid_attr_release,
+       .show_attribute = f_hid_opts_attr_show,
+       .store_attribute = f_hid_opts_attr_store,
+};
+
+#define F_HID_OPT(name, prec, limit)                                   \
+static ssize_t f_hid_opts_##name##_show(struct f_hid_opts *opts, char *page)\
+{                                                                      \
+       int result;                                                     \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       result = sprintf(page, "%d\n", opts->name);                     \
+       mutex_unlock(&opts->lock);                                      \
+                                                                       \
+       return result;                                                  \
+}                                                                      \
+                                                                       \
+static ssize_t f_hid_opts_##name##_store(struct f_hid_opts *opts,      \
+                                        const char *page, size_t len)  \
+{                                                                      \
+       int ret;                                                        \
+       u##prec num;                                                    \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       if (opts->refcnt) {                                             \
+               ret = -EBUSY;                                           \
+               goto end;                                               \
+       }                                                               \
+                                                                       \
+       ret = kstrtou##prec(page, 0, &num);                             \
+       if (ret)                                                        \
+               goto end;                                               \
+                                                                       \
+       if (num > limit) {                                              \
+               ret = -EINVAL;                                          \
+               goto end;                                               \
+       }                                                               \
+       opts->name = num;                                               \
+       ret = len;                                                      \
+                                                                       \
+end:                                                                   \
+       mutex_unlock(&opts->lock);                                      \
+       return ret;                                                     \
+}                                                                      \
+                                                                       \
+static struct f_hid_opts_attribute f_hid_opts_##name =                 \
+       __CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, f_hid_opts_##name##_show,\
+                       f_hid_opts_##name##_store)
+
+F_HID_OPT(subclass, 8, 255);
+F_HID_OPT(protocol, 8, 255);
+F_HID_OPT(report_length, 16, 65536);
+
+static ssize_t f_hid_opts_report_desc_show(struct f_hid_opts *opts, char *page)
+{
+       int result;
+
+       mutex_lock(&opts->lock);
+       result = opts->report_desc_length;
+       memcpy(page, opts->report_desc, opts->report_desc_length);
+       mutex_unlock(&opts->lock);
+
+       return result;
+}
+
+static ssize_t f_hid_opts_report_desc_store(struct f_hid_opts *opts,
+                                           const char *page, size_t len)
+{
+       int ret = -EBUSY;
+       char *d;
+
+       mutex_lock(&opts->lock);
+
+       if (opts->refcnt)
+               goto end;
+       if (len > PAGE_SIZE) {
+               ret = -ENOSPC;
+               goto end;
+       }
+       d = kmemdup(page, len, GFP_KERNEL);
+       if (!d) {
+               ret = -ENOMEM;
+               goto end;
+       }
+       kfree(opts->report_desc);
+       opts->report_desc = d;
+       opts->report_desc_length = len;
+       opts->report_desc_alloc = true;
+       ret = len;
+end:
+       mutex_unlock(&opts->lock);
+       return ret;
+}
+
+static struct f_hid_opts_attribute f_hid_opts_report_desc =
+       __CONFIGFS_ATTR(report_desc, S_IRUGO | S_IWUSR,
+                       f_hid_opts_report_desc_show,
+                       f_hid_opts_report_desc_store);
+
+static struct configfs_attribute *hid_attrs[] = {
+       &f_hid_opts_subclass.attr,
+       &f_hid_opts_protocol.attr,
+       &f_hid_opts_report_length.attr,
+       &f_hid_opts_report_desc.attr,
+       NULL,
+};
+
+static struct config_item_type hid_func_type = {
+       .ct_item_ops    = &hidg_item_ops,
+       .ct_attrs       = hid_attrs,
+       .ct_owner       = THIS_MODULE,
+};
+
 static inline void hidg_put_minor(int minor)
 {
        ida_simple_remove(&hidg_ida, minor);
@@ -724,7 +854,7 @@ static struct usb_function_instance *hidg_alloc_inst(void)
        opts = kzalloc(sizeof(*opts), GFP_KERNEL);
        if (!opts)
                return ERR_PTR(-ENOMEM);
-
+       mutex_init(&opts->lock);
        opts->func_inst.free_func_inst = hidg_free_inst;
        ret = &opts->func_inst;
 
@@ -746,6 +876,7 @@ static struct usb_function_instance *hidg_alloc_inst(void)
                if (idr_is_empty(&hidg_ida.idr))
                        ghid_cleanup();
        }
+       config_group_init_type_name(&opts->func_inst.group, "", &hid_func_type);
 
 unlock:
        mutex_unlock(&hidg_ida_lock);
@@ -755,10 +886,15 @@ unlock:
 static void hidg_free(struct usb_function *f)
 {
        struct f_hidg *hidg;
+       struct f_hid_opts *opts;
 
        hidg = func_to_hidg(f);
+       opts = container_of(f->fi, struct f_hid_opts, func_inst);
        kfree(hidg->report_desc);
        kfree(hidg);
+       mutex_lock(&opts->lock);
+       --opts->refcnt;
+       mutex_unlock(&opts->lock);
 }
 
 static void hidg_unbind(struct usb_configuration *c, struct usb_function *f)
@@ -789,6 +925,9 @@ struct usb_function *hidg_alloc(struct usb_function_instance *fi)
 
        opts = container_of(fi, struct f_hid_opts, func_inst);
 
+       mutex_lock(&opts->lock);
+       ++opts->refcnt;
+
        hidg->minor = opts->minor;
        hidg->bInterfaceSubClass = opts->subclass;
        hidg->bInterfaceProtocol = opts->protocol;
@@ -800,10 +939,13 @@ struct usb_function *hidg_alloc(struct usb_function_instance *fi)
                                            GFP_KERNEL);
                if (!hidg->report_desc) {
                        kfree(hidg);
+                       mutex_unlock(&opts->lock);
                        return ERR_PTR(-ENOMEM);
                }
        }
 
+       mutex_unlock(&opts->lock);
+
        hidg->func.name    = "hid";
        hidg->func.bind    = hidg_bind;
        hidg->func.unbind  = hidg_unbind;
index 3edfc9567ab7f55b49e5058eb05fce28e97087b3..aaa0e368a159695a94a00d5d792a44c8bf635d1d 100644 (file)
@@ -27,6 +27,13 @@ struct f_hid_opts {
        unsigned short                  report_desc_length;
        unsigned char                   *report_desc;
        bool                            report_desc_alloc;
+
+       /*
+        * Protect the data form concurrent access by read/write
+        * and create symlink/remove symlink.
+        */
+        struct mutex                   lock;
+        int                            refcnt;
 };
 
 int ghid_setup(struct usb_gadget *g, int count);