From: Vicki Pfau <vi@endrift.com>
To: linux-usb@vger.kernel.org,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Benjamin Tissoires <benjamin.tissoires@redhat.com>,
Maxim Devaev <mdevaev@gmail.com>
Cc: Vicki Pfau <vi@endrift.com>, David Sands <david.sands@biamp.com>
Subject: [PATCH v2 3/3] USB: gadget: f_hid: Add Set-Feature report
Date: Wed, 17 Jan 2024 16:37:57 -0800 [thread overview]
Message-ID: <20240118003758.1870691-4-vi@endrift.com> (raw)
In-Reply-To: <20240118003758.1870691-1-vi@endrift.com>
While the HID gadget implementation has been sufficient for devices that only
use INTERRUPT transfers, the USB HID standard includes provisions for Set- and
Get-Feature report CONTROL transfers that go over endpoint 0. These were
previously impossible with the existing implementation, and would either send
an empty reply, or stall out.
As the feature is a standard part of USB HID, it stands to reason that devices
would use it, and that the HID gadget should support it. This patch adds
support for host-to-device Set-Feature reports through a new ioctl
interface to the hidg class dev nodes.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/usb/gadget/function/f_hid.c | 110 ++++++++++++++++++++++++--
include/uapi/linux/usb/gadget-ioctl.h | 1 +
2 files changed, 103 insertions(+), 8 deletions(-)
diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
index 5a097ea718e8..18e679dc52e3 100644
--- a/drivers/usb/gadget/function/f_hid.c
+++ b/drivers/usb/gadget/function/f_hid.c
@@ -76,6 +76,11 @@ struct f_hidg {
wait_queue_head_t write_queue;
struct usb_request *req;
+ /* set report */
+ struct list_head completed_set_req;
+ spinlock_t set_spinlock;
+ wait_queue_head_t set_queue;
+
/* get report */
struct usb_request *get_req;
struct usb_hidg_report get_report;
@@ -532,6 +537,54 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
return status;
}
+static int f_hidg_set_report(struct file *file, struct usb_hidg_report __user *buffer)
+{
+ struct f_hidg *hidg = file->private_data;
+ struct f_hidg_req_list *list;
+ struct usb_request *req;
+ unsigned long flags;
+ unsigned short length;
+ int status;
+
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
+
+#define SET_REPORT_COND (!list_empty(&hidg->completed_set_req))
+
+ /* wait for at least one buffer to complete */
+ while (!SET_REPORT_COND) {
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ if (wait_event_interruptible(hidg->set_queue, SET_REPORT_COND))
+ return -ERESTARTSYS;
+
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
+ }
+
+ /* pick the first one */
+ list = list_first_entry(&hidg->completed_set_req,
+ struct f_hidg_req_list, list);
+
+ /*
+ * Remove this from list to protect it from being free()
+ * while host disables our function
+ */
+ list_del(&list->list);
+
+ req = list->req;
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
+
+ /* copy to user outside spinlock */
+ length = min_t(unsigned short, sizeof(buffer->data), req->actual);
+ status = copy_to_user(&buffer->length, &length, sizeof(buffer->length));
+ if (!status) {
+ status = copy_to_user(&buffer->data, req->buf, length);
+ }
+ kfree(list);
+ free_ep_req(hidg->func.config->cdev->gadget->ep0, req);
+ return status;
+}
static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer)
{
@@ -583,6 +636,8 @@ static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *b
static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg)
{
switch (code) {
+ case GADGET_HID_READ_SET_REPORT:
+ return f_hidg_set_report(file, (struct usb_hidg_report __user *)arg);
case GADGET_HID_WRITE_GET_REPORT:
return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg);
default:
@@ -597,6 +652,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
poll_wait(file, &hidg->read_queue, wait);
poll_wait(file, &hidg->write_queue, wait);
+ poll_wait(file, &hidg->set_queue, wait);
if (WRITE_COND)
ret |= EPOLLOUT | EPOLLWRNORM;
@@ -609,12 +665,16 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
ret |= EPOLLIN | EPOLLRDNORM;
}
+ if (SET_REPORT_COND)
+ ret |= EPOLLPRI;
+
return ret;
}
#undef WRITE_COND
#undef READ_COND_SSREPORT
#undef READ_COND_INTOUT
+#undef SET_REPORT_COND
#undef GET_REPORT_COND
static int f_hidg_release(struct inode *inode, struct file *fd)
@@ -659,11 +719,19 @@ static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req)
req_list->req = req;
- spin_lock_irqsave(&hidg->read_spinlock, flags);
- list_add_tail(&req_list->list, &hidg->completed_out_req);
- spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+ if (ep == cdev->gadget->ep0) {
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
+ list_add_tail(&req_list->list, &hidg->completed_set_req);
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
- wake_up(&hidg->read_queue);
+ wake_up(&hidg->set_queue);
+ } else {
+ spin_lock_irqsave(&hidg->read_spinlock, flags);
+ list_add_tail(&req_list->list, &hidg->completed_out_req);
+ spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+ wake_up(&hidg->read_queue);
+ }
break;
default:
ERROR(cdev, "Set report failed %d\n", req->status);
@@ -776,12 +844,27 @@ static int hidg_setup(struct usb_function *f,
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
| HID_REQ_SET_REPORT):
VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength);
- if (hidg->use_out_ep)
+ if (!hidg->use_out_ep) {
+ req->complete = hidg_ssreport_complete;
+ req->context = hidg;
+ goto respond;
+ }
+ if (!length)
goto stall;
- req->complete = hidg_ssreport_complete;
+ req = alloc_ep_req(cdev->gadget->ep0, GFP_ATOMIC);
+ if (!req)
+ return -ENOMEM;
+ req->complete = hidg_intout_complete;
req->context = hidg;
- goto respond;
- break;
+ 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 set_report %d\n", status);
+ free_ep_req(cdev->gadget->ep0, req);
+ }
+
+ return status;
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
| HID_REQ_SET_PROTOCOL):
@@ -881,6 +964,14 @@ static void hidg_disable(struct usb_function *f)
spin_unlock_irqrestore(&hidg->read_spinlock, flags);
}
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
+ list_for_each_entry_safe(list, next, &hidg->completed_set_req, list) {
+ free_ep_req(f->config->cdev->gadget->ep0, list->req);
+ list_del(&list->list);
+ kfree(list);
+ }
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
+
spin_lock_irqsave(&hidg->write_spinlock, flags);
if (!hidg->write_pending) {
free_ep_req(hidg->in_ep, hidg->req);
@@ -1109,11 +1200,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
hidg->write_pending = 1;
hidg->req = NULL;
spin_lock_init(&hidg->read_spinlock);
+ spin_lock_init(&hidg->set_spinlock);
spin_lock_init(&hidg->get_spinlock);
init_waitqueue_head(&hidg->write_queue);
init_waitqueue_head(&hidg->read_queue);
+ init_waitqueue_head(&hidg->set_queue);
init_waitqueue_head(&hidg->get_queue);
INIT_LIST_HEAD(&hidg->completed_out_req);
+ INIT_LIST_HEAD(&hidg->completed_set_req);
/* create char device */
cdev_init(&hidg->cdev, &f_hidg_fops);
diff --git a/include/uapi/linux/usb/gadget-ioctl.h b/include/uapi/linux/usb/gadget-ioctl.h
index 81f488643b53..f447699148e4 100644
--- a/include/uapi/linux/usb/gadget-ioctl.h
+++ b/include/uapi/linux/usb/gadget-ioctl.h
@@ -26,6 +26,7 @@
#define GADGETFS_CLEAR_HALT _IO('g', 3)
/* g_hid ioctls */
+#define GADGET_HID_READ_SET_REPORT _IOR('g', 0x41, struct usb_hidg_report)
#define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report)
/* g_printer ioctls */
--
2.43.0
prev parent reply other threads:[~2024-01-18 0:38 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-01-18 0:37 [PATCH v2 0/3] USB: gadget: f_hid: Add Feature reports Vicki Pfau
2024-01-18 0:37 ` [PATCH v2 1/3] USB: gadget: Move gadget-related ioctl codes to gadget-ioctl.h Vicki Pfau
2024-01-28 1:34 ` Greg Kroah-Hartman
2024-01-18 0:37 ` [PATCH v2 2/3] USB: gadget: f_hid: Add Get-Feature report Vicki Pfau
2024-01-18 0:37 ` Vicki Pfau [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240118003758.1870691-4-vi@endrift.com \
--to=vi@endrift.com \
--cc=benjamin.tissoires@redhat.com \
--cc=david.sands@biamp.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-usb@vger.kernel.org \
--cc=mdevaev@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox