Created
May 18, 2016 18:34
-
-
Save pelya/548e981523e5b737e80270340af49ccf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/android.c src/drivers/usb/gadget/android.c | |
--- src-old/drivers/usb/gadget/android.c 2014-06-26 15:26:34.000000000 +0300 | |
+++ src/drivers/usb/gadget/android.c 2016-05-18 21:16:56.637939774 +0300 | |
@@ -51,6 +51,9 @@ | |
#include "f_rndis.c" | |
#include "rndis.c" | |
#include "u_ether.c" | |
+#include "f_hid.h" | |
+#include "f_hid_android_keyboard.c" | |
+#include "f_hid_android_mouse.c" | |
MODULE_AUTHOR("Mike Lockwood"); | |
MODULE_DESCRIPTION("Android Composite USB Driver"); | |
@@ -1273,6 +1276,41 @@ | |
.attributes = audio_source_function_attributes, | |
}; | |
+static int hid_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) | |
+{ | |
+ return ghid_setup(cdev->gadget, 2); | |
+} | |
+ | |
+static void hid_function_cleanup(struct android_usb_function *f) | |
+{ | |
+ ghid_cleanup(); | |
+} | |
+ | |
+static int hid_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) | |
+{ | |
+ int ret; | |
+ printk(KERN_INFO "hid keyboard\n"); | |
+ ret = hidg_bind_config(c, &ghid_device_android_keyboard, 0); | |
+ if (ret) { | |
+ pr_info("%s: hid_function_bind_config keyboard failed: %d\n", __func__, ret); | |
+ return ret; | |
+ } | |
+ printk(KERN_INFO "hid mouse\n"); | |
+ ret = hidg_bind_config(c, &ghid_device_android_mouse, 1); | |
+ if (ret) { | |
+ pr_info("%s: hid_function_bind_config mouse failed: %d\n", __func__, ret); | |
+ return ret; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static struct android_usb_function hid_function = { | |
+ .name = "hid", | |
+ .init = hid_function_init, | |
+ .cleanup = hid_function_cleanup, | |
+ .bind_config = hid_function_bind_config, | |
+}; | |
+ | |
static struct android_usb_function *supported_functions[] = { | |
&ffs_function, | |
&adb_function, | |
@@ -1287,6 +1325,7 @@ | |
// &gser_function, | |
//#else | |
&acm_function, | |
+ &hid_function, | |
//#endif | |
NULL | |
@@ -1506,6 +1545,8 @@ | |
#endif | |
} | |
+ android_enable_function(dev, "hid"); | |
+ | |
mutex_unlock(&dev->mutex); | |
return size; | |
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/f_hid_android_keyboard.c src/drivers/usb/gadget/f_hid_android_keyboard.c | |
--- src-old/drivers/usb/gadget/f_hid_android_keyboard.c 1970-01-01 03:00:00.000000000 +0300 | |
+++ src/drivers/usb/gadget/f_hid_android_keyboard.c 2016-05-18 21:14:09.469947222 +0300 | |
@@ -0,0 +1,44 @@ | |
+#include <linux/platform_device.h> | |
+#include <linux/usb/g_hid.h> | |
+ | |
+/* hid descriptor for a keyboard */ | |
+static struct hidg_func_descriptor ghid_device_android_keyboard = { | |
+ .subclass = 1, /* Boot Interface Subclass */ | |
+ .protocol = 1, /* Keyboard */ | |
+ .report_length = 8, | |
+ .report_desc_length = 63, | |
+ .report_desc = { | |
+ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ | |
+ 0x09, 0x06, /* USAGE (Keyboard) */ | |
+ 0xa1, 0x01, /* COLLECTION (Application) */ | |
+ 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ | |
+ 0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */ | |
+ 0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ | |
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ | |
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ | |
+ 0x75, 0x01, /* REPORT_SIZE (1) */ | |
+ 0x95, 0x08, /* REPORT_COUNT (8) */ | |
+ 0x81, 0x02, /* INPUT (Data,Var,Abs) */ | |
+ 0x95, 0x01, /* REPORT_COUNT (1) */ | |
+ 0x75, 0x08, /* REPORT_SIZE (8) */ | |
+ 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ | |
+ 0x95, 0x05, /* REPORT_COUNT (5) */ | |
+ 0x75, 0x01, /* REPORT_SIZE (1) */ | |
+ 0x05, 0x08, /* USAGE_PAGE (LEDs) */ | |
+ 0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */ | |
+ 0x29, 0x05, /* USAGE_MAXIMUM (Kana) */ | |
+ 0x91, 0x02, /* OUTPUT (Data,Var,Abs) */ | |
+ 0x95, 0x01, /* REPORT_COUNT (1) */ | |
+ 0x75, 0x03, /* REPORT_SIZE (3) */ | |
+ 0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */ | |
+ 0x95, 0x06, /* REPORT_COUNT (6) */ | |
+ 0x75, 0x08, /* REPORT_SIZE (8) */ | |
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ | |
+ 0x25, 0x65, /* LOGICAL_MAXIMUM (101) */ | |
+ 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ | |
+ 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */ | |
+ 0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */ | |
+ 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ | |
+ 0xc0 /* END_COLLECTION */ | |
+ } | |
+}; | |
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/f_hid_android_mouse.c src/drivers/usb/gadget/f_hid_android_mouse.c | |
--- src-old/drivers/usb/gadget/f_hid_android_mouse.c 1970-01-01 03:00:00.000000000 +0300 | |
+++ src/drivers/usb/gadget/f_hid_android_mouse.c 2016-05-18 21:14:09.469947222 +0300 | |
@@ -0,0 +1,40 @@ | |
+#include <linux/platform_device.h> | |
+#include <linux/usb/g_hid.h> | |
+ | |
+/* HID descriptor for a mouse */ | |
+static struct hidg_func_descriptor ghid_device_android_mouse = { | |
+ .subclass = 1, /* Boot Interface Subclass */ | |
+ .protocol = 2, /* Mouse */ | |
+ .report_length = 4, | |
+ .report_desc_length = 52, | |
+ .report_desc = { | |
+ 0x05, 0x01, //Usage Page(Generic Desktop Controls) | |
+ 0x09, 0x02, //Usage (Mouse) | |
+ 0xa1, 0x01, //Collection (Application) | |
+ 0x09, 0x01, //Usage (pointer) | |
+ 0xa1, 0x00, //Collection (Physical) | |
+ 0x05, 0x09, //Usage Page (Button) | |
+ 0x19, 0x01, //Usage Minimum(1) | |
+ 0x29, 0x05, //Usage Maximum(5) | |
+ 0x15, 0x00, //Logical Minimum(1) | |
+ 0x25, 0x01, //Logical Maximum(1) | |
+ 0x95, 0x05, //Report Count(5) | |
+ 0x75, 0x01, //Report Size(1) | |
+ 0x81, 0x02, //Input(Data,Variable,Absolute,BitField) | |
+ 0x95, 0x01, //Report Count(1) | |
+ 0x75, 0x03, //Report Size(3) | |
+ 0x81, 0x01, //Input(Constant,Array,Absolute,BitField) | |
+ 0x05, 0x01, //Usage Page(Generic Desktop Controls) | |
+ 0x09, 0x30, //Usage(x) | |
+ 0x09, 0x31, //Usage(y) | |
+ 0x09, 0x38, //Usage(Wheel) | |
+ 0x15, 0x81, //Logical Minimum(-127) | |
+ 0x25, 0x7F, //Logical Maximum(127) | |
+ 0x75, 0x08, //Report Size(8) | |
+ 0x95, 0x03, //Report Count(3) | |
+ 0x81, 0x06, //Input(Data,Variable,Relative,BitField) | |
+ 0xc0, //End Collection | |
+ 0xc0 //End Collection | |
+ } | |
+}; | |
+ | |
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/f_hid.c src/drivers/usb/gadget/f_hid.c | |
--- src-old/drivers/usb/gadget/f_hid.c 2014-06-26 15:26:34.000000000 +0300 | |
+++ src/drivers/usb/gadget/f_hid.c 2016-05-18 21:14:09.469947222 +0300 | |
@@ -17,8 +17,10 @@ | |
#include <linux/poll.h> | |
#include <linux/uaccess.h> | |
#include <linux/wait.h> | |
+#include <linux/delay.h> | |
#include <linux/sched.h> | |
#include <linux/usb/g_hid.h> | |
+#include "f_hid.h" | |
static int major, minors; | |
static struct class *hidg_class; | |
@@ -60,6 +62,43 @@ | |
struct usb_ep *out_ep; | |
}; | |
+/* Hacky device list to fix f_hidg_write being called after device destroyed. | |
+ It covers only most common race conditions, there will be rare crashes anyway. */ | |
+enum { HACKY_DEVICE_LIST_SIZE = 4 }; | |
+static struct f_hidg *hacky_device_list[HACKY_DEVICE_LIST_SIZE]; | |
+static void hacky_device_list_add(struct f_hidg *hidg) | |
+{ | |
+ int i; | |
+ for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) { | |
+ if (!hacky_device_list[i]) { | |
+ hacky_device_list[i] = hidg; | |
+ return; | |
+ } | |
+ } | |
+ pr_err("%s: too many devices, not adding device %p\n", __func__, hidg); | |
+} | |
+static void hacky_device_list_remove(struct f_hidg *hidg) | |
+{ | |
+ int i; | |
+ for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) { | |
+ if (hacky_device_list[i] == hidg) { | |
+ hacky_device_list[i] = NULL; | |
+ return; | |
+ } | |
+ } | |
+ pr_err("%s: cannot find device %p\n", __func__, hidg); | |
+} | |
+static int hacky_device_list_check(struct f_hidg *hidg) | |
+{ | |
+ int i; | |
+ for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) { | |
+ if (hacky_device_list[i] == hidg) { | |
+ return 0; | |
+ } | |
+ } | |
+ return 1; | |
+} | |
+ | |
static inline struct f_hidg *func_to_hidg(struct usb_function *f) | |
{ | |
return container_of(f, struct f_hidg, func); | |
@@ -176,6 +215,11 @@ | |
if (!access_ok(VERIFY_WRITE, buffer, count)) | |
return -EFAULT; | |
+ if (hacky_device_list_check(hidg)) { | |
+ pr_err("%s: trying to read from device %p that was destroyed\n", __func__, hidg); | |
+ return -EIO; | |
+ } | |
+ | |
spin_lock_irqsave(&hidg->spinlock, flags); | |
#define READ_COND (!list_empty(&hidg->completed_out_req)) | |
@@ -246,6 +290,11 @@ | |
if (!access_ok(VERIFY_READ, buffer, count)) | |
return -EFAULT; | |
+ if (hacky_device_list_check(hidg)) { | |
+ pr_err("%s: trying to write to device %p that was destroyed\n", __func__, hidg); | |
+ return -EIO; | |
+ } | |
+ | |
mutex_lock(&hidg->lock); | |
#define WRITE_COND (!hidg->write_pending) | |
@@ -260,6 +309,11 @@ | |
hidg->write_queue, WRITE_COND)) | |
return -ERESTARTSYS; | |
+ if (hacky_device_list_check(hidg)) { | |
+ pr_err("%s: trying to write to device %p that was destroyed\n", __func__, hidg); | |
+ return -EIO; | |
+ } | |
+ | |
mutex_lock(&hidg->lock); | |
} | |
@@ -300,7 +354,18 @@ | |
struct f_hidg *hidg = file->private_data; | |
unsigned int ret = 0; | |
+ if (hacky_device_list_check(hidg)) { | |
+ pr_err("%s: trying to poll device %p that was destroyed\n", __func__, hidg); | |
+ return -EIO; | |
+ } | |
+ | |
poll_wait(file, &hidg->read_queue, wait); | |
+ | |
+ if (hacky_device_list_check(hidg)) { | |
+ pr_err("%s: trying to poll device %p that was destroyed\n", __func__, hidg); | |
+ return -EIO; | |
+ } | |
+ | |
poll_wait(file, &hidg->write_queue, wait); | |
if (WRITE_COND) | |
@@ -399,7 +464,12 @@ | |
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | |
| HID_REQ_GET_PROTOCOL): | |
VDBG(cdev, "get_protocol\n"); | |
- goto stall; | |
+ length = min_t(unsigned, length, 1); | |
+ if (hidg->bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) | |
+ ((u8 *) req->buf)[0] = 0; /* Boot protocol */ | |
+ else | |
+ ((u8 *) req->buf)[0] = 1; /* Report protocol */ | |
+ goto respond; | |
break; | |
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | |
@@ -411,6 +481,14 @@ | |
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | |
| HID_REQ_SET_PROTOCOL): | |
VDBG(cdev, "set_protocol\n"); | |
+ length = 0; | |
+ if (hidg->bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) { | |
+ if (value == 0) /* Boot protocol */ | |
+ goto respond; | |
+ } else { | |
+ if (value == 1) /* Report protocol */ | |
+ goto respond; | |
+ } | |
goto stall; | |
break; | |
@@ -560,13 +638,15 @@ | |
.llseek = noop_llseek, | |
}; | |
-static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) | |
+static int hidg_bind(struct usb_configuration *c, struct usb_function *f) | |
{ | |
struct usb_ep *ep; | |
struct f_hidg *hidg = func_to_hidg(f); | |
int status; | |
dev_t dev; | |
+ pr_info("%s: creating device %p\n", __func__, hidg); | |
+ | |
/* allocate instance-specific interface IDs, and patch descriptors */ | |
status = usb_interface_id(c, f); | |
if (status < 0) | |
@@ -632,6 +712,7 @@ | |
goto fail; | |
device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor); | |
+ hacky_device_list_add(hidg); | |
return 0; | |
@@ -651,12 +732,21 @@ | |
{ | |
struct f_hidg *hidg = func_to_hidg(f); | |
+ pr_info("%s: destroying device %p\n", __func__, hidg); | |
+ /* This does not cover all race conditions, only most common one */ | |
+ mutex_lock(&hidg->lock); | |
+ hacky_device_list_remove(hidg); | |
+ mutex_unlock(&hidg->lock); | |
+ | |
device_destroy(hidg_class, MKDEV(major, hidg->minor)); | |
cdev_del(&hidg->cdev); | |
/* disable/free request and end point */ | |
usb_ep_disable(hidg->in_ep); | |
- usb_ep_dequeue(hidg->in_ep, hidg->req); | |
+ /* TODO: calling this function crash kernel, | |
+ not calling this funct ion crash kernel inside f_hidg_write */ | |
+ /* usb_ep_dequeue(hidg->in_ep, hidg->req); */ | |
+ | |
kfree(hidg->req->buf); | |
usb_ep_free_request(hidg->in_ep, hidg->req); | |
@@ -689,7 +779,7 @@ | |
/*-------------------------------------------------------------------------*/ | |
/* usb_configuration */ | |
-int __init hidg_bind_config(struct usb_configuration *c, | |
+int hidg_bind_config(struct usb_configuration *c, | |
struct hidg_func_descriptor *fdesc, int index) | |
{ | |
struct f_hidg *hidg; | |
@@ -743,7 +833,7 @@ | |
return status; | |
} | |
-int __init ghid_setup(struct usb_gadget *g, int count) | |
+int ghid_setup(struct usb_gadget *g, int count) | |
{ | |
int status; | |
dev_t dev; | |
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/f_hid.c.orig src/drivers/usb/gadget/f_hid.c.orig | |
--- src-old/drivers/usb/gadget/f_hid.c.orig 1970-01-01 03:00:00.000000000 +0300 | |
+++ src/drivers/usb/gadget/f_hid.c.orig 2014-06-26 15:26:34.000000000 +0300 | |
@@ -0,0 +1,771 @@ | |
+/* | |
+ * f_hid.c -- USB HID function driver | |
+ * | |
+ * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com> | |
+ * | |
+ * 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. | |
+ */ | |
+ | |
+#include <linux/kernel.h> | |
+#include <linux/module.h> | |
+#include <linux/hid.h> | |
+#include <linux/cdev.h> | |
+#include <linux/mutex.h> | |
+#include <linux/poll.h> | |
+#include <linux/uaccess.h> | |
+#include <linux/wait.h> | |
+#include <linux/sched.h> | |
+#include <linux/usb/g_hid.h> | |
+ | |
+static int major, minors; | |
+static struct class *hidg_class; | |
+ | |
+/*-------------------------------------------------------------------------*/ | |
+/* HID gadget struct */ | |
+ | |
+struct f_hidg_req_list { | |
+ struct usb_request *req; | |
+ unsigned int pos; | |
+ struct list_head list; | |
+}; | |
+ | |
+struct f_hidg { | |
+ /* configuration */ | |
+ unsigned char bInterfaceSubClass; | |
+ unsigned char bInterfaceProtocol; | |
+ unsigned short report_desc_length; | |
+ char *report_desc; | |
+ unsigned short report_length; | |
+ | |
+ /* recv report */ | |
+ struct list_head completed_out_req; | |
+ spinlock_t spinlock; | |
+ wait_queue_head_t read_queue; | |
+ unsigned int qlen; | |
+ | |
+ /* send report */ | |
+ struct mutex lock; | |
+ bool write_pending; | |
+ wait_queue_head_t write_queue; | |
+ struct usb_request *req; | |
+ | |
+ int minor; | |
+ struct cdev cdev; | |
+ struct usb_function func; | |
+ | |
+ struct usb_ep *in_ep; | |
+ struct usb_ep *out_ep; | |
+}; | |
+ | |
+static inline struct f_hidg *func_to_hidg(struct usb_function *f) | |
+{ | |
+ return container_of(f, struct f_hidg, func); | |
+} | |
+ | |
+/*-------------------------------------------------------------------------*/ | |
+/* Static descriptors */ | |
+ | |
+static struct usb_interface_descriptor hidg_interface_desc = { | |
+ .bLength = sizeof hidg_interface_desc, | |
+ .bDescriptorType = USB_DT_INTERFACE, | |
+ /* .bInterfaceNumber = DYNAMIC */ | |
+ .bAlternateSetting = 0, | |
+ .bNumEndpoints = 2, | |
+ .bInterfaceClass = USB_CLASS_HID, | |
+ /* .bInterfaceSubClass = DYNAMIC */ | |
+ /* .bInterfaceProtocol = DYNAMIC */ | |
+ /* .iInterface = DYNAMIC */ | |
+}; | |
+ | |
+static struct hid_descriptor hidg_desc = { | |
+ .bLength = sizeof hidg_desc, | |
+ .bDescriptorType = HID_DT_HID, | |
+ .bcdHID = 0x0101, | |
+ .bCountryCode = 0x00, | |
+ .bNumDescriptors = 0x1, | |
+ /*.desc[0].bDescriptorType = DYNAMIC */ | |
+ /*.desc[0].wDescriptorLenght = DYNAMIC */ | |
+}; | |
+ | |
+/* High-Speed Support */ | |
+ | |
+static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { | |
+ .bLength = USB_DT_ENDPOINT_SIZE, | |
+ .bDescriptorType = USB_DT_ENDPOINT, | |
+ .bEndpointAddress = USB_DIR_IN, | |
+ .bmAttributes = USB_ENDPOINT_XFER_INT, | |
+ /*.wMaxPacketSize = DYNAMIC */ | |
+ .bInterval = 4, /* FIXME: Add this field in the | |
+ * HID gadget configuration? | |
+ * (struct hidg_func_descriptor) | |
+ */ | |
+}; | |
+ | |
+static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { | |
+ .bLength = USB_DT_ENDPOINT_SIZE, | |
+ .bDescriptorType = USB_DT_ENDPOINT, | |
+ .bEndpointAddress = USB_DIR_OUT, | |
+ .bmAttributes = USB_ENDPOINT_XFER_INT, | |
+ /*.wMaxPacketSize = DYNAMIC */ | |
+ .bInterval = 4, /* FIXME: Add this field in the | |
+ * HID gadget configuration? | |
+ * (struct hidg_func_descriptor) | |
+ */ | |
+}; | |
+ | |
+static struct usb_descriptor_header *hidg_hs_descriptors[] = { | |
+ (struct usb_descriptor_header *)&hidg_interface_desc, | |
+ (struct usb_descriptor_header *)&hidg_desc, | |
+ (struct usb_descriptor_header *)&hidg_hs_in_ep_desc, | |
+ (struct usb_descriptor_header *)&hidg_hs_out_ep_desc, | |
+ NULL, | |
+}; | |
+ | |
+/* Full-Speed Support */ | |
+ | |
+static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { | |
+ .bLength = USB_DT_ENDPOINT_SIZE, | |
+ .bDescriptorType = USB_DT_ENDPOINT, | |
+ .bEndpointAddress = USB_DIR_IN, | |
+ .bmAttributes = USB_ENDPOINT_XFER_INT, | |
+ /*.wMaxPacketSize = DYNAMIC */ | |
+ .bInterval = 10, /* FIXME: Add this field in the | |
+ * HID gadget configuration? | |
+ * (struct hidg_func_descriptor) | |
+ */ | |
+}; | |
+ | |
+static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { | |
+ .bLength = USB_DT_ENDPOINT_SIZE, | |
+ .bDescriptorType = USB_DT_ENDPOINT, | |
+ .bEndpointAddress = USB_DIR_OUT, | |
+ .bmAttributes = USB_ENDPOINT_XFER_INT, | |
+ /*.wMaxPacketSize = DYNAMIC */ | |
+ .bInterval = 10, /* FIXME: Add this field in the | |
+ * HID gadget configuration? | |
+ * (struct hidg_func_descriptor) | |
+ */ | |
+}; | |
+ | |
+static struct usb_descriptor_header *hidg_fs_descriptors[] = { | |
+ (struct usb_descriptor_header *)&hidg_interface_desc, | |
+ (struct usb_descriptor_header *)&hidg_desc, | |
+ (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, | |
+ (struct usb_descriptor_header *)&hidg_fs_out_ep_desc, | |
+ NULL, | |
+}; | |
+ | |
+/*-------------------------------------------------------------------------*/ | |
+/* Char Device */ | |
+ | |
+static ssize_t f_hidg_read(struct file *file, char __user *buffer, | |
+ size_t count, loff_t *ptr) | |
+{ | |
+ struct f_hidg *hidg = file->private_data; | |
+ struct f_hidg_req_list *list; | |
+ struct usb_request *req; | |
+ unsigned long flags; | |
+ int ret; | |
+ | |
+ if (!count) | |
+ return 0; | |
+ | |
+ if (!access_ok(VERIFY_WRITE, buffer, count)) | |
+ return -EFAULT; | |
+ | |
+ spin_lock_irqsave(&hidg->spinlock, flags); | |
+ | |
+#define READ_COND (!list_empty(&hidg->completed_out_req)) | |
+ | |
+ /* wait for at least one buffer to complete */ | |
+ while (!READ_COND) { | |
+ spin_unlock_irqrestore(&hidg->spinlock, flags); | |
+ if (file->f_flags & O_NONBLOCK) | |
+ return -EAGAIN; | |
+ | |
+ if (wait_event_interruptible(hidg->read_queue, READ_COND)) | |
+ return -ERESTARTSYS; | |
+ | |
+ spin_lock_irqsave(&hidg->spinlock, flags); | |
+ } | |
+ | |
+ /* pick the first one */ | |
+ list = list_first_entry(&hidg->completed_out_req, | |
+ struct f_hidg_req_list, list); | |
+ req = list->req; | |
+ count = min_t(unsigned int, count, req->actual - list->pos); | |
+ spin_unlock_irqrestore(&hidg->spinlock, flags); | |
+ | |
+ /* copy to user outside spinlock */ | |
+ count -= copy_to_user(buffer, req->buf + list->pos, count); | |
+ list->pos += count; | |
+ | |
+ /* | |
+ * if this request is completely handled and transfered to | |
+ * userspace, remove its entry from the list and requeue it | |
+ * again. Otherwise, we will revisit it again upon the next | |
+ * call, taking into account its current read position. | |
+ */ | |
+ if (list->pos == req->actual) { | |
+ spin_lock_irqsave(&hidg->spinlock, flags); | |
+ list_del(&list->list); | |
+ kfree(list); | |
+ spin_unlock_irqrestore(&hidg->spinlock, flags); | |
+ | |
+ req->length = hidg->report_length; | |
+ ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL); | |
+ if (ret < 0) | |
+ return ret; | |
+ } | |
+ | |
+ return count; | |
+} | |
+ | |
+static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req) | |
+{ | |
+ struct f_hidg *hidg = (struct f_hidg *)ep->driver_data; | |
+ | |
+ if (req->status != 0) { | |
+ ERROR(hidg->func.config->cdev, | |
+ "End Point Request ERROR: %d\n", req->status); | |
+ } | |
+ | |
+ hidg->write_pending = 0; | |
+ wake_up(&hidg->write_queue); | |
+} | |
+ | |
+static ssize_t f_hidg_write(struct file *file, const char __user *buffer, | |
+ size_t count, loff_t *offp) | |
+{ | |
+ struct f_hidg *hidg = file->private_data; | |
+ ssize_t status = -ENOMEM; | |
+ | |
+ if (!access_ok(VERIFY_READ, buffer, count)) | |
+ return -EFAULT; | |
+ | |
+ mutex_lock(&hidg->lock); | |
+ | |
+#define WRITE_COND (!hidg->write_pending) | |
+ | |
+ /* write queue */ | |
+ while (!WRITE_COND) { | |
+ mutex_unlock(&hidg->lock); | |
+ if (file->f_flags & O_NONBLOCK) | |
+ return -EAGAIN; | |
+ | |
+ if (wait_event_interruptible_exclusive( | |
+ hidg->write_queue, WRITE_COND)) | |
+ return -ERESTARTSYS; | |
+ | |
+ mutex_lock(&hidg->lock); | |
+ } | |
+ | |
+ count = min_t(unsigned, count, hidg->report_length); | |
+ status = copy_from_user(hidg->req->buf, buffer, count); | |
+ | |
+ if (status != 0) { | |
+ ERROR(hidg->func.config->cdev, | |
+ "copy_from_user error\n"); | |
+ mutex_unlock(&hidg->lock); | |
+ return -EINVAL; | |
+ } | |
+ | |
+ hidg->req->status = 0; | |
+ hidg->req->zero = 0; | |
+ hidg->req->length = count; | |
+ hidg->req->complete = f_hidg_req_complete; | |
+ hidg->req->context = hidg; | |
+ hidg->write_pending = 1; | |
+ | |
+ status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC); | |
+ if (status < 0) { | |
+ ERROR(hidg->func.config->cdev, | |
+ "usb_ep_queue error on int endpoint %zd\n", status); | |
+ hidg->write_pending = 0; | |
+ wake_up(&hidg->write_queue); | |
+ } else { | |
+ status = count; | |
+ } | |
+ | |
+ mutex_unlock(&hidg->lock); | |
+ | |
+ return status; | |
+} | |
+ | |
+static unsigned int f_hidg_poll(struct file *file, poll_table *wait) | |
+{ | |
+ struct f_hidg *hidg = file->private_data; | |
+ unsigned int ret = 0; | |
+ | |
+ poll_wait(file, &hidg->read_queue, wait); | |
+ poll_wait(file, &hidg->write_queue, wait); | |
+ | |
+ if (WRITE_COND) | |
+ ret |= POLLOUT | POLLWRNORM; | |
+ | |
+ if (READ_COND) | |
+ ret |= POLLIN | POLLRDNORM; | |
+ | |
+ return ret; | |
+} | |
+ | |
+#undef WRITE_COND | |
+#undef READ_COND | |
+ | |
+static int f_hidg_release(struct inode *inode, struct file *fd) | |
+{ | |
+ fd->private_data = NULL; | |
+ return 0; | |
+} | |
+ | |
+static int f_hidg_open(struct inode *inode, struct file *fd) | |
+{ | |
+ struct f_hidg *hidg = | |
+ container_of(inode->i_cdev, struct f_hidg, cdev); | |
+ | |
+ fd->private_data = hidg; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/*-------------------------------------------------------------------------*/ | |
+/* usb_function */ | |
+ | |
+static struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, unsigned length) | |
+{ | |
+ struct usb_request *req; | |
+ | |
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC); | |
+ if (req) { | |
+ req->length = length; | |
+ req->buf = kmalloc(length, GFP_ATOMIC); | |
+ if (!req->buf) { | |
+ usb_ep_free_request(ep, req); | |
+ req = NULL; | |
+ } | |
+ } | |
+ return req; | |
+} | |
+ | |
+static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) | |
+{ | |
+ struct f_hidg *hidg = (struct f_hidg *) req->context; | |
+ struct f_hidg_req_list *req_list; | |
+ unsigned long flags; | |
+ | |
+ req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC); | |
+ if (!req_list) | |
+ return; | |
+ | |
+ req_list->req = req; | |
+ | |
+ spin_lock_irqsave(&hidg->spinlock, flags); | |
+ list_add_tail(&req_list->list, &hidg->completed_out_req); | |
+ spin_unlock_irqrestore(&hidg->spinlock, flags); | |
+ | |
+ wake_up(&hidg->read_queue); | |
+} | |
+ | |
+static int hidg_setup(struct usb_function *f, | |
+ const struct usb_ctrlrequest *ctrl) | |
+{ | |
+ struct f_hidg *hidg = func_to_hidg(f); | |
+ struct usb_composite_dev *cdev = f->config->cdev; | |
+ struct usb_request *req = cdev->req; | |
+ int status = 0; | |
+ __u16 value, length; | |
+ | |
+ 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, hidg->report_length); | |
+ memset(req->buf, 0x0, length); | |
+ | |
+ goto respond; | |
+ break; | |
+ | |
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | |
+ | HID_REQ_GET_PROTOCOL): | |
+ VDBG(cdev, "get_protocol\n"); | |
+ goto stall; | |
+ 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); | |
+ goto stall; | |
+ break; | |
+ | |
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | |
+ | HID_REQ_SET_PROTOCOL): | |
+ VDBG(cdev, "set_protocol\n"); | |
+ goto stall; | |
+ 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, | |
+ hidg_desc.bLength); | |
+ memcpy(req->buf, &hidg_desc, length); | |
+ goto respond; | |
+ break; | |
+ case HID_DT_REPORT: | |
+ VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n"); | |
+ length = min_t(unsigned short, length, | |
+ hidg->report_desc_length); | |
+ memcpy(req->buf, hidg->report_desc, length); | |
+ goto respond; | |
+ break; | |
+ | |
+ default: | |
+ VDBG(cdev, "Unknown descriptor request 0x%x\n", | |
+ value >> 8); | |
+ goto stall; | |
+ break; | |
+ } | |
+ break; | |
+ | |
+ default: | |
+ VDBG(cdev, "Unknown request 0x%x\n", | |
+ ctrl->bRequest); | |
+ goto stall; | |
+ break; | |
+ } | |
+ | |
+stall: | |
+ return -EOPNOTSUPP; | |
+ | |
+respond: | |
+ 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 void hidg_disable(struct usb_function *f) | |
+{ | |
+ struct f_hidg *hidg = func_to_hidg(f); | |
+ struct f_hidg_req_list *list, *next; | |
+ | |
+ usb_ep_disable(hidg->in_ep); | |
+ hidg->in_ep->driver_data = NULL; | |
+ | |
+ usb_ep_disable(hidg->out_ep); | |
+ hidg->out_ep->driver_data = NULL; | |
+ | |
+ list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { | |
+ list_del(&list->list); | |
+ kfree(list); | |
+ } | |
+} | |
+ | |
+static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) | |
+{ | |
+ struct usb_composite_dev *cdev = f->config->cdev; | |
+ struct f_hidg *hidg = func_to_hidg(f); | |
+ int i, status = 0; | |
+ | |
+ VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); | |
+ | |
+ if (hidg->in_ep != NULL) { | |
+ /* restart endpoint */ | |
+ if (hidg->in_ep->driver_data != NULL) | |
+ usb_ep_disable(hidg->in_ep); | |
+ | |
+ status = config_ep_by_speed(f->config->cdev->gadget, f, | |
+ hidg->in_ep); | |
+ if (status) { | |
+ ERROR(cdev, "config_ep_by_speed FAILED!\n"); | |
+ goto fail; | |
+ } | |
+ status = usb_ep_enable(hidg->in_ep); | |
+ if (status < 0) { | |
+ ERROR(cdev, "Enable IN endpoint FAILED!\n"); | |
+ goto fail; | |
+ } | |
+ hidg->in_ep->driver_data = hidg; | |
+ } | |
+ | |
+ | |
+ if (hidg->out_ep != NULL) { | |
+ /* restart endpoint */ | |
+ if (hidg->out_ep->driver_data != NULL) | |
+ usb_ep_disable(hidg->out_ep); | |
+ | |
+ status = config_ep_by_speed(f->config->cdev->gadget, f, | |
+ hidg->out_ep); | |
+ if (status) { | |
+ ERROR(cdev, "config_ep_by_speed FAILED!\n"); | |
+ goto fail; | |
+ } | |
+ status = usb_ep_enable(hidg->out_ep); | |
+ if (status < 0) { | |
+ ERROR(cdev, "Enable IN endpoint FAILED!\n"); | |
+ goto fail; | |
+ } | |
+ hidg->out_ep->driver_data = hidg; | |
+ | |
+ /* | |
+ * allocate a bunch of read buffers and queue them all at once. | |
+ */ | |
+ for (i = 0; i < hidg->qlen && status == 0; i++) { | |
+ struct usb_request *req = | |
+ hidg_alloc_ep_req(hidg->out_ep, | |
+ hidg->report_length); | |
+ if (req) { | |
+ req->complete = hidg_set_report_complete; | |
+ req->context = hidg; | |
+ status = usb_ep_queue(hidg->out_ep, req, | |
+ GFP_ATOMIC); | |
+ if (status) | |
+ ERROR(cdev, "%s queue req --> %d\n", | |
+ hidg->out_ep->name, status); | |
+ } else { | |
+ usb_ep_disable(hidg->out_ep); | |
+ hidg->out_ep->driver_data = NULL; | |
+ status = -ENOMEM; | |
+ goto fail; | |
+ } | |
+ } | |
+ } | |
+ | |
+fail: | |
+ return status; | |
+} | |
+ | |
+const struct file_operations f_hidg_fops = { | |
+ .owner = THIS_MODULE, | |
+ .open = f_hidg_open, | |
+ .release = f_hidg_release, | |
+ .write = f_hidg_write, | |
+ .read = f_hidg_read, | |
+ .poll = f_hidg_poll, | |
+ .llseek = noop_llseek, | |
+}; | |
+ | |
+static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) | |
+{ | |
+ struct usb_ep *ep; | |
+ struct f_hidg *hidg = func_to_hidg(f); | |
+ int status; | |
+ dev_t dev; | |
+ | |
+ /* allocate instance-specific interface IDs, and patch descriptors */ | |
+ status = usb_interface_id(c, f); | |
+ if (status < 0) | |
+ goto fail; | |
+ hidg_interface_desc.bInterfaceNumber = status; | |
+ | |
+ /* allocate instance-specific endpoints */ | |
+ status = -ENODEV; | |
+ ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc); | |
+ if (!ep) | |
+ goto fail; | |
+ ep->driver_data = c->cdev; /* claim */ | |
+ hidg->in_ep = ep; | |
+ | |
+ ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); | |
+ if (!ep) | |
+ goto fail; | |
+ ep->driver_data = c->cdev; /* claim */ | |
+ hidg->out_ep = ep; | |
+ | |
+ /* preallocate request and buffer */ | |
+ status = -ENOMEM; | |
+ hidg->req = usb_ep_alloc_request(hidg->in_ep, GFP_KERNEL); | |
+ if (!hidg->req) | |
+ goto fail; | |
+ | |
+ hidg->req->buf = kmalloc(hidg->report_length, GFP_KERNEL); | |
+ if (!hidg->req->buf) | |
+ goto fail; | |
+ | |
+ /* set descriptor dynamic values */ | |
+ hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; | |
+ hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; | |
+ hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); | |
+ hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); | |
+ hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); | |
+ hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); | |
+ hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT; | |
+ hidg_desc.desc[0].wDescriptorLength = | |
+ cpu_to_le16(hidg->report_desc_length); | |
+ | |
+ hidg_hs_in_ep_desc.bEndpointAddress = | |
+ hidg_fs_in_ep_desc.bEndpointAddress; | |
+ hidg_hs_out_ep_desc.bEndpointAddress = | |
+ hidg_fs_out_ep_desc.bEndpointAddress; | |
+ | |
+ status = usb_assign_descriptors(f, hidg_fs_descriptors, | |
+ hidg_hs_descriptors, NULL); | |
+ if (status) | |
+ goto fail; | |
+ | |
+ mutex_init(&hidg->lock); | |
+ spin_lock_init(&hidg->spinlock); | |
+ init_waitqueue_head(&hidg->write_queue); | |
+ init_waitqueue_head(&hidg->read_queue); | |
+ INIT_LIST_HEAD(&hidg->completed_out_req); | |
+ | |
+ /* create char device */ | |
+ cdev_init(&hidg->cdev, &f_hidg_fops); | |
+ dev = MKDEV(major, hidg->minor); | |
+ status = cdev_add(&hidg->cdev, dev, 1); | |
+ if (status) | |
+ goto fail; | |
+ | |
+ device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor); | |
+ | |
+ return 0; | |
+ | |
+fail: | |
+ ERROR(f->config->cdev, "hidg_bind FAILED\n"); | |
+ if (hidg->req != NULL) { | |
+ kfree(hidg->req->buf); | |
+ if (hidg->in_ep != NULL) | |
+ usb_ep_free_request(hidg->in_ep, hidg->req); | |
+ } | |
+ | |
+ usb_free_all_descriptors(f); | |
+ return status; | |
+} | |
+ | |
+static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) | |
+{ | |
+ struct f_hidg *hidg = func_to_hidg(f); | |
+ | |
+ device_destroy(hidg_class, MKDEV(major, hidg->minor)); | |
+ cdev_del(&hidg->cdev); | |
+ | |
+ /* disable/free request and end point */ | |
+ usb_ep_disable(hidg->in_ep); | |
+ usb_ep_dequeue(hidg->in_ep, hidg->req); | |
+ kfree(hidg->req->buf); | |
+ usb_ep_free_request(hidg->in_ep, hidg->req); | |
+ | |
+ usb_free_all_descriptors(f); | |
+ | |
+ kfree(hidg->report_desc); | |
+ kfree(hidg); | |
+} | |
+ | |
+/*-------------------------------------------------------------------------*/ | |
+/* 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, | |
+}; | |
+ | |
+/*-------------------------------------------------------------------------*/ | |
+/* usb_configuration */ | |
+ | |
+int __init hidg_bind_config(struct usb_configuration *c, | |
+ struct hidg_func_descriptor *fdesc, int index) | |
+{ | |
+ struct f_hidg *hidg; | |
+ int status; | |
+ | |
+ if (index >= minors) | |
+ return -ENOENT; | |
+ | |
+ /* maybe allocate device-global string IDs, and patch descriptors */ | |
+ if (ct_func_string_defs[CT_FUNC_HID_IDX].id == 0) { | |
+ status = usb_string_id(c->cdev); | |
+ if (status < 0) | |
+ return status; | |
+ ct_func_string_defs[CT_FUNC_HID_IDX].id = status; | |
+ hidg_interface_desc.iInterface = status; | |
+ } | |
+ | |
+ /* allocate and initialize one new instance */ | |
+ hidg = kzalloc(sizeof *hidg, GFP_KERNEL); | |
+ if (!hidg) | |
+ return -ENOMEM; | |
+ | |
+ hidg->minor = index; | |
+ hidg->bInterfaceSubClass = fdesc->subclass; | |
+ hidg->bInterfaceProtocol = fdesc->protocol; | |
+ hidg->report_length = fdesc->report_length; | |
+ hidg->report_desc_length = fdesc->report_desc_length; | |
+ hidg->report_desc = kmemdup(fdesc->report_desc, | |
+ fdesc->report_desc_length, | |
+ GFP_KERNEL); | |
+ if (!hidg->report_desc) { | |
+ kfree(hidg); | |
+ return -ENOMEM; | |
+ } | |
+ | |
+ hidg->func.name = "hid"; | |
+ hidg->func.strings = ct_func_strings; | |
+ hidg->func.bind = hidg_bind; | |
+ hidg->func.unbind = hidg_unbind; | |
+ hidg->func.set_alt = hidg_set_alt; | |
+ hidg->func.disable = hidg_disable; | |
+ hidg->func.setup = hidg_setup; | |
+ | |
+ /* this could me made configurable at some point */ | |
+ hidg->qlen = 4; | |
+ | |
+ status = usb_add_function(c, &hidg->func); | |
+ if (status) | |
+ kfree(hidg); | |
+ | |
+ return status; | |
+} | |
+ | |
+int __init ghid_setup(struct usb_gadget *g, int count) | |
+{ | |
+ int status; | |
+ dev_t dev; | |
+ | |
+ hidg_class = class_create(THIS_MODULE, "hidg"); | |
+ | |
+ status = alloc_chrdev_region(&dev, 0, count, "hidg"); | |
+ if (!status) { | |
+ major = MAJOR(dev); | |
+ minors = count; | |
+ } | |
+ | |
+ return status; | |
+} | |
+ | |
+void ghid_cleanup(void) | |
+{ | |
+ if (major) { | |
+ unregister_chrdev_region(MKDEV(major, 0), minors); | |
+ major = minors = 0; | |
+ } | |
+ | |
+ class_destroy(hidg_class); | |
+ hidg_class = NULL; | |
+} | |
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/f_hid.h src/drivers/usb/gadget/f_hid.h | |
--- src-old/drivers/usb/gadget/f_hid.h 1970-01-01 03:00:00.000000000 +0300 | |
+++ src/drivers/usb/gadget/f_hid.h 2016-05-18 21:14:09.469947222 +0300 | |
@@ -0,0 +1,16 @@ | |
+#ifndef _GADGET_F_HID_H | |
+#define _GADGET_F_HID_H | |
+ | |
+#include <linux/hid.h> | |
+#include <linux/usb/composite.h> | |
+#include <linux/usb/gadget.h> | |
+#include <linux/usb/g_hid.h> | |
+ | |
+int hidg_bind_config(struct usb_configuration *c, | |
+ struct hidg_func_descriptor *fdesc, int index); | |
+ | |
+int ghid_setup(struct usb_gadget *g, int count); | |
+ | |
+void ghid_cleanup(void); | |
+ | |
+#endif | |
diff -u -r --unidirectional-new-file src-old/drivers/usb/gadget/Makefile src/drivers/usb/gadget/Makefile | |
--- src-old/drivers/usb/gadget/Makefile 2014-06-26 15:26:34.000000000 +0300 | |
+++ src/drivers/usb/gadget/Makefile 2016-05-18 21:14:09.469947222 +0300 | |
@@ -66,7 +66,7 @@ | |
g_webcam-y := webcam.o | |
g_ncm-y := ncm.o | |
g_acm_ms-y := acm_ms.o | |
-g_android-y := android.o | |
+g_android-y := android.o f_hid.o | |
g_tcm_usb_gadget-y := tcm_usb_gadget.o | |
obj-$(CONFIG_USB_ZERO) += g_zero.o |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment