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

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>

authored by

Ben Hoff and committed by
Greg Kroah-Hartman
ea34925f 3fc08104

+90 -31
+88 -31
drivers/usb/gadget/function/f_hid.c
··· 62 62 unsigned short report_desc_length; 63 63 char *report_desc; 64 64 unsigned short report_length; 65 + unsigned char interval; 66 + bool interval_user_set; 67 + 65 68 /* 66 69 * use_out_ep - if true, the OUT Endpoint (interrupt out method) 67 70 * will be used to receive reports from the host ··· 160 157 .bEndpointAddress = USB_DIR_IN, 161 158 .bmAttributes = USB_ENDPOINT_XFER_INT, 162 159 /*.wMaxPacketSize = DYNAMIC */ 163 - .bInterval = 4, /* FIXME: Add this field in the 164 - * HID gadget configuration? 165 - * (struct hidg_func_descriptor) 166 - */ 160 + /*.bInterval = DYNAMIC */ 167 161 }; 168 162 169 163 static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = { ··· 178 178 .bEndpointAddress = USB_DIR_OUT, 179 179 .bmAttributes = USB_ENDPOINT_XFER_INT, 180 180 /*.wMaxPacketSize = DYNAMIC */ 181 - .bInterval = 4, /* FIXME: Add this field in the 182 - * HID gadget configuration? 183 - * (struct hidg_func_descriptor) 184 - */ 181 + /*.bInterval = DYNAMIC */ 185 182 }; 186 183 187 184 static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = { ··· 216 219 .bEndpointAddress = USB_DIR_IN, 217 220 .bmAttributes = USB_ENDPOINT_XFER_INT, 218 221 /*.wMaxPacketSize = DYNAMIC */ 219 - .bInterval = 4, /* FIXME: Add this field in the 220 - * HID gadget configuration? 221 - * (struct hidg_func_descriptor) 222 - */ 222 + /* .bInterval = DYNAMIC */ 223 223 }; 224 224 225 225 static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { ··· 225 231 .bEndpointAddress = USB_DIR_OUT, 226 232 .bmAttributes = USB_ENDPOINT_XFER_INT, 227 233 /*.wMaxPacketSize = DYNAMIC */ 228 - .bInterval = 4, /* FIXME: Add this field in the 229 - * HID gadget configuration? 230 - * (struct hidg_func_descriptor) 231 - */ 234 + /*.bInterval = DYNAMIC */ 232 235 }; 233 236 234 237 static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = { ··· 251 260 .bEndpointAddress = USB_DIR_IN, 252 261 .bmAttributes = USB_ENDPOINT_XFER_INT, 253 262 /*.wMaxPacketSize = DYNAMIC */ 254 - .bInterval = 10, /* FIXME: Add this field in the 255 - * HID gadget configuration? 256 - * (struct hidg_func_descriptor) 257 - */ 263 + /*.bInterval = DYNAMIC */ 258 264 }; 259 265 260 266 static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { ··· 260 272 .bEndpointAddress = USB_DIR_OUT, 261 273 .bmAttributes = USB_ENDPOINT_XFER_INT, 262 274 /*.wMaxPacketSize = DYNAMIC */ 263 - .bInterval = 10, /* FIXME: Add this field in the 264 - * HID gadget configuration? 265 - * (struct hidg_func_descriptor) 266 - */ 275 + /*.bInterval = DYNAMIC */ 267 276 }; 268 277 269 278 static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = { ··· 1202 1217 hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); 1203 1218 hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); 1204 1219 hidg_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); 1220 + 1221 + /* IN endpoints: FS default=10ms, HS default=4µ-frame; user override if set */ 1222 + if (!hidg->interval_user_set) { 1223 + hidg_fs_in_ep_desc.bInterval = 10; 1224 + hidg_hs_in_ep_desc.bInterval = 4; 1225 + } else { 1226 + hidg_fs_in_ep_desc.bInterval = hidg->interval; 1227 + hidg_hs_in_ep_desc.bInterval = hidg->interval; 1228 + } 1229 + 1205 1230 hidg_ss_out_comp_desc.wBytesPerInterval = 1206 1231 cpu_to_le16(hidg->report_length); 1207 1232 hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); ··· 1234 1239 hidg_ss_out_ep_desc.bEndpointAddress = 1235 1240 hidg_fs_out_ep_desc.bEndpointAddress; 1236 1241 1237 - if (hidg->use_out_ep) 1242 + if (hidg->use_out_ep) { 1243 + /* OUT endpoints: same defaults (FS=10, HS=4) unless user set */ 1244 + if (!hidg->interval_user_set) { 1245 + hidg_fs_out_ep_desc.bInterval = 10; 1246 + hidg_hs_out_ep_desc.bInterval = 4; 1247 + } else { 1248 + hidg_fs_out_ep_desc.bInterval = hidg->interval; 1249 + hidg_hs_out_ep_desc.bInterval = hidg->interval; 1250 + } 1238 1251 status = usb_assign_descriptors(f, 1239 - hidg_fs_descriptors_intout, 1240 - hidg_hs_descriptors_intout, 1241 - hidg_ss_descriptors_intout, 1242 - hidg_ss_descriptors_intout); 1243 - else 1252 + hidg_fs_descriptors_intout, 1253 + hidg_hs_descriptors_intout, 1254 + hidg_ss_descriptors_intout, 1255 + hidg_ss_descriptors_intout); 1256 + } else { 1244 1257 status = usb_assign_descriptors(f, 1245 1258 hidg_fs_descriptors_ssreport, 1246 1259 hidg_hs_descriptors_ssreport, 1247 1260 hidg_ss_descriptors_ssreport, 1248 1261 hidg_ss_descriptors_ssreport); 1249 - 1262 + } 1250 1263 if (status) 1251 1264 goto fail; 1252 1265 ··· 1426 1423 1427 1424 CONFIGFS_ATTR(f_hid_opts_, report_desc); 1428 1425 1426 + static ssize_t f_hid_opts_interval_show(struct config_item *item, char *page) 1427 + { 1428 + struct f_hid_opts *opts = to_f_hid_opts(item); 1429 + int result; 1430 + 1431 + mutex_lock(&opts->lock); 1432 + result = sprintf(page, "%d\n", opts->interval); 1433 + mutex_unlock(&opts->lock); 1434 + 1435 + return result; 1436 + } 1437 + 1438 + static ssize_t f_hid_opts_interval_store(struct config_item *item, 1439 + const char *page, size_t len) 1440 + { 1441 + struct f_hid_opts *opts = to_f_hid_opts(item); 1442 + int ret; 1443 + unsigned int tmp; 1444 + 1445 + mutex_lock(&opts->lock); 1446 + if (opts->refcnt) { 1447 + ret = -EBUSY; 1448 + goto end; 1449 + } 1450 + 1451 + /* parse into a wider type first */ 1452 + ret = kstrtouint(page, 0, &tmp); 1453 + if (ret) 1454 + goto end; 1455 + 1456 + /* range-check against unsigned char max */ 1457 + if (tmp > 255) { 1458 + ret = -EINVAL; 1459 + goto end; 1460 + } 1461 + 1462 + opts->interval = (unsigned char)tmp; 1463 + opts->interval_user_set = true; 1464 + ret = len; 1465 + 1466 + end: 1467 + mutex_unlock(&opts->lock); 1468 + return ret; 1469 + } 1470 + 1471 + CONFIGFS_ATTR(f_hid_opts_, interval); 1472 + 1429 1473 static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page) 1430 1474 { 1431 1475 struct f_hid_opts *opts = to_f_hid_opts(item); ··· 1487 1437 &f_hid_opts_attr_protocol, 1488 1438 &f_hid_opts_attr_no_out_endpoint, 1489 1439 &f_hid_opts_attr_report_length, 1440 + &f_hid_opts_attr_interval, 1490 1441 &f_hid_opts_attr_report_desc, 1491 1442 &f_hid_opts_attr_dev, 1492 1443 NULL, ··· 1534 1483 if (!opts) 1535 1484 return ERR_PTR(-ENOMEM); 1536 1485 mutex_init(&opts->lock); 1486 + 1487 + opts->interval = 4; 1488 + opts->interval_user_set = false; 1489 + 1537 1490 opts->func_inst.free_func_inst = hidg_free_inst; 1538 1491 ret = &opts->func_inst; 1539 1492 ··· 1616 1561 hidg->bInterfaceProtocol = opts->protocol; 1617 1562 hidg->report_length = opts->report_length; 1618 1563 hidg->report_desc_length = opts->report_desc_length; 1564 + hidg->interval = opts->interval; 1565 + hidg->interval_user_set = opts->interval_user_set; 1619 1566 if (opts->report_desc) { 1620 1567 hidg->report_desc = kmemdup(opts->report_desc, 1621 1568 opts->report_desc_length,
+2
drivers/usb/gadget/function/u_hid.h
··· 25 25 unsigned short report_desc_length; 26 26 unsigned char *report_desc; 27 27 bool report_desc_alloc; 28 + unsigned char interval; 29 + bool interval_user_set; 28 30 29 31 /* 30 32 * Protect the data form concurrent access by read/write