Re: [PATCH v2 2/3] usb: gadget: composite: Support eUSB2v2 bulk MPS update request
From: Selvarasu Ganesan
Date: Thu Sep 10 2026 - 08:13:43 EST
On 8/26/2026 4:54 PM, Pawel Laszczak via B4 Relay wrote:
> From: Pawel Laszczak <pawell@xxxxxxxxxxx>
>
> Add support for eUSB2v2 1024-byte Bulk MPS negotiation to the Gadget
> Composite framework.
>
> If 'gadget->is_eusb2v2' is set, force bcdUSB to 0x0230 and bMaxPacketSize0
> to 64 bytes. Handle the USB_DEVICE_BULK_MAX_PACKET_UPDATE feature request
> by introducing eusb2_update_mps_bulk(), which dynamically updates the
> wMaxPacketSize of all HS Bulk endpoint descriptors to 1024 bytes before
> the device is configured.
>
> Signed-off-by: Pawel Laszczak <pawell@xxxxxxxxxxx>
> ---
> Changes in v2:
> - composite.c: CLEAR_FEATURE(BULK_MAX_PACKET_UPDATE) is not defined in the
> eUSB2v2 spec; stall it instead of incorrectly setting 1024-byte mode.
> - restore MPS in __composite_disconnect
> ---
> drivers/usb/gadget/composite.c | 68 ++++++++++++++++++++++++++++++++++++++----
> include/linux/usb/gadget.h | 2 ++
> 2 files changed, 64 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> index dc3664374596..f0246e25f2b5 100644
> --- a/drivers/usb/gadget/composite.c
> +++ b/drivers/usb/gadget/composite.c
> @@ -924,6 +924,48 @@ static void device_qual(struct usb_composite_dev *cdev)
> }
>
> /*-------------------------------------------------------------------------*/
> +static void eusb2_update_ep_mps(struct usb_descriptor_header *header, __le16 mps)
> +{
> + struct usb_endpoint_descriptor *epd;
> +
> + if (header->bDescriptorType != USB_DT_ENDPOINT)
> + return;
> +
> + epd = (void *)header;
> + if (usb_endpoint_xfer_bulk(epd))
> + epd->wMaxPacketSize = mps;
> +}
> +
> +static int eusb2_update_mps_bulk(struct usb_composite_dev *cdev, bool set)
> +{
> + __le16 mps = cpu_to_le16(set ? 1024 : 512);
> + struct usb_gadget *gadget = cdev->gadget;
> + struct usb_configuration *config;
> + struct usb_function *f;
> +
> + if (!gadget->is_eusb2v2)
> + return -EINVAL;
> +
> + if (set && gadget->state >= USB_STATE_CONFIGURED)
> + return -EINVAL;
> +
> + list_for_each_entry(config, &cdev->configs, list) {
> + if (!config->highspeed)
> + continue;
> +
> + list_for_each_entry(f, &config->functions, list) {
> + struct usb_descriptor_header **desc = f->hs_descriptors;
> +
> + if (!desc)
> + continue;
> +
> + for (; *desc; desc++)
> + eusb2_update_ep_mps(*desc, mps);
> + }
> + }
> +
> + return 0;
> +}
>
> static void reset_config(struct usb_composite_dev *cdev)
> {
> @@ -971,8 +1013,10 @@ static int set_config(struct usb_composite_dev *cdev,
> if (result < 0)
> goto done;
> } else { /* Zero configuration value - need to reset the config */
> - if (cdev->config)
> + if (cdev->config) {
> reset_config(cdev);
> + eusb2_update_mps_bulk(cdev, false);
> + }
> result = 0;
> }
>
> @@ -1807,7 +1851,11 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> count_configs(cdev, USB_DT_DEVICE);
> cdev->desc.bMaxPacketSize0 =
> cdev->gadget->ep0->maxpacket;
> - if (gadget_is_superspeed(gadget)) {
> +
> + if (gadget->is_eusb2v2) {
> + cdev->desc.bcdUSB = cpu_to_le16(0x0230);
> + cdev->desc.bMaxPacketSize0 = 64;
> + } else if (gadget_is_superspeed(gadget)) {
> if (gadget->speed >= USB_SPEED_SUPER) {
> cdev->desc.bcdUSB = cpu_to_le16(0x0320);
> cdev->desc.bMaxPacketSize0 = 9;
> @@ -2005,12 +2053,19 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> */
> case USB_REQ_CLEAR_FEATURE:
> case USB_REQ_SET_FEATURE:
> - if (!gadget_is_superspeed(gadget))
> - goto unknown;
> - if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
> - goto unknown;
> switch (w_value) {
> + case USB_DEVICE_BULK_MAX_PACKET_UPDATE:
> + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE))
> + goto unknown;
> + if (ctrl->bRequest != USB_REQ_SET_FEATURE)
> + goto unknown;
> + value = eusb2_update_mps_bulk(cdev, true);
Here both USB_REQ_CLEAR_FEATURE and USB_REQ_SET_FEATURE fall through to
the same switch, and USB_DEVICE_BULK_MAX_PACKET_UPDATE unconditionally
calls eusb2_update_mps_bulk(cdev, true) regardless of which request type
was received.
The CLEAR_FEATURE may request should revert the MPS back to the default
by calling eusb2_update_mps_bulk(cdev, false), but instead it performs
the same action as SET_FEATURE.
Is this required distinguished using ctrl->bRequest to check whether
this is a SET or CLEAR operation?
Thanks,
Selva
> + break;
> case USB_INTRF_FUNC_SUSPEND:
> + if (!gadget_is_superspeed(gadget))
> + goto unknown;
> + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
> + goto unknown;
> if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
> break;
> f = cdev->config->interface[intf];
> @@ -2293,6 +2348,7 @@ static void __composite_disconnect(struct usb_gadget *gadget)
> * disconnect callbacks?
> */
> spin_lock_irqsave(&cdev->lock, flags);
> + eusb2_update_mps_bulk(cdev, false);
> cdev->suspended = 0;
> if (cdev->config)
> reset_config(cdev);
> diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
> index 8285b19a25e0..3c554fe95f87 100644
> --- a/include/linux/usb/gadget.h
> +++ b/include/linux/usb/gadget.h
> @@ -420,6 +420,7 @@ struct usb_gadget_ops {
> * @wakeup_armed: True if gadget is armed by the host for remote wakeup.
> * @irq: the interrupt number for device controller.
> * @id_number: a unique ID number for ensuring that gadget names are distinct
> + * @is_eusb2v2: True if controller is Embedded usb2.
> *
> * Gadgets have a mostly-portable "gadget driver" implementing device
> * functions, handling all usb configurations and interfaces. Gadget
> @@ -483,6 +484,7 @@ struct usb_gadget {
> unsigned lpm_capable:1;
> unsigned wakeup_capable:1;
> unsigned wakeup_armed:1;
> + unsigned is_eusb2v2:1;
> int irq;
> int id_number;
> };
>