2022-08-01 12:55:59

by Suniel Mahesh

[permalink] [raw]
Subject: [RFC 0/2] Extend functionality for GET_REPORT

This series does the following:

patch 1/2 extends functionality for GET_REPORT.

The current kernel implementation for GET_REPORT is that the kernel
sends back a zero filled report (of length == report_length), when
the Host request's a particular report from the device/gadget.

This changeset extends functionality for GET_REPORT by sending a
particular report based on report type and report number.

patch 2/2 adds a test application to test the extended
functionality.

please review and share your thoughts.

Suniel Mahesh (2):
usb: gadget: f_hid: Extend functionality for GET_REPORT mode
HID: ghid: add example program for GET_REPORT

drivers/usb/gadget/function/f_hid.c | 166 ++++++++++++++++++++++++++-
include/{ => uapi}/linux/usb/g_hid.h | 10 ++
samples/Kconfig | 10 ++
samples/Makefile | 1 +
samples/ghid/Makefile | 4 +
samples/ghid/test-hid.c | 134 +++++++++++++++++++++
6 files changed, 323 insertions(+), 2 deletions(-)
rename include/{ => uapi}/linux/usb/g_hid.h (72%)
create mode 100644 samples/ghid/Makefile
create mode 100644 samples/ghid/test-hid.c

--
2.25.1



2022-08-01 13:17:19

by Suniel Mahesh

[permalink] [raw]
Subject: [RFC 2/2] HID: ghid: add example program for GET_REPORT

This adds a user-space ghid sample get report program at
gadget side. This program sends reports from userspace(gadget side)
which are saved in the kernel in a list.

When a host requests a particular report based on report number
and type, corresponding report can be send to host, instead of a
zero filled in report.

This program can add, delete reports and modify an existing report.

Signed-off-by: Suniel Mahesh <[email protected]>
---
samples/Kconfig | 10 +++
samples/Makefile | 1 +
samples/ghid/Makefile | 4 ++
samples/ghid/test-hid.c | 134 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 149 insertions(+)
create mode 100644 samples/ghid/Makefile
create mode 100644 samples/ghid/test-hid.c

diff --git a/samples/Kconfig b/samples/Kconfig
index 470ee3baf2e1..f3d7873bb966 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -86,6 +86,16 @@ config SAMPLE_FPROBE
This builds a fprobe example module. This module has an option 'symbol'.
You can specify a probed symbol or symbols separated with ','.

