usb: gadget: hid: allow dynamic interval configuration via configfs

This patch enhances the HID gadget driver to support dynamic configuration
of the interrupt polling interval (bInterval) via configfs.  A new
‘interval’ attribute is exposed under each HID function’s configfs
directory, and any write to it will adjust the poll rate for all endpoints
without requiring a rebuild.

When the attribute has never been written, legacy defaults are preserved:
  • Full-Speed (FS) endpoints (IN & OUT) poll every 10 ms
  • High-Speed (HS) endpoints (IN & OUT) poll every 4 micro-frames
    (~1 ms)

To implement this cleanly:
  • Add two new fields to f_hid_opts and f_hidg:
      – unsigned char interval
      – bool           interval_user_set
  • Introduce dedicated f_hid_opts_interval_show/store functions.
    The store routine parses into an unsigned int, bounds‐checks,
    assigns to opts->interval, and sets opts->interval_user_set = true.
  • Initialize opts->interval = 4 and opts->interval_user_set = false in
    hidg_alloc_inst(), then copy both into the live f_hidg instance in
    hidg_alloc().
  • In hidg_bind(), set each endpoint’s bInterval based on whether the
  user has written the attribute:
      – If interval_user_set == false, use FS=10 / HS=4
      – If interval_user_set == true, use the user’s value for both FS
        & HS

Signed-off-by: Ben Hoff <hoff.benjamin.k@gmail.com>
Link: https://lore.kernel.org/r/20250429182809.811786-1-hoff.benjamin.k@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Ben Hoff
2025-04-29 14:28:09 -04:00
committed by Greg Kroah-Hartman
parent 3fc0810497
commit ea34925f5b
2 changed files with 90 additions and 31 deletions

View File

@@ -62,6 +62,9 @@ struct f_hidg {
unsigned short report_desc_length;
char *report_desc;
unsigned short report_length;
unsigned char interval;
bool interval_user_set;
/*
* use_out_ep - if true, the OUT Endpoint (interrupt out method)
* will be used to receive reports from the host
@@ -157,10 +160,7 @@ static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = {
.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)
*/
/*.bInterval = DYNAMIC */
};
static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = {
@@ -178,10 +178,7 @@ static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = {
.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)
*/
/*.bInterval = DYNAMIC */
};
static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = {
@@ -219,10 +216,7 @@ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = {
.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)
*/
/* .bInterval = DYNAMIC */
};
static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = {
@@ -231,10 +225,7 @@ static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = {
.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)
*/
/*.bInterval = DYNAMIC */
};
static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = {
@@ -260,10 +251,7 @@ static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = {
.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)
*/
/*.bInterval = DYNAMIC */
};
static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = {
@@ -272,10 +260,7 @@ static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = {
.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)
*/
/*.bInterval = DYNAMIC */
};
static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = {
@@ -1217,6 +1202,16 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
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_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
/* IN endpoints: FS default=10ms, HS default=4µ-frame; user override if set */
if (!hidg->interval_user_set) {
hidg_fs_in_ep_desc.bInterval = 10;
hidg_hs_in_ep_desc.bInterval = 4;
} else {
hidg_fs_in_ep_desc.bInterval = hidg->interval;
hidg_hs_in_ep_desc.bInterval = hidg->interval;
}
hidg_ss_out_comp_desc.wBytesPerInterval =
cpu_to_le16(hidg->report_length);
hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
@@ -1239,19 +1234,27 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
hidg_ss_out_ep_desc.bEndpointAddress =
hidg_fs_out_ep_desc.bEndpointAddress;
if (hidg->use_out_ep)
if (hidg->use_out_ep) {
/* OUT endpoints: same defaults (FS=10, HS=4) unless user set */
if (!hidg->interval_user_set) {
hidg_fs_out_ep_desc.bInterval = 10;
hidg_hs_out_ep_desc.bInterval = 4;
} else {
hidg_fs_out_ep_desc.bInterval = hidg->interval;
hidg_hs_out_ep_desc.bInterval = hidg->interval;
}
status = usb_assign_descriptors(f,
hidg_fs_descriptors_intout,
hidg_hs_descriptors_intout,
hidg_ss_descriptors_intout,
hidg_ss_descriptors_intout);
else
hidg_fs_descriptors_intout,
hidg_hs_descriptors_intout,
hidg_ss_descriptors_intout,
hidg_ss_descriptors_intout);
} else {
status = usb_assign_descriptors(f,
hidg_fs_descriptors_ssreport,
hidg_hs_descriptors_ssreport,
hidg_ss_descriptors_ssreport,
hidg_ss_descriptors_ssreport);
}
if (status)
goto fail;
@@ -1423,6 +1426,53 @@ static ssize_t f_hid_opts_report_desc_store(struct config_item *item,
CONFIGFS_ATTR(f_hid_opts_, report_desc);
static ssize_t f_hid_opts_interval_show(struct config_item *item, char *page)
{
struct f_hid_opts *opts = to_f_hid_opts(item);
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d\n", opts->interval);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_hid_opts_interval_store(struct config_item *item,
const char *page, size_t len)
{
struct f_hid_opts *opts = to_f_hid_opts(item);
int ret;
unsigned int tmp;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
/* parse into a wider type first */
ret = kstrtouint(page, 0, &tmp);
if (ret)
goto end;
/* range-check against unsigned char max */
if (tmp > 255) {
ret = -EINVAL;
goto end;
}
opts->interval = (unsigned char)tmp;
opts->interval_user_set = true;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
CONFIGFS_ATTR(f_hid_opts_, interval);
static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page)
{
struct f_hid_opts *opts = to_f_hid_opts(item);
@@ -1437,6 +1487,7 @@ static struct configfs_attribute *hid_attrs[] = {
&f_hid_opts_attr_protocol,
&f_hid_opts_attr_no_out_endpoint,
&f_hid_opts_attr_report_length,
&f_hid_opts_attr_interval,
&f_hid_opts_attr_report_desc,
&f_hid_opts_attr_dev,
NULL,
@@ -1483,6 +1534,10 @@ static struct usb_function_instance *hidg_alloc_inst(void)
if (!opts)
return ERR_PTR(-ENOMEM);
mutex_init(&opts->lock);
opts->interval = 4;
opts->interval_user_set = false;
opts->func_inst.free_func_inst = hidg_free_inst;
ret = &opts->func_inst;
@@ -1561,6 +1616,8 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi)
hidg->bInterfaceProtocol = opts->protocol;
hidg->report_length = opts->report_length;
hidg->report_desc_length = opts->report_desc_length;
hidg->interval = opts->interval;
hidg->interval_user_set = opts->interval_user_set;
if (opts->report_desc) {
hidg->report_desc = kmemdup(opts->report_desc,
opts->report_desc_length,

View File

@@ -25,6 +25,8 @@ struct f_hid_opts {
unsigned short report_desc_length;
unsigned char *report_desc;
bool report_desc_alloc;
unsigned char interval;
bool interval_user_set;
/*
* Protect the data form concurrent access by read/write