Commit ea34925f authored by Ben Hoff's avatar Ben Hoff Committed by Greg Kroah-Hartman
Browse files

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: default avatarBen Hoff <hoff.benjamin.k@gmail.com>
Link: https://lore.kernel.org/r/20250429182809.811786-1-hoff.benjamin.k@gmail.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 3fc08104
Loading
Loading
Loading
Loading
+88 −31
Original line number Diff line number Diff line
@@ -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
	} 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,
+2 −0
Original line number Diff line number Diff line
@@ -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