+config SAMPLE_GHID_GET_REPORT
+ bool "GHID sample get report"
+ depends on CC_CAN_LINK && HEADERS_INSTALL
+ help
+ Build GHID sample get report program. This program can send reports from
+ userspace(gadget side) which are saved in the kernel in a linked list.
+ When a host requests a particular report based on report number and type,
+ corresponding report can be send to host, instead of a zero filled in report.
+ This program can add, delete reports and modify an existing report.
+
config SAMPLE_KFIFO
tristate "Build kfifo examples -- loadable modules only"
depends on m
diff --git a/samples/Makefile b/samples/Makefile
index 701e912ab5af..1d58f7a0381e 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -6,6 +6,7 @@ subdir-$(CONFIG_SAMPLE_ANDROID_BINDERFS) += binderfs
obj-$(CONFIG_SAMPLE_CONFIGFS) += configfs/
obj-$(CONFIG_SAMPLE_CONNECTOR) += connector/
obj-$(CONFIG_SAMPLE_FANOTIFY_ERROR) += fanotify/
+subdir-$(SAMPLE_GHID_GET_REPORT) += ghid
subdir-$(CONFIG_SAMPLE_HIDRAW) += hidraw
obj-$(CONFIG_SAMPLE_HW_BREAKPOINT) += hw_breakpoint/
obj-$(CONFIG_SAMPLE_KDB) += kdb/
diff --git a/samples/ghid/Makefile b/samples/ghid/Makefile
new file mode 100644
index 000000000000..8c93ded625c1
--- /dev/null
+++ b/samples/ghid/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+userprogs-always-y += test-hid
+
+userccflags += -I usr/include
diff --git a/samples/ghid/test-hid.c b/samples/ghid/test-hid.c
new file mode 100644
index 000000000000..f76786509047
--- /dev/null
+++ b/samples/ghid/test-hid.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * usb hidg GET_REPORT example, device/gadget side
+ * This program tests the newly implemented GET_REPORT feature
+ *
+ * Copyright (c) 2022 Amarula Solutions India PVT LTD
+ *
+ * Authors:
+ * Copyright (c) 2022 Suniel Mahesh <[email protected]>
+ *
+ */
+
+#include <pthread.h>
+/* Linux */
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/hidraw.h>
+#include <linux/uhid.h>
+#include <uapi/linux/usb/g_hid.h>
+
+/* Unix */
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/* C */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+
+/*
+ * fix for failing compilation on systems that don't
+ * yet populate new version of uapi/linux/usb/g_hid.h
+ * to userspace.
+ */
+#define GADGET_ADD_REPORT_STATUS _IOWR('g', 0x41, struct uhid_set_report_req)
+#define GADGET_REMOVE_REPORT_STATUS _IOWR('g', 0x42, struct uhid_get_report_req)
+#define GADGET_UPDATE_REPORT_STATUS _IOWR('g', 0x43, struct uhid_set_report_req)
+
+struct uhid_set_report_req *create_report(void)
+{
+ struct uhid_set_report_req *rep;
+ int i;
+
+ rep = (struct uhid_set_report_req *)calloc(1, sizeof(struct uhid_set_report_req));
+ if (rep == NULL) {
+ perror("calloc() failed");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("enter report id:\n");
+ scanf("%u", &rep->id);
+ printf("enter report number:\n");
+ scanf("%hhu", &rep->rnum);
+ printf("enter report type:\n");
+ scanf("%hhu", &rep->rtype);
+ printf("enter report size:\n");
+ scanf("%hu", &rep->size);
+ printf("enter report data:\n");
+
+ for (i = 0; i < rep->size; i++)
+ scanf("%hhu", &rep->data[i]);
+
+ return rep;
+}
+
+int main(int argc, char **argv)
+{
+ const char *filename = NULL;
+ struct uhid_set_report_req *report;
+ int fd = 0, res = 0, i, reports;
+
+ if (argc < 1) {
+ fprintf(stderr, "Usage: %s /dev/hidg0\n", argv[0]);
+ return -1;
+ }
+
+ filename = argv[1];
+ fd = open(filename, O_RDWR, 0666);
+
+ if (fd == -1) {
+ perror(filename);
+ return -2;
+ }
+
+ printf("enter no of reports to send from userspace:\n");
+ scanf("%d", &reports);
+
+ if (reports == 0)
+ goto out;
+
+ for (i = 0; i < reports; i++) {
+ report = create_report();
+/* send reports to device */
+ res = ioctl(fd, GADGET_ADD_REPORT_STATUS, report);
+ if (res < 0) {
+ perror("GADGET_ADD_REPORT_STATUS");
+ res = -3;
+ goto test_end;
+ }
+ free(report);
+ }
+
+/* delete report with report number specified */
+ printf("deleting report w.r.t rtype and rnum:\n");
+ report = create_report();
+ res = ioctl(fd, GADGET_REMOVE_REPORT_STATUS, report);
+ if (res < 0) {
+ perror("GADGET_REMOVE_REPORT_STATUS");
+ res = -4;
+ goto test_end;
+ }
+ free(report);
+
+/* modify an existing report identified by report number */
+ printf("modify report w.r.t rtype and rnum:\n");
+ report = create_report();
+ res = ioctl(fd, GADGET_UPDATE_REPORT_STATUS, report);
+ if (res < 0) {
+ perror("GADGET_UPDATE_REPORT_STATUS");
+ res = -5;
+ }
+
+test_end:
+ free(report);
+ report = NULL;
+out:
+ close(fd);
+ return res;
+}
--
2.25.1


2022-08-01 13:18:48

by Suniel Mahesh

[permalink] [raw]
Subject: [RFC 1/2] usb: gadget: f_hid: Extend functionality for GET_REPORT mode

The current kernel implementation for GET_REPORT is that the kernel
sends back a zero filled report (of length == report_length), when
the Host request's a particular report from the device/gadget.

This changeset extends functionality for GET_REPORT by sending a
particular report based on report type and report number.

corresponding ioctl is also implemented.

Signed-off-by: Suniel Mahesh <[email protected]>
Signed-off-by: Michael Trimarchi <[email protected]>
---
drivers/usb/gadget/function/f_hid.c | 166 ++++++++++++++++++++++++++-
include/{ => uapi}/linux/usb/g_hid.h | 10 ++
2 files changed, 174 insertions(+), 2 deletions(-)
rename include/{ => uapi}/linux/usb/g_hid.h (72%)

diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
index ca0a7d9eaa34..70787c73caf9 100644
--- a/drivers/usb/gadget/function/f_hid.c
+++ b/drivers/usb/gadget/function/f_hid.c
@@ -16,6 +16,7 @@
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/usb/g_hid.h>
+#include <linux/uhid.h>

#include "u_f.h"
#include "u_hid.h"
@@ -27,6 +28,11 @@ static struct class *hidg_class;
static DEFINE_IDA(hidg_ida);
static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */

+struct report_entry {
+ struct uhid_set_report_req report_data;
+ struct list_head node;
+};
+
/*-------------------------------------------------------------------------*/
/* HID gadget struct */

@@ -71,6 +77,10 @@ struct f_hidg {
wait_queue_head_t write_queue;
struct usb_request *req;

+ /* hid report list */
+ spinlock_t report_spinlock;
+ struct list_head report_list;
+
int minor;
struct cdev cdev;
struct usb_function func;
@@ -553,6 +563,136 @@ static int f_hidg_open(struct inode *inode, struct file *fd)
return 0;
}

+static bool f_hidg_param_valid(struct report_entry *entry)
+{
+
+ if (entry->report_data.size > UHID_DATA_MAX)
+ return false;
+
+ switch (entry->report_data.rtype) {
+ case UHID_FEATURE_REPORT:
+ case UHID_OUTPUT_REPORT:
+ case UHID_INPUT_REPORT:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static struct report_entry *f_hidg_search_for_report(struct f_hidg *hidg, u8 rnum, u8 rtype)
+{
+ struct list_head *ptr;
+ struct report_entry *entry;
+
+ list_for_each(ptr, &hidg->report_list) {
+ entry = list_entry(ptr, struct report_entry, node);
+ if (entry->report_data.rnum == rnum &&
+ entry->report_data.rtype == rtype) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+static long f_hidg_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct f_hidg *hidg = file->private_data;
+ struct report_entry *entry;
+ struct report_entry *ptr;
+ unsigned long flags;
+ struct uhid_get_report_req report;
+ u16 size;
+
+ switch (cmd) {
+ case GADGET_ADD_REPORT_STATUS:
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ if (copy_from_user(&entry->report_data, (struct uhid_set_report_req *)arg,
+ sizeof(struct uhid_set_report_req))) {
+ kfree(entry);
+ return -EFAULT;
+ }
+
+ if (f_hidg_param_valid(entry) == false) {
+ kfree(entry);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ ptr = f_hidg_search_for_report(hidg, entry->report_data.rnum,
+ entry->report_data.rtype);
+ if (ptr) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ kfree(ptr);
+ return -EEXIST;
+ }
+ list_add_tail(&entry->node, &hidg->report_list);
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ break;
+ case GADGET_REMOVE_REPORT_STATUS:
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ if (list_empty(&hidg->report_list)) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+
+ if (copy_from_user(&report, (struct uhid_get_report_req *)arg, sizeof(report)))
+ return -EFAULT;
+
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+
+ ptr = f_hidg_search_for_report(hidg, report.rnum, report.rtype);
+ if (ptr) {
+ list_del(&ptr->node);
+ } else {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ kfree(ptr);
+ break;
+ case GADGET_UPDATE_REPORT_STATUS:
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ if (list_empty(&hidg->report_list)) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+
+ if (copy_from_user(&report, (struct uhid_get_report_req *)arg, sizeof(report)))
+ return -EFAULT;
+
+ if (copy_from_user(&size, (void __user *)(arg + sizeof(report)), sizeof(size)))
+ return -EFAULT;
+
+ if (size > UHID_DATA_MAX)
+ return -EINVAL;
+
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+
+ ptr = f_hidg_search_for_report(hidg, report.rnum, report.rtype);
+ if (!ptr) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+
+ if (copy_from_user(&ptr->report_data, (struct uhid_set_report_req *)arg,
+ sizeof(struct uhid_set_report_req))) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -EFAULT;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ break;
+ }
+ return 0;
+}
+
/*-------------------------------------------------------------------------*/
/* usb_function */

@@ -634,6 +774,8 @@ static int hidg_setup(struct usb_function *f,
struct f_hidg *hidg = func_to_hidg(f);
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = cdev->req;
+ struct report_entry *entry;
+ unsigned long flags;
int status = 0;
__u16 value, length;

@@ -649,9 +791,25 @@ static int hidg_setup(struct usb_function *f,
| 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);
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ if (list_empty(&hidg->report_list)) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ memset(req->buf, 0x0, length);
+ goto respond;
+ }
+
+ entry = f_hidg_search_for_report(hidg, value & 0xf,
+ value >> 8);
+
+ /* send a report */
+ if (entry) {
+ length = min_t(unsigned, length, entry->report_data.size);
+ memcpy(req->buf, entry->report_data.data, length);
+ } else {
+ memset(req->buf, 0x0, length);
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);

goto respond;
break;
@@ -893,6 +1051,7 @@ static const struct file_operations f_hidg_fops = {
.owner = THIS_MODULE,
.open = f_hidg_open,
.release = f_hidg_release,
+ .unlocked_ioctl = f_hidg_ioctl,
.write = f_hidg_write,
.read = f_hidg_read,
.poll = f_hidg_poll,
@@ -997,6 +1156,9 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
init_waitqueue_head(&hidg->read_queue);
INIT_LIST_HEAD(&hidg->completed_out_req);

+ spin_lock_init(&hidg->report_spinlock);
+ INIT_LIST_HEAD(&hidg->report_list);
+
/* create char device */
cdev_init(&hidg->cdev, &f_hidg_fops);
dev = MKDEV(major, hidg->minor);
diff --git a/include/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h
similarity index 72%
rename from include/linux/usb/g_hid.h
rename to include/uapi/linux/usb/g_hid.h
index 7581e488c237..ba3e47f076bb 100644
--- a/include/linux/usb/g_hid.h
+++ b/include/uapi/linux/usb/g_hid.h
@@ -22,6 +22,8 @@
#ifndef __LINUX_USB_G_HID_H
#define __LINUX_USB_G_HID_H

+#include <linux/uhid.h>
+
struct hidg_func_descriptor {
unsigned char subclass;
unsigned char protocol;
@@ -30,4 +32,12 @@ struct hidg_func_descriptor {
unsigned char report_desc[];
};

+/* The 'g' code is also used by gadgetfs and printer ioctl requests.
+ * Don't add any colliding codes to either driver, and keep
+ * them in unique ranges (size 0x40 for now).
+ */
+#define GADGET_ADD_REPORT_STATUS _IOWR('g', 0x41, struct uhid_set_report_req)
+#define GADGET_REMOVE_REPORT_STATUS _IOWR('g', 0x42, struct uhid_get_report_req)
+#define GADGET_UPDATE_REPORT_STATUS _IOWR('g', 0x43, struct uhid_set_report_req)
+
#endif /* __LINUX_USB_G_HID_H */
--
2.25.1