Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

USB: gadget: f_hid: Add GET_REPORT via userspace IOCTL

While supporting GET_REPORT is a mandatory request per the HID
specification the current implementation of the GET_REPORT request responds
to the USB Host with an empty reply of the request length. However, some
USB Hosts will request the contents of feature reports via the GET_REPORT
request. In addition, some proprietary HID 'protocols' will expect
different data, for the same report ID, to be to become available in the
feature report by sending a preceding SET_REPORT to the USB Device that
defines what data is to be presented when that feature report is
subsequently retrieved via GET_REPORT (with a very fast < 5ms turn around
between the SET_REPORT and the GET_REPORT).

There are two other patch sets already submitted for adding GET_REPORT
support. The first [1] allows for pre-priming a list of reports via IOCTLs
which then allows the USB Host to perform the request, with no further
userspace interaction possible during the GET_REPORT request. And another
[2] which allows for a single report to be setup by userspace via IOCTL,
which will be fetched and returned by the kernel for subsequent GET_REPORT
requests by the USB Host, also with no further userspace interaction
possible.

This patch, while loosely based on both the patch sets, differs by allowing
the option for userspace to respond to each GET_REPORT request by setting
up a poll to notify userspace that a new GET_REPORT request has arrived. To
support this, two extra IOCTLs are supplied. The first of which is used to
retrieve the report ID of the GET_REPORT request (in the case of having
non-zero report IDs in the HID descriptor). The second IOCTL allows for
storing report responses in a list for responding to requests.

The report responses are stored in a list (it will be either added if it
does not exist or updated if it exists already). A flag (userspace_req) can
be set to whether subsequent requests notify userspace or not.

Basic operation when a GET_REPORT request arrives from USB Host:

- If the report ID exists in the list and it is set for immediate return
(i.e. userspace_req == false) then response is sent immediately,
userspace is not notified

- The report ID does not exist, or exists but is set to notify userspace
(i.e. userspace_req == true) then notify userspace via poll:

- If userspace responds, and either adds or update the response in
the list and respond to the host with the contents

- If userspace does not respond within the fixed timeout (2500ms)
but the report has been set prevously, then send 'old' report
contents

- If userspace does not respond within the fixed timeout (2500ms)
and the report does not exist in the list then send an empty
report

Note that userspace could 'prime' the report list at any other time.

While this patch allows for flexibility in how the system responds to
requests, and therefore the HID 'protocols' that could be supported, a
drawback is the time it takes to service the requests and therefore the
maximum throughput that would be achievable. The USB HID Specification
v1.11 itself states that GET_REPORT is not intended for periodic data
polling, so this limitation is not severe.

Testing on an iMX8M Nano Ultra Lite with a heavy multi-core CPU loading
showed that userspace can typically respond to the GET_REPORT request
within 1200ms - which is well within the 5000ms most operating systems seem
to allow, and within the 2500ms set by this patch.

[1] https://lore.kernel.org/all/20220805070507.123151-2-sunil@amarulasolutions.com/
[2] https://lore.kernel.org/all/20220726005824.2817646-1-vi@endrift.com/

Signed-off-by: David Sands <david.sands@biamp.com>
Signed-off-by: Chris Wulff <chris.wulff@biamp.com>
Link: https://lore.kernel.org/r/20240817142850.1311460-2-crwulff@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Chris Wulff and committed by
Greg Kroah-Hartman
a139c98f db63d986

+307 -8
+266 -7
drivers/usb/gadget/function/f_hid.c
··· 15 15 #include <linux/uaccess.h> 16 16 #include <linux/wait.h> 17 17 #include <linux/sched.h> 18 + #include <linux/workqueue.h> 18 19 #include <linux/usb/g_hid.h> 20 + #include <uapi/linux/usb/g_hid.h> 19 21 20 22 #include "u_f.h" 21 23 #include "u_hid.h" 22 24 23 25 #define HIDG_MINORS 4 26 + 27 + /* 28 + * Most operating systems seem to allow for 5000ms timeout, we will allow 29 + * userspace half that time to respond before we return an empty report. 30 + */ 31 + #define GET_REPORT_TIMEOUT_MS 2500 24 32 25 33 static int major, minors; 26 34 ··· 38 30 39 31 static DEFINE_IDA(hidg_ida); 40 32 static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */ 33 + 34 + struct report_entry { 35 + struct usb_hidg_report report_data; 36 + struct list_head node; 37 + }; 41 38 42 39 /*-------------------------------------------------------------------------*/ 43 40 /* HID gadget struct */ ··· 87 74 bool write_pending; 88 75 wait_queue_head_t write_queue; 89 76 struct usb_request *req; 77 + 78 + /* get report */ 79 + struct usb_request *get_req; 80 + struct usb_hidg_report get_report; 81 + bool get_report_returned; 82 + int get_report_req_report_id; 83 + int get_report_req_report_length; 84 + spinlock_t get_report_spinlock; 85 + wait_queue_head_t get_queue; /* Waiting for userspace response */ 86 + wait_queue_head_t get_id_queue; /* Get ID came in */ 87 + struct work_struct work; 88 + struct workqueue_struct *workqueue; 89 + struct list_head report_list; 90 90 91 91 struct device dev; 92 92 struct cdev cdev; ··· 550 524 return status; 551 525 } 552 526 527 + static struct report_entry *f_hidg_search_for_report(struct f_hidg *hidg, u8 report_id) 528 + { 529 + struct list_head *ptr; 530 + struct report_entry *entry; 531 + 532 + list_for_each(ptr, &hidg->report_list) { 533 + entry = list_entry(ptr, struct report_entry, node); 534 + if (entry->report_data.report_id == report_id) 535 + return entry; 536 + } 537 + 538 + return NULL; 539 + } 540 + 541 + static void get_report_workqueue_handler(struct work_struct *work) 542 + { 543 + struct f_hidg *hidg = container_of(work, struct f_hidg, work); 544 + struct usb_composite_dev *cdev = hidg->func.config->cdev; 545 + struct usb_request *req; 546 + struct report_entry *ptr; 547 + unsigned long flags; 548 + 549 + int status = 0; 550 + 551 + spin_lock_irqsave(&hidg->get_report_spinlock, flags); 552 + req = hidg->get_req; 553 + if (!req) { 554 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 555 + return; 556 + } 557 + 558 + req->zero = 0; 559 + req->length = min_t(unsigned int, min_t(unsigned int, hidg->get_report_req_report_length, 560 + hidg->report_length), 561 + MAX_REPORT_LENGTH); 562 + 563 + /* Check if there is a response available for immediate response */ 564 + ptr = f_hidg_search_for_report(hidg, hidg->get_report_req_report_id); 565 + if (ptr && !ptr->report_data.userspace_req) { 566 + /* Report exists in list and it is to be used for immediate response */ 567 + req->buf = ptr->report_data.data; 568 + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); 569 + hidg->get_report_returned = true; 570 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 571 + } else { 572 + /* 573 + * Report does not exist in list or should not be immediately sent 574 + * i.e. give userspace time to respond 575 + */ 576 + hidg->get_report_returned = false; 577 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 578 + wake_up(&hidg->get_id_queue); 579 + #define GET_REPORT_COND (!hidg->get_report_returned) 580 + /* Wait until userspace has responded or timeout */ 581 + status = wait_event_interruptible_timeout(hidg->get_queue, !GET_REPORT_COND, 582 + msecs_to_jiffies(GET_REPORT_TIMEOUT_MS)); 583 + spin_lock_irqsave(&hidg->get_report_spinlock, flags); 584 + req = hidg->get_req; 585 + if (!req) { 586 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 587 + return; 588 + } 589 + if (status == 0 && !hidg->get_report_returned) { 590 + /* GET_REPORT request was not serviced by userspace within timeout period */ 591 + VDBG(cdev, "get_report : userspace timeout.\n"); 592 + hidg->get_report_returned = true; 593 + } 594 + 595 + /* Search again for report ID in list and respond to GET_REPORT request */ 596 + ptr = f_hidg_search_for_report(hidg, hidg->get_report_req_report_id); 597 + if (ptr) { 598 + /* 599 + * Either get an updated response just serviced by userspace 600 + * or send the latest response in the list 601 + */ 602 + req->buf = ptr->report_data.data; 603 + } else { 604 + /* If there are no prevoiusly sent reports send empty report */ 605 + req->buf = hidg->get_report.data; 606 + memset(req->buf, 0x0, req->length); 607 + } 608 + 609 + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); 610 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 611 + } 612 + 613 + if (status < 0) 614 + VDBG(cdev, "usb_ep_queue error on ep0 responding to GET_REPORT\n"); 615 + } 616 + 617 + static int f_hidg_get_report_id(struct file *file, __u8 __user *buffer) 618 + { 619 + struct f_hidg *hidg = file->private_data; 620 + int ret = 0; 621 + 622 + ret = put_user(hidg->get_report_req_report_id, buffer); 623 + 624 + return ret; 625 + } 626 + 627 + static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer) 628 + { 629 + struct f_hidg *hidg = file->private_data; 630 + struct usb_composite_dev *cdev = hidg->func.config->cdev; 631 + unsigned long flags; 632 + struct report_entry *entry; 633 + struct report_entry *ptr; 634 + __u8 report_id; 635 + 636 + entry = kmalloc(sizeof(*entry), GFP_KERNEL); 637 + if (!entry) 638 + return -ENOMEM; 639 + 640 + if (copy_from_user(&entry->report_data, buffer, 641 + sizeof(struct usb_hidg_report))) { 642 + ERROR(cdev, "copy_from_user error\n"); 643 + kfree(entry); 644 + return -EINVAL; 645 + } 646 + 647 + report_id = entry->report_data.report_id; 648 + 649 + spin_lock_irqsave(&hidg->get_report_spinlock, flags); 650 + ptr = f_hidg_search_for_report(hidg, report_id); 651 + 652 + if (ptr) { 653 + /* Report already exists in list - update it */ 654 + if (copy_from_user(&ptr->report_data, buffer, 655 + sizeof(struct usb_hidg_report))) { 656 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 657 + ERROR(cdev, "copy_from_user error\n"); 658 + kfree(entry); 659 + return -EINVAL; 660 + } 661 + kfree(entry); 662 + } else { 663 + /* Report does not exist in list - add it */ 664 + list_add_tail(&entry->node, &hidg->report_list); 665 + } 666 + 667 + /* If there is no response pending then do nothing further */ 668 + if (hidg->get_report_returned) { 669 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 670 + return 0; 671 + } 672 + 673 + /* If this userspace response serves the current pending report */ 674 + if (hidg->get_report_req_report_id == report_id) { 675 + hidg->get_report_returned = true; 676 + wake_up(&hidg->get_queue); 677 + } 678 + 679 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 680 + return 0; 681 + } 682 + 683 + static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg) 684 + { 685 + switch (code) { 686 + case GADGET_HID_READ_GET_REPORT_ID: 687 + return f_hidg_get_report_id(file, (__u8 __user *)arg); 688 + case GADGET_HID_WRITE_GET_REPORT: 689 + return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg); 690 + default: 691 + return -ENOTTY; 692 + } 693 + } 694 + 553 695 static __poll_t f_hidg_poll(struct file *file, poll_table *wait) 554 696 { 555 697 struct f_hidg *hidg = file->private_data; ··· 725 531 726 532 poll_wait(file, &hidg->read_queue, wait); 727 533 poll_wait(file, &hidg->write_queue, wait); 534 + poll_wait(file, &hidg->get_queue, wait); 535 + poll_wait(file, &hidg->get_id_queue, wait); 728 536 729 537 if (WRITE_COND) 730 538 ret |= EPOLLOUT | EPOLLWRNORM; ··· 739 543 ret |= EPOLLIN | EPOLLRDNORM; 740 544 } 741 545 546 + if (GET_REPORT_COND) 547 + ret |= EPOLLPRI; 548 + 742 549 return ret; 743 550 } 744 551 745 552 #undef WRITE_COND 746 553 #undef READ_COND_SSREPORT 747 554 #undef READ_COND_INTOUT 555 + #undef GET_REPORT_COND 748 556 749 557 static int f_hidg_release(struct inode *inode, struct file *fd) 750 558 { ··· 841 641 wake_up(&hidg->read_queue); 842 642 } 843 643 644 + static void hidg_get_report_complete(struct usb_ep *ep, struct usb_request *req) 645 + { 646 + } 647 + 844 648 static int hidg_setup(struct usb_function *f, 845 649 const struct usb_ctrlrequest *ctrl) 846 650 { ··· 853 649 struct usb_request *req = cdev->req; 854 650 int status = 0; 855 651 __u16 value, length; 652 + unsigned long flags; 856 653 857 654 value = __le16_to_cpu(ctrl->wValue); 858 655 length = __le16_to_cpu(ctrl->wLength); ··· 865 660 switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { 866 661 case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 867 662 | HID_REQ_GET_REPORT): 868 - VDBG(cdev, "get_report\n"); 663 + VDBG(cdev, "get_report | wLength=%d\n", ctrl->wLength); 869 664 870 - /* send an empty report */ 871 - length = min_t(unsigned, length, hidg->report_length); 872 - memset(req->buf, 0x0, length); 665 + /* 666 + * Update GET_REPORT ID so that an ioctl can be used to determine what 667 + * GET_REPORT the request was actually for. 668 + */ 669 + spin_lock_irqsave(&hidg->get_report_spinlock, flags); 670 + hidg->get_report_req_report_id = value & 0xff; 671 + hidg->get_report_req_report_length = length; 672 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 873 673 874 - goto respond; 875 - break; 674 + queue_work(hidg->workqueue, &hidg->work); 675 + 676 + return status; 876 677 877 678 case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 878 679 | HID_REQ_GET_PROTOCOL): ··· 1004 793 spin_unlock_irqrestore(&hidg->read_spinlock, flags); 1005 794 } 1006 795 796 + spin_lock_irqsave(&hidg->get_report_spinlock, flags); 797 + if (!hidg->get_report_returned) { 798 + usb_ep_free_request(f->config->cdev->gadget->ep0, hidg->get_req); 799 + hidg->get_req = NULL; 800 + hidg->get_report_returned = true; 801 + } 802 + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); 803 + 1007 804 spin_lock_irqsave(&hidg->write_spinlock, flags); 1008 805 if (!hidg->write_pending) { 1009 806 free_ep_req(hidg->in_ep, hidg->req); ··· 1121 902 return status; 1122 903 } 1123 904 905 + #ifdef CONFIG_COMPAT 906 + static long f_hidg_compat_ioctl(struct file *file, unsigned int code, 907 + unsigned long value) 908 + { 909 + return f_hidg_ioctl(file, code, value); 910 + } 911 + #endif 912 + 1124 913 static const struct file_operations f_hidg_fops = { 1125 914 .owner = THIS_MODULE, 1126 915 .open = f_hidg_open, ··· 1136 909 .write = f_hidg_write, 1137 910 .read = f_hidg_read, 1138 911 .poll = f_hidg_poll, 912 + .unlocked_ioctl = f_hidg_ioctl, 913 + #ifdef CONFIG_COMPAT 914 + .compat_ioctl = f_hidg_compat_ioctl, 915 + #endif 1139 916 .llseek = noop_llseek, 1140 917 }; 1141 918 ··· 1149 918 struct f_hidg *hidg = func_to_hidg(f); 1150 919 struct usb_string *us; 1151 920 int status; 921 + 922 + hidg->get_req = usb_ep_alloc_request(c->cdev->gadget->ep0, GFP_ATOMIC); 923 + if (!hidg->get_req) 924 + return -ENOMEM; 925 + 926 + hidg->get_req->zero = 0; 927 + hidg->get_req->complete = hidg_get_report_complete; 928 + hidg->get_req->context = hidg; 929 + hidg->get_report_returned = true; 1152 930 1153 931 /* maybe allocate device-global string IDs, and patch descriptors */ 1154 932 us = usb_gstrings_attach(c->cdev, ct_func_strings, ··· 1244 1004 hidg->write_pending = 1; 1245 1005 hidg->req = NULL; 1246 1006 spin_lock_init(&hidg->read_spinlock); 1007 + spin_lock_init(&hidg->get_report_spinlock); 1247 1008 init_waitqueue_head(&hidg->write_queue); 1248 1009 init_waitqueue_head(&hidg->read_queue); 1010 + init_waitqueue_head(&hidg->get_queue); 1011 + init_waitqueue_head(&hidg->get_id_queue); 1249 1012 INIT_LIST_HEAD(&hidg->completed_out_req); 1013 + INIT_LIST_HEAD(&hidg->report_list); 1014 + 1015 + INIT_WORK(&hidg->work, get_report_workqueue_handler); 1016 + hidg->workqueue = alloc_workqueue("report_work", 1017 + WQ_FREEZABLE | 1018 + WQ_MEM_RECLAIM, 1019 + 1); 1020 + 1021 + if (!hidg->workqueue) { 1022 + status = -ENOMEM; 1023 + goto fail; 1024 + } 1250 1025 1251 1026 /* create char device */ 1252 1027 cdev_init(&hidg->cdev, &f_hidg_fops); ··· 1271 1016 1272 1017 return 0; 1273 1018 fail_free_descs: 1019 + destroy_workqueue(hidg->workqueue); 1274 1020 usb_free_all_descriptors(f); 1275 1021 fail: 1276 1022 ERROR(f->config->cdev, "hidg_bind FAILED\n"); 1277 1023 if (hidg->req != NULL) 1278 1024 free_ep_req(hidg->in_ep, hidg->req); 1025 + 1026 + usb_ep_free_request(c->cdev->gadget->ep0, hidg->get_req); 1027 + hidg->get_req = NULL; 1279 1028 1280 1029 return status; 1281 1030 } ··· 1515 1256 struct f_hidg *hidg = func_to_hidg(f); 1516 1257 1517 1258 cdev_device_del(&hidg->cdev, &hidg->dev); 1518 - 1259 + destroy_workqueue(hidg->workqueue); 1519 1260 usb_free_all_descriptors(f); 1520 1261 } 1521 1262
+40
include/uapi/linux/usb/g_hid.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ 2 + 3 + #ifndef __UAPI_LINUX_USB_G_HID_H 4 + #define __UAPI_LINUX_USB_G_HID_H 5 + 6 + #include <linux/types.h> 7 + 8 + /* Maximum HID report length for High-Speed USB (i.e. USB 2.0) */ 9 + #define MAX_REPORT_LENGTH 64 10 + 11 + /** 12 + * struct usb_hidg_report - response to GET_REPORT 13 + * @report_id: report ID that this is a response for 14 + * @userspace_req: 15 + * !0 this report is used for any pending GET_REPORT request 16 + * but wait on userspace to issue a new report on future requests 17 + * 0 this report is to be used for any future GET_REPORT requests 18 + * @length: length of the report response 19 + * @data: report response 20 + * @padding: padding for 32/64 bit compatibility 21 + * 22 + * Structure used by GADGET_HID_WRITE_GET_REPORT ioctl on /dev/hidg*. 23 + */ 24 + struct usb_hidg_report { 25 + __u8 report_id; 26 + __u8 userspace_req; 27 + __u16 length; 28 + __u8 data[MAX_REPORT_LENGTH]; 29 + __u8 padding[4]; 30 + }; 31 + 32 + /* The 'g' code is used by gadgetfs and hid gadget ioctl requests. 33 + * Don't add any colliding codes to either driver, and keep 34 + * them in unique ranges. 35 + */ 36 + 37 + #define GADGET_HID_READ_GET_REPORT_ID _IOR('g', 0x41, __u8) 38 + #define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report) 39 + 40 + #endif /* __UAPI_LINUX_USB_G_HID_H */
+1 -1
include/uapi/linux/usb/gadgetfs.h
··· 62 62 }; 63 63 64 64 65 - /* The 'g' code is also used by printer gadget ioctl requests. 65 + /* The 'g' code is also used by printer and hid gadget ioctl requests. 66 66 * Don't add any colliding codes to either driver, and keep 67 67 * them in unique ranges (size 0x20 for now). 68 68 */