From 50246d57233ad3b3c5dab99001a84d93e3e3d8f0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 26 May 2026 12:47:52 +0200 Subject: [PATCH 01/30] usb: udc: pxa: fix error handling As Dan Carpenter points out, my recent change makes subtle changes to the error handling that were not intended. Move the warning print up so it does not get skipped in case of an error, but handle -EPROBE_DEFER properly now. Change the devm_gpiod_get() to the _optional variant, which is in line with the intended behavior and the DT binding, though this did not work previously. Reported-by: Dan Carpenter Link: https://lore.kernel.org/linux-usb/ag6-xhfFjb5NpXQz@stanley.mountain/ Fixes: 25bd55f46032 ("usb: udc: pxa: remove unused platform_data") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260526104810.3906090-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa27x_udc.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index 640f81988c04..df5cca43afbe 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -2374,9 +2374,10 @@ static int pxa_udc_probe(struct platform_device *pdev) struct pxa_udc *udc = &memory; int retval = 0; - udc->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_ASIS); + udc->gpiod = devm_gpiod_get_optional(&pdev->dev, NULL, GPIOD_ASIS); if (IS_ERR(udc->gpiod)) - return PTR_ERR(udc->gpiod); + return dev_err_probe(&pdev->dev, PTR_ERR(udc->gpiod), + "Couldn't find or request D+ gpio\n"); udc->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(udc->regs)) @@ -2395,11 +2396,6 @@ static int pxa_udc_probe(struct platform_device *pdev) udc->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); } - if (IS_ERR(udc->gpiod)) { - dev_err(&pdev->dev, "Couldn't find or request D+ gpio : %ld\n", - PTR_ERR(udc->gpiod)); - return PTR_ERR(udc->gpiod); - } if (udc->gpiod) gpiod_direction_output(udc->gpiod, 0); From 79e2d75725c85607f8a9d87ae9cace62a19f767d Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 27 Jun 2026 18:41:53 +0800 Subject: [PATCH 02/30] usb: gadget: f_tcm: synchronize delayed set_alt with teardown The f_tcm set_alt() path defers endpoint setup to a work item and completes the delayed status response from process context. The delayed work uses f_tcm private state and may complete the setup request after disconnect or function teardown has already moved on. Cancel and drain the delayed set_alt work when the function is unbound or freed. For disable paths, which are reached under the composite device lock, use a small state machine and a non-sleeping cancellation path instead of cancel_work_sync(). If the work is already running, mark it cancelled and let the worker own the cleanup; otherwise tcm_disable() can cancel the queued work and clean up immediately. Also serialize the final delayed-status completion with the cancellation check while holding the composite device lock. This prevents a disconnect from clearing delayed_status while the worker is about to complete the control request. Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in tcm_delayed_set_alt+0x6c/0xef0 Call Trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 ? tcm_delayed_set_alt+0x6c/0xef0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x188/0x320 ? tcm_delayed_set_alt+0x6c/0xef0 kasan_report+0xe0/0x110 ? tcm_delayed_set_alt+0x6c/0xef0 tcm_delayed_set_alt+0x6c/0xef0 ? __pfx_tcm_delayed_set_alt+0x10/0x10 ? process_one_work+0x4cb/0xb90 ? rcu_is_watching+0x20/0x50 ? tcm_delayed_set_alt+0x9/0xef0 process_one_work+0x4d7/0xb90 ? __pfx_process_one_work+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __list_add_valid_or_report+0x37/0xf0 ? __pfx_tcm_delayed_set_alt+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 worker_thread+0x2d8/0x570 ? __pfx_worker_thread+0x10/0x10 kthread+0x1ad/0x1f0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x3c9/0x540 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x2e9/0x730 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Allocated by task 544: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x8f/0xa0 tcm_alloc+0x68/0x180 usb_get_function+0x36/0x60 config_usb_cfg_link+0x125/0x1b0 configfs_symlink+0x322/0x890 vfs_symlink+0xc2/0x270 filename_symlinkat+0x295/0x2f0 __x64_sys_symlinkat+0x62/0x90 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 661: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kfree+0x2f9/0x530 config_usb_cfg_unlink+0x173/0x1e0 configfs_unlink+0x1fa/0x340 vfs_unlink+0x15c/0x510 filename_unlinkat+0x2ba/0x450 __x64_sys_unlinkat+0x63/0x90 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: c52661d60f63 ("usb-gadget: Initial merge of target module for UASP + BOT") Cc: stable Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260627104153.3822495-1-zzzccc427@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_tcm.c | 192 ++++++++++++++++++++++++---- drivers/usb/gadget/function/tcm.h | 13 ++ 2 files changed, 177 insertions(+), 28 deletions(-) diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 34d9f49e9987..b3fa5a17fd2d 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -2363,31 +2363,158 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f) return -ENOTSUPP; } -struct guas_setup_wq { - struct work_struct work; - struct f_uas *fu; - unsigned int alt; -}; +static void tcm_cleanup_old_alt(struct f_uas *fu) +{ + if (fu->flags & USBG_IS_UAS) + uasp_cleanup_old_alt(fu); + else if (fu->flags & USBG_IS_BOT) + bot_cleanup_old_alt(fu); + fu->flags = 0; +} + +static void tcm_delayed_set_alt_done(struct f_uas *fu) +{ + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); +} + +static bool tcm_delayed_set_alt_cancelled(struct f_uas *fu) +{ + bool cancelled; + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + cancelled = fu->delayed_set_alt_cancel; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + return cancelled; +} + +static bool tcm_complete_delayed_status(struct f_uas *fu) +{ + struct usb_composite_dev *cdev = fu->function.config->cdev; + struct usb_request *req = cdev->req; + unsigned long cdev_flags; + bool cancelled; + int ret; + + spin_lock_irqsave(&cdev->lock, cdev_flags); + spin_lock(&fu->delayed_set_alt_lock); + cancelled = fu->delayed_set_alt_cancel; + if (!cancelled) { + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + } + spin_unlock(&fu->delayed_set_alt_lock); + + if (cancelled) { + spin_unlock_irqrestore(&cdev->lock, cdev_flags); + return false; + } + + if (cdev->delayed_status == 0) { + WARN(cdev, "%s: Unexpected call\n", __func__); + } else if (--cdev->delayed_status == 0) { + req->length = 0; + req->context = cdev; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret == 0) { + cdev->setup_pending = true; + } else { + req->status = 0; + req->complete(cdev->gadget->ep0, req); + } + } + + spin_unlock_irqrestore(&cdev->lock, cdev_flags); + + return true; +} + +static bool tcm_cancel_delayed_set_alt(struct f_uas *fu) +{ + bool cleanup = false; + bool cancel = false; + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + switch (fu->delayed_set_alt_state) { + case USBG_DELAYED_SET_ALT_IDLE: + cleanup = true; + break; + case USBG_DELAYED_SET_ALT_QUEUED: + case USBG_DELAYED_SET_ALT_RUNNING: + fu->delayed_set_alt_cancel = true; + cancel = true; + break; + } + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + if (cancel && cancel_work(&fu->delayed_set_alt)) { + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state == USBG_DELAYED_SET_ALT_QUEUED) { + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + cleanup = true; + } + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + } + + return cleanup; +} + +static void tcm_cancel_delayed_set_alt_sync(struct f_uas *fu) +{ + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_IDLE) + fu->delayed_set_alt_cancel = true; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + cancel_work_sync(&fu->delayed_set_alt); + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); +} static void tcm_delayed_set_alt(struct work_struct *wq) { - struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq, - work); - struct f_uas *fu = work->fu; - int alt = work->alt; + struct f_uas *fu = container_of(wq, struct f_uas, delayed_set_alt); + unsigned long flags; + unsigned int alt; - kfree(work); + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_QUEUED) { + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + return; + } + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_RUNNING; + alt = fu->delayed_alt; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); - if (fu->flags & USBG_IS_BOT) - bot_cleanup_old_alt(fu); - if (fu->flags & USBG_IS_UAS) - uasp_cleanup_old_alt(fu); + tcm_cleanup_old_alt(fu); + + if (tcm_delayed_set_alt_cancelled(fu)) + goto out_done; if (alt == USB_G_ALT_INT_BBB) bot_set_alt(fu); else if (alt == USB_G_ALT_INT_UAS) uasp_set_alt(fu); - usb_composite_setup_continue(fu->function.config->cdev); + + if (tcm_complete_delayed_status(fu)) + return; + + tcm_cleanup_old_alt(fu); +out_done: + tcm_delayed_set_alt_done(fu); } static int tcm_get_alt(struct usb_function *f, unsigned intf) @@ -2413,15 +2540,20 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) return -EOPNOTSUPP; if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { - struct guas_setup_wq *work; + unsigned long flags; - work = kmalloc_obj(*work, GFP_ATOMIC); - if (!work) - return -ENOMEM; - INIT_WORK(&work->work, tcm_delayed_set_alt); - work->fu = fu; - work->alt = alt; - schedule_work(&work->work); + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_IDLE) { + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, + flags); + return -EBUSY; + } + fu->delayed_alt = alt; + fu->delayed_set_alt_cancel = false; + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_QUEUED; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + schedule_work(&fu->delayed_set_alt); return USB_GADGET_DELAYED_STATUS; } return -EOPNOTSUPP; @@ -2431,11 +2563,8 @@ static void tcm_disable(struct usb_function *f) { struct f_uas *fu = to_f_uas(f); - if (fu->flags & USBG_IS_UAS) - uasp_cleanup_old_alt(fu); - else if (fu->flags & USBG_IS_BOT) - bot_cleanup_old_alt(fu); - fu->flags = 0; + if (tcm_cancel_delayed_set_alt(fu)) + tcm_cleanup_old_alt(fu); } static int tcm_setup(struct usb_function *f, @@ -2583,11 +2712,16 @@ static void tcm_free(struct usb_function *f) { struct f_uas *tcm = to_f_uas(f); + tcm_cancel_delayed_set_alt_sync(tcm); kfree(tcm); } static void tcm_unbind(struct usb_configuration *c, struct usb_function *f) { + struct f_uas *fu = to_f_uas(f); + + tcm_cancel_delayed_set_alt_sync(fu); + tcm_cleanup_old_alt(fu); usb_free_all_descriptors(f); } @@ -2620,6 +2754,8 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi) fu->function.disable = tcm_disable; fu->function.free_func = tcm_free; fu->tpg = tpg_instances[i].tpg; + INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt); + spin_lock_init(&fu->delayed_set_alt_lock); hash_init(fu->stream_hash); mutex_unlock(&tpg_instances_lock); diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h index 009974d81d66..e1d5a9391612 100644 --- a/drivers/usb/gadget/function/tcm.h +++ b/drivers/usb/gadget/function/tcm.h @@ -3,6 +3,7 @@ #define __TARGET_USB_GADGET_H__ #include +#include /* #include */ #include #include @@ -29,6 +30,12 @@ enum { #define USB_G_DEFAULT_SESSION_TAGS USBG_NUM_CMDS +enum { + USBG_DELAYED_SET_ALT_IDLE = 0, + USBG_DELAYED_SET_ALT_QUEUED, + USBG_DELAYED_SET_ALT_RUNNING, +}; + struct tcm_usbg_nexus { struct se_session *tvn_se_sess; }; @@ -132,6 +139,12 @@ struct f_uas { #define USBG_BOT_CMD_PEND (1 << 4) #define USBG_BOT_WEDGED (1 << 5) + struct work_struct delayed_set_alt; + spinlock_t delayed_set_alt_lock; /* protects delayed_set_alt_* */ + unsigned int delayed_alt; + unsigned int delayed_set_alt_state; + bool delayed_set_alt_cancel; + struct usbg_cdb cmd[USBG_NUM_CMDS]; struct usb_ep *ep_in; struct usb_ep *ep_out; From b70dc75e85ba968b7b76eebfe5d63000080b875b Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 30 Jun 2026 00:50:04 +0500 Subject: [PATCH 03/30] usb: gadget: uvc: clamp SEND_RESPONSE length to the response buffer uvc_send_response() builds the UVC control response from a user-supplied struct uvc_request_data: req->length = min_t(unsigned int, uvc->event_length, data->length); ... memcpy(req->buf, data->data, req->length); req->length is clamped to uvc->event_length, which is taken from the host control request wLength (up to UVC_MAX_REQUEST_SIZE, 64), and to data->length, which comes from the UVCIOC_SEND_RESPONSE ioctl and is only checked for being negative. The source buffer data->data is only 60 bytes, so a response with uvc->event_length and data->length both greater than 60 makes memcpy() read past the end of data->data. Clamp req->length to sizeof(data->data) as well. Fixes: a5eaaa1f33e7 ("usb: gadget: uvc: use capped length value") Cc: stable Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260629195004.148405-1-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_v4l2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index 514e5930b9ca..dfa0521a243a 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -200,6 +200,8 @@ uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data) return usb_ep_set_halt(cdev->gadget->ep0); req->length = min_t(unsigned int, uvc->event_length, data->length); + if (req->length > sizeof(data->data)) + req->length = sizeof(data->data); req->zero = data->length < uvc->event_length; memcpy(req->buf, data->data, req->length); From 1fc50f1ecde39feb4fccdaf4bc71aa6c0eb25c49 Mon Sep 17 00:00:00 2001 From: Xincheng Zhang Date: Tue, 30 Jun 2026 09:26:48 +0800 Subject: [PATCH 04/30] usb: xhci-pci: Limit VIA VL805 DMA addressing to 36 bits The VIA VL805/806 xHCI controller advertises AC64, but fails to handle DMA addresses at or above 0x1000000000. On systems with large amounts of RAM, this can cause USB device failures when the controller is given DMA addresses beyond its usable address width. Do not use XHCI_NO_64BIT_SUPPORT for this controller. That quirk clears the cached AC64 capability and limits DMA to 32 bits, causing unnecessary bouncing for addresses between 4GiB and 64GiB and hiding the controller's real AC64 capability from code that may need to distinguish register access width from usable DMA address width. Track the usable DMA address width separately from the AC64 capability. Initialize the generic xhci->dma_mask_bits field to 64 and let PCI quirks reduce it for controllers with narrower DMA support. Set VIA VL805/806 to 36 bits so the DMA API only hands it addresses in the range it can handle while keeping HCCPARAMS1.AC64 visible. Cc: stable Signed-off-by: Xincheng Zhang Link: https://patch.msgid.link/20260630-xhci-via-dma-fix-v3-1-690dcb8cf75a@ultrarisc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 1 + drivers/usb/host/xhci.c | 15 ++++++++++----- drivers/usb/host/xhci.h | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 039c26b241d0..6b3fcba44b08 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -448,6 +448,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == PCI_DEVICE_ID_VIA_VL805) { xhci->quirks |= XHCI_LPM_SUPPORT; xhci->quirks |= XHCI_TRB_OVERFETCH; + xhci->dma_mask_bits = 36; } if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index f44ccee5fa07..091c82ca8ee2 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5466,6 +5466,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + xhci->dma_mask_bits = 64; xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); /* xhci-plat or xhci-pci might have set max_interrupters already */ @@ -5515,12 +5516,16 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) xhci->hcc_params &= ~BIT(0); - /* Set dma_mask and coherent_dma_mask to 64-bits, - * if xHC supports 64-bit addressing */ + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports + * 64-bit addressing, unless a controller-specific quirk callback + * limits the usable address width. + */ if ((xhci->hcc_params & HCC_64BIT_ADDR) && - !dma_set_mask(dev, DMA_BIT_MASK(64))) { - xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); - dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + !dma_set_mask(dev, DMA_BIT_MASK(xhci->dma_mask_bits))) { + xhci_dbg(xhci, "Enabling %u-bit DMA addresses.\n", + xhci->dma_mask_bits); + dma_set_coherent_mask(dev, DMA_BIT_MASK(xhci->dma_mask_bits)); } else { /* * This is to avoid error in cases where a 32-bit USB diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index d02046a573e4..2d3941b5e1e3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1526,6 +1526,7 @@ struct xhci_hcd { /* imod_interval in ns (I * 250ns) */ u32 imod_interval; u32 page_size; + unsigned int dma_mask_bits; /* MSI-X/MSI vectors */ int nvecs; /* optional clocks */ From 6b874d00c466e73c6448a89856407fe46b2f50e4 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:33 +0200 Subject: [PATCH 05/30] USB: gadget: fsl-udc: fix device name leak on probe failure The gadget device name is set by UDC core when registering the gadget and must not be set before to avoid leaking the name in intermediate error paths (e.g. on dma pool creation failure). Fixes: eab35c4e6d95 ("usb: gadget: fsl_udc_core: let udc-core manage gadget->dev") Cc: stable Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141536.90887-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_udc_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 600ce8cc0fef..8c5b2f42ff44 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2474,7 +2474,6 @@ static int fsl_udc_probe(struct platform_device *pdev) udc_controller->gadget.name = driver_name; /* Setup gadget.dev and register with kernel */ - dev_set_name(&udc_controller->gadget.dev, "gadget"); udc_controller->gadget.dev.of_node = pdev->dev.of_node; if (!IS_ERR_OR_NULL(udc_controller->transceiver)) From 29a142d3e8b35ebc9e0bcc78f4bc26c9b6a9ac0b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:34 +0200 Subject: [PATCH 06/30] USB: gadget: snps-udc: fix device name leak on probe failure The gadget device name is set by UDC core when registering the gadget and must not be set before to avoid leaking the name in intermediate error paths (e.g. when detecting an older chip revision). Fixes: 12ad0fcaf2fb ("usb: gadget: amd5536udc: let udc-core manage gadget->dev") Cc: stable Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141536.90887-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/snps_udc_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 0e0db68e0b27..d506f9d92bca 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -3133,7 +3133,6 @@ int udc_probe(struct udc *dev) /* device struct setup */ dev->gadget.ops = &udc_ops; - dev_set_name(&dev->gadget.dev, "gadget"); dev->gadget.name = name; dev->gadget.max_speed = USB_SPEED_HIGH; From c4dd150fceab281496acb3a643ae712aacb74864 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:35 +0200 Subject: [PATCH 07/30] USB: gadget: fsl-udc: fix dev_printk() device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A change replacing custom printk() macros with dev_printk() incorrectly used the gadget struct device instead of the controller struct device (including for messages printed before the gadget device name has been initialised). Switch to using the controller platform device with dev_printk() so that the controller device and driver names are included in log messages as expected. Fixes: 6025f20f16c2 ("usb: gadget: fsl-udc: Replace custom log wrappers by dev_{err,warn,dbg,vdbg}") Cc: stable Cc: Uwe Kleine-König Signed-off-by: Johan Hovold Acked-by: Uwe Kleine-König Link: https://patch.msgid.link/20260702141536.90887-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_udc_core.c | 92 ++++++++++++--------------- drivers/usb/gadget/udc/fsl_usb2_udc.h | 1 + 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 8c5b2f42ff44..c4761933ec86 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -184,7 +184,7 @@ __acquires(ep->udc->lock) usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); if (status && (status != -ESHUTDOWN)) - dev_vdbg(&udc->gadget.dev, "complete %s req %p stat %d len %u/%u\n", + dev_vdbg(udc->dev, "complete %s req %p stat %d len %u/%u\n", ep->ep.name, &req->req, status, req->req.actual, req->req.length); @@ -286,7 +286,7 @@ static int dr_controller_setup(struct fsl_udc *udc) timeout = jiffies + FSL_UDC_RESET_TIMEOUT; while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { if (time_after(jiffies, timeout)) { - dev_err(&udc->gadget.dev, "udc reset timeout!\n"); + dev_err(udc->dev, "udc reset timeout!\n"); return -ETIMEDOUT; } cpu_relax(); @@ -309,7 +309,7 @@ static int dr_controller_setup(struct fsl_udc *udc) tmp &= USB_EP_LIST_ADDRESS_MASK; fsl_writel(tmp, &dr_regs->endpointlistaddr); - dev_vdbg(&udc->gadget.dev, + dev_vdbg(udc->dev, "vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n", udc->ep_qh, (int)tmp, fsl_readl(&dr_regs->endpointlistaddr)); @@ -500,7 +500,7 @@ static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; break; default: - dev_vdbg(&udc->gadget.dev, "error ep type is %d\n", ep_type); + dev_vdbg(udc->dev, "error ep type is %d\n", ep_type); return; } if (zlt) @@ -613,7 +613,7 @@ static int fsl_ep_enable(struct usb_ep *_ep, spin_unlock_irqrestore(&udc->lock, flags); retval = 0; - dev_vdbg(&udc->gadget.dev, "enabled %s (ep%d%s) maxpacket %d\n", + dev_vdbg(udc->dev, "enabled %s (ep%d%s) maxpacket %d\n", ep->ep.name, ep->ep.desc->bEndpointAddress & 0x0f, (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", max); @@ -634,13 +634,8 @@ static int fsl_ep_disable(struct usb_ep *_ep) int ep_num; ep = container_of(_ep, struct fsl_ep, ep); - if (!_ep || !ep->ep.desc) { - /* - * dev_vdbg(&udc->gadget.dev, "%s not enabled\n", - * _ep ? ep->ep.name : NULL); - */ + if (!_ep || !ep->ep.desc) return -EINVAL; - } /* disable ep on controller */ ep_num = ep_index(ep); @@ -664,7 +659,7 @@ static int fsl_ep_disable(struct usb_ep *_ep) ep->stopped = 1; spin_unlock_irqrestore(&udc->lock, flags); - dev_vdbg(&udc->gadget.dev, "disabled %s OK\n", _ep->name); + dev_vdbg(udc->dev, "disabled %s OK\n", _ep->name); return 0; } @@ -724,9 +719,6 @@ static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) { u32 temp, bitmask, tmp_stat; - /* dev_vdbg(&udc->gadget.dev, "QH addr Register 0x%8x\n", dr_regs->endpointlistaddr); - dev_vdbg(&udc->gadget.dev, "ep_qh[%d] addr is 0x%8x\n", i, (u32)&(ep->udc->ep_qh[i])); */ - bitmask = ep_is_in(ep) ? (1 << (ep_index(ep) + 16)) : (1 << (ep_index(ep))); @@ -813,7 +805,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, *is_last = 0; if ((*is_last) == 0) - dev_vdbg(&udc_controller->gadget.dev, "multi-dtd request!\n"); + dev_vdbg(udc_controller->dev, "multi-dtd request!\n"); /* Fill in the transfer size; set active bit */ swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); @@ -825,7 +817,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, mb(); - dev_vdbg(&udc_controller->gadget.dev, "length = %d address= 0x%x\n", *length, (int)*dma); + dev_vdbg(udc_controller->dev, "length = %d address= 0x%x\n", *length, (int)*dma); return dtd; } @@ -876,11 +868,11 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) /* catch various bogus parameters */ if (!_req || !req->req.complete || !req->req.buf || !list_empty(&req->queue)) { - dev_vdbg(&udc->gadget.dev, "%s, bad params\n", __func__); + dev_vdbg(udc->dev, "%s, bad params\n", __func__); return -EINVAL; } if (unlikely(!ep->ep.desc)) { - dev_vdbg(&udc->gadget.dev, "%s, bad ep\n", __func__); + dev_vdbg(udc->dev, "%s, bad ep\n", __func__); return -EINVAL; } if (usb_endpoint_xfer_isoc(ep->ep.desc)) { @@ -1040,7 +1032,7 @@ static int fsl_ep_set_halt(struct usb_ep *_ep, int value) udc->ep0_dir = 0; } out: - dev_vdbg(&udc->gadget.dev, "%s %s halt stat %d\n", ep->ep.name, + dev_vdbg(udc->dev, "%s %s halt stat %d\n", ep->ep.name, value ? "set" : "clear", status); return status; @@ -1109,7 +1101,7 @@ static void fsl_ep_fifo_flush(struct usb_ep *_ep) /* Wait until flush complete */ while (fsl_readl(&dr_regs->endptflush)) { if (time_after(jiffies, timeout)) { - dev_err(&udc_controller->gadget.dev, + dev_err(udc_controller->dev, "ep flush timeout\n"); return; } @@ -1182,7 +1174,7 @@ static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) udc = container_of(gadget, struct fsl_udc, gadget); spin_lock_irqsave(&udc->lock, flags); - dev_vdbg(&gadget->dev, "VBUS %s\n", str_on_off(is_active)); + dev_vdbg(udc->dev, "VBUS %s\n", str_on_off(is_active)); udc->vbus_active = (is_active != 0); if (can_pullup(udc)) fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), @@ -1548,7 +1540,7 @@ static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, udc->ep0_state = WAIT_FOR_SETUP; break; case WAIT_FOR_SETUP: - dev_err(&udc->gadget.dev, "Unexpected ep0 packets\n"); + dev_err(udc->dev, "Unexpected ep0 packets\n"); break; default: ep0stall(udc); @@ -1617,7 +1609,7 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, errors = hc32_to_cpu(curr_td->size_ioc_sts); if (errors & DTD_ERROR_MASK) { if (errors & DTD_STATUS_HALTED) { - dev_err(&udc->gadget.dev, "dTD error %08x QH=%d\n", errors, pipe); + dev_err(udc->dev, "dTD error %08x QH=%d\n", errors, pipe); /* Clear the errors and Halt condition */ tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts); tmp &= ~errors; @@ -1628,26 +1620,26 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, break; } if (errors & DTD_STATUS_DATA_BUFF_ERR) { - dev_vdbg(&udc->gadget.dev, "Transfer overflow\n"); + dev_vdbg(udc->dev, "Transfer overflow\n"); status = -EPROTO; break; } else if (errors & DTD_STATUS_TRANSACTION_ERR) { - dev_vdbg(&udc->gadget.dev, "ISO error\n"); + dev_vdbg(udc->dev, "ISO error\n"); status = -EILSEQ; break; } else - dev_err(&udc->gadget.dev, + dev_err(udc->dev, "Unknown error has occurred (0x%x)!\n", errors); } else if (hc32_to_cpu(curr_td->size_ioc_sts) & DTD_STATUS_ACTIVE) { - dev_vdbg(&udc->gadget.dev, "Request not complete\n"); + dev_vdbg(udc->dev, "Request not complete\n"); status = REQ_UNCOMPLETE; return status; } else if (remaining_length) { if (direction) { - dev_vdbg(&udc->gadget.dev, + dev_vdbg(udc->dev, "Transmit dTD remaining length not zero\n"); status = -EPROTO; break; @@ -1655,8 +1647,7 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, break; } } else { - dev_vdbg(&udc->gadget.dev, - "dTD transmitted successful\n"); + dev_vdbg(udc->dev, "dTD transmitted successful\n"); } if (j != curr_req->dtd_count - 1) @@ -1699,7 +1690,7 @@ static void dtd_complete_irq(struct fsl_udc *udc) /* If the ep is configured */ if (!curr_ep->ep.name) { - dev_warn(&udc->gadget.dev, "Invalid EP?\n"); + dev_warn(udc->dev, "Invalid EP?\n"); continue; } @@ -1708,7 +1699,7 @@ static void dtd_complete_irq(struct fsl_udc *udc) queue) { status = process_ep_req(udc, i, curr_req); - dev_vdbg(&udc->gadget.dev, + dev_vdbg(udc->dev, "status of process_ep_req= %d, ep = %d\n", status, ep_num); if (status == REQ_UNCOMPLETE) @@ -1829,7 +1820,7 @@ static void reset_irq(struct fsl_udc *udc) while (fsl_readl(&dr_regs->endpointprime)) { /* Wait until all endptprime bits cleared */ if (time_after(jiffies, timeout)) { - dev_err(&udc->gadget.dev, "Timeout for reset\n"); + dev_err(udc->dev, "Timeout for reset\n"); break; } cpu_relax(); @@ -1839,7 +1830,7 @@ static void reset_irq(struct fsl_udc *udc) fsl_writel(0xffffffff, &dr_regs->endptflush); if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { - dev_vdbg(&udc->gadget.dev, "Bus reset\n"); + dev_vdbg(udc->dev, "Bus reset\n"); /* Bus is reseting */ udc->bus_reset = 1; /* Reset all the queues, include XD, dTD, EP queue @@ -1847,7 +1838,7 @@ static void reset_irq(struct fsl_udc *udc) reset_queues(udc, true); udc->usb_state = USB_STATE_DEFAULT; } else { - dev_vdbg(&udc->gadget.dev, "Controller reset\n"); + dev_vdbg(udc->dev, "Controller reset\n"); /* initialize usb hw reg except for regs for EP, not * touch usbintr reg */ dr_controller_setup(udc); @@ -1881,7 +1872,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Clear notification bits */ fsl_writel(irq_src, &dr_regs->usbsts); - /* dev_vdbg(&udc->gadget.dev, "irq_src [0x%8x]", irq_src); */ + /* dev_vdbg(udc->dev, "irq_src [0x%8x]", irq_src); */ /* Need to resume? */ if (udc->usb_state == USB_STATE_SUSPENDED) @@ -1890,7 +1881,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* USB Interrupt */ if (irq_src & USB_STS_INT) { - dev_vdbg(&udc->gadget.dev, "Packet int\n"); + dev_vdbg(udc->dev, "Packet int\n"); /* Setup package, we only support ep0 as control ep */ if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { tripwire_handler(udc, 0, @@ -1919,7 +1910,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Reset Received */ if (irq_src & USB_STS_RESET) { - dev_vdbg(&udc->gadget.dev, "reset int\n"); + dev_vdbg(udc->dev, "reset int\n"); reset_irq(udc); status = IRQ_HANDLED; } @@ -1931,7 +1922,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) } if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { - dev_vdbg(&udc->gadget.dev, "Error IRQ %x\n", irq_src); + dev_vdbg(udc->dev, "Error IRQ %x\n", irq_src); } spin_unlock_irqrestore(&udc->lock, flags); @@ -1967,7 +1958,7 @@ static int fsl_udc_start(struct usb_gadget *g, udc_controller->transceiver->otg, &udc_controller->gadget); if (retval < 0) { - dev_err(&udc_controller->gadget.dev, "can't bind to transceiver\n"); + dev_err(udc_controller->dev, "can't bind to transceiver\n"); udc_controller->driver = NULL; return retval; } @@ -2252,7 +2243,7 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->eps = kzalloc_objs(struct fsl_ep, udc->max_ep); if (!udc->eps) { - dev_err(&udc->gadget.dev, "kmalloc udc endpoint status failed\n"); + dev_err(udc->dev, "kmalloc udc endpoint status failed\n"); goto eps_alloc_failed; } @@ -2267,7 +2258,7 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, &udc->ep_qh_dma, GFP_KERNEL); if (!udc->ep_qh) { - dev_err(&udc->gadget.dev, "malloc QHs for udc failed\n"); + dev_err(udc->dev, "malloc QHs for udc failed\n"); goto ep_queue_alloc_failed; } @@ -2278,14 +2269,14 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), struct fsl_req, req); if (!udc->status_req) { - dev_err(&udc->gadget.dev, "kzalloc for udc status request failed\n"); + dev_err(udc->dev, "kzalloc for udc status request failed\n"); goto udc_status_alloc_failed; } /* allocate a small amount of memory to get valid address */ udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); if (!udc->status_req->req.buf) { - dev_err(&udc->gadget.dev, "kzalloc for udc request buffer failed\n"); + dev_err(udc->dev, "kzalloc for udc request buffer failed\n"); goto udc_req_buf_alloc_failed; } @@ -2373,6 +2364,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (udc_controller == NULL) return -ENOMEM; + udc_controller->dev = &pdev->dev; pdata = dev_get_platdata(&pdev->dev); udc_controller->pdata = pdata; spin_lock_init(&udc_controller->lock); @@ -2382,7 +2374,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (pdata->operating_mode == FSL_USB2_DR_OTG) { udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); if (IS_ERR_OR_NULL(udc_controller->transceiver)) { - dev_err(&udc_controller->gadget.dev, "Can't find OTG driver!\n"); + dev_err(&pdev->dev, "Can't find OTG driver!\n"); ret = -ENODEV; goto err_kfree; } @@ -2398,7 +2390,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (pdata->operating_mode == FSL_USB2_DR_DEVICE) { if (!request_mem_region(res->start, resource_size(res), driver_name)) { - dev_err(&udc_controller->gadget.dev, "request mem region for %s failed\n", pdev->name); + dev_err(&pdev->dev, "failed to request mem region\n"); ret = -EBUSY; goto err_kfree; } @@ -2429,7 +2421,7 @@ static int fsl_udc_probe(struct platform_device *pdev) /* Read Device Controller Capability Parameters register */ dccparams = fsl_readl(&dr_regs->dccparams); if (!(dccparams & DCCPARAMS_DC)) { - dev_err(&udc_controller->gadget.dev, "This SOC doesn't support device role\n"); + dev_err(&pdev->dev, "This SOC doesn't support device role\n"); ret = -ENODEV; goto err_exit; } @@ -2447,14 +2439,14 @@ static int fsl_udc_probe(struct platform_device *pdev) ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, driver_name, udc_controller); if (ret != 0) { - dev_err(&udc_controller->gadget.dev, "cannot request irq %d err %d\n", + dev_err(&pdev->dev, "cannot request irq %d err %d\n", udc_controller->irq, ret); goto err_exit; } /* Initialize the udc structure including QH member and other member */ if (struct_udc_setup(udc_controller, pdev)) { - dev_err(&udc_controller->gadget.dev, "Can't initialize udc data structure\n"); + dev_err(&pdev->dev, "Can't initialize udc data structure\n"); ret = -ENOMEM; goto err_free_irq; } diff --git a/drivers/usb/gadget/udc/fsl_usb2_udc.h b/drivers/usb/gadget/udc/fsl_usb2_udc.h index cc1756f3e89d..53922bc58ca0 100644 --- a/drivers/usb/gadget/udc/fsl_usb2_udc.h +++ b/drivers/usb/gadget/udc/fsl_usb2_udc.h @@ -470,6 +470,7 @@ struct fsl_ep { #define EP_DIR_OUT 0 struct fsl_udc { + struct device *dev; struct usb_gadget gadget; struct usb_gadget_driver *driver; struct fsl_usb2_platform_data *pdata; From f05847a216a74e1f2387169a336edbc198960016 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:36 +0200 Subject: [PATCH 08/30] USB: gadget: fsl-udc: drop misleading unbind sanity check The UDC pointer is set on successful probe and will never be NULL when the driver is later unbound so drop the misleading sanity check (and confused error message). Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141536.90887-5-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_udc_core.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index c4761933ec86..20392409afb1 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2539,12 +2539,6 @@ static void fsl_udc_remove(struct platform_device *pdev) DECLARE_COMPLETION_ONSTACK(done); - if (!udc_controller) { - dev_err(&pdev->dev, - "Driver still in use but removing anyhow\n"); - return; - } - udc_controller->done = &done; usb_del_gadget_udc(&udc_controller->gadget); From 1febec7e47cdcd01f43fb0211094e3010474666e Mon Sep 17 00:00:00 2001 From: Sonali Pradhan Date: Fri, 3 Jul 2026 08:37:24 +0000 Subject: [PATCH 09/30] usb: gadget: f_ncm: validate datagram bounds in ncm_unwrap_ntb() When unpacking host-supplied NTBs, ncm_unwrap_ntb() checks datagram length against frame_max but does not verify that the datagram fits within the declared block length. Additionally, when decoding multiple NTBs from a single socket buffer, subsequent block lengths are not checked against the actual remaining buffer data. With these checks missing, a malicious USB host can specify datagram offsets and lengths that point beyond the block, or supply secondary NTB headers declaring lengths larger than the buffer. skb_put_data() then copies adjacent kernel memory from skb_shared_info into the network skb. Fix this by verifying that sufficient buffer space remains for the NTB header before parsing, handling zero-length block declarations, ensuring that block lengths never exceed the remaining buffer space, and verifying that each datagram payload stays strictly within the block boundary. Fixes: 427694cfaafa ("usb: gadget: ncm: Handle decoding of multiple NTB's in unwrap call") Fixes: 2b74b0a04d3e ("USB: gadget: f_ncm: add bounds checks to ncm_unwrap_ntb()") Cc: stable Assisted-by: Jetski:Gemini-2.5-Pro Signed-off-by: Sonali Pradhan Link: https://patch.msgid.link/20260703083725.1903850-1-sonalipradhan@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index c5bf8a448d64..64eabda2f546 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1189,6 +1189,10 @@ static int ncm_unwrap_ntb(struct gether *port, frame_max = ncm_opts->max_segment_size; parse_ntb: + if (to_process < (int)opts->nth_size) { + INFO(port->func.config->cdev, "Packet too small for headers\n"); + goto err; + } tmp = (__le16 *)ntb_ptr; /* dwSignature */ @@ -1209,8 +1213,12 @@ static int ncm_unwrap_ntb(struct gether *port, tmp++; /* skip wSequence */ block_len = get_ncm(&tmp, opts->block_length); + if (block_len == 0) + block_len = to_process; + /* (d)wBlockLength */ - if ((block_len < opts->nth_size + opts->ndp_size) || (block_len > ntb_max)) { + if ((block_len < opts->nth_size + opts->ndp_size) || (block_len > ntb_max) || + (block_len > to_process)) { INFO(port->func.config->cdev, "Bad block length: %#X\n", block_len); goto err; } @@ -1273,7 +1281,7 @@ static int ncm_unwrap_ntb(struct gether *port, index = index2; /* wDatagramIndex[0] */ if ((index < opts->nth_size) || - (index > block_len - opts->dpe_size)) { + (index > block_len)) { INFO(port->func.config->cdev, "Bad index: %#X\n", index); goto err; @@ -1285,7 +1293,8 @@ static int ncm_unwrap_ntb(struct gether *port, * ethernet hdr + crc or larger than max frame size */ if ((dg_len < 14 + crc_len) || - (dg_len > frame_max)) { + (dg_len > frame_max) || + (dg_len > block_len - index)) { INFO(port->func.config->cdev, "Bad dgram length: %#X\n", dg_len); goto err; @@ -1310,7 +1319,7 @@ static int ncm_unwrap_ntb(struct gether *port, dg_len2 = get_ncm(&tmp, opts->dgram_item_len); /* wDatagramIndex[1] */ - if (index2 > block_len - opts->dpe_size) { + if (index2 > block_len) { INFO(port->func.config->cdev, "Bad index: %#X\n", index2); goto err; From 7aa7d4bf9d3fa9a6a47b640ad103ab433b7ff261 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Tue, 7 Jul 2026 14:17:36 +0000 Subject: [PATCH 10/30] usb: typec: ucsi: Fix race condition and ordering in port unregistration A synchronization issue exists during port unregistration where pending partner work items can race against workqueue destruction, leading to use-after-free conditions: cros_ec_ucsi cros_ec_ucsi.3.auto: error -ETIMEDOUT: PPM init failed BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:__queue_work+0x83/0x4a0 Call Trace: __cfi_delayed_work_timer_fn+0x10/0x10 run_timer_softirq+0x3b6/0xbd0 sched_clock_cpu+0xc/0x110 irq_exit_rcu+0x18d/0x330 fred_sysvec_apic_timer_interrupt+0x5e/0x80 Fix this by ensuring strict ordering and proper serialization during teardown: 1. Move ucsi_unregister_partner() to the beginning of the teardown sequence and protect it under the connector mutex lock. 2. Ensure all pending partner tasks are explicitly flushed and finished before the workqueue is destroyed. 3. Switch from mod_delayed_work() to a cancel_delayed_work() and queue_delayed_work() sequence. This guarantees that items currently marked as pending won't be scheduled an additional time, preventing a double release of resources which leads to the following crash: Oops: general protection fault, probably for non-canonical address 0xdead000000000122: 0000 [#1] SMP NOPTI Workqueue: cros_ec_ucsi.3.auto-con2 ucsi_poll_worker RIP: 0010:ucsi_poll_worker+0x65/0x1e0 Call Trace: process_scheduled_works+0x218/0x6d0 worker_thread+0x188/0x3f0 __cfi_worker_thread+0x10/0x10 kthread+0x226/0x2a0 To ensure these rules are applied identically across both the normal teardown and the ucsi_init() error paths, consolidate the cleanup logic into a new helper, ucsi_unregister_port(). Cc: stable Fixes: b9aa02ca39a4 ("usb: typec: ucsi: Add polling mechanism for partner tasks like alt mode checking") Fixes: b13abcb7ddd8 ("usb: typec: ucsi: Fix NULL pointer access") Fixes: fac4b8633fd6 ("usb: ucsi: Ensure connector delayed work items are flushed") Signed-off-by: Andrei Kuchynski Reviewed-by: Benson Leung Link: https://patch.msgid.link/20260707141736.1635698-1-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 82 +++++++++++++++++------------------ 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 6a6723e8fb12..f56897e23df8 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1845,6 +1845,42 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con) return ret; } +static void ucsi_unregister_port(struct ucsi_connector *con) +{ + struct ucsi_work *uwork; + + if (con->wq) { + mutex_lock(&con->lock); + ucsi_unregister_partner(con); + /* + * queue delayed items immediately so they can execute + * and free themselves before the wq is destroyed + */ + list_for_each_entry(uwork, &con->partner_tasks, node) { + if (cancel_delayed_work(&uwork->work)) + queue_delayed_work(con->wq, &uwork->work, 0); + } + mutex_unlock(&con->lock); + + destroy_workqueue(con->wq); + con->wq = NULL; + } else { + ucsi_unregister_partner(con); + } + + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); + ucsi_unregister_port_psy(con); + + usb_power_delivery_unregister_capabilities(con->port_sink_caps); + con->port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(con->port_source_caps); + con->port_source_caps = NULL; + usb_power_delivery_unregister(con->pd); + con->pd = NULL; + typec_unregister_port(con->port); + con->port = NULL; +} + static u64 ucsi_get_supported_notifications(struct ucsi *ucsi) { u16 features = ucsi->cap.features; @@ -1971,22 +2007,8 @@ static int ucsi_init(struct ucsi *ucsi) for (i = 0; i < ucsi->cap.num_connectors; i++) lockdep_unregister_key(&connector[i].lock_key); - for (con = connector; con->port; con++) { - if (con->wq) - destroy_workqueue(con->wq); - ucsi_unregister_partner(con); - ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); - ucsi_unregister_port_psy(con); - - usb_power_delivery_unregister_capabilities(con->port_sink_caps); - con->port_sink_caps = NULL; - usb_power_delivery_unregister_capabilities(con->port_source_caps); - con->port_source_caps = NULL; - usb_power_delivery_unregister(con->pd); - con->pd = NULL; - typec_unregister_port(con->port); - con->port = NULL; - } + for (con = connector; con->port; con++) + ucsi_unregister_port(con); kfree(connector); err_reset: memset(&ucsi->cap, 0, sizeof(ucsi->cap)); @@ -2214,33 +2236,7 @@ void ucsi_unregister(struct ucsi *ucsi) for (i = 0; i < ucsi->cap.num_connectors; i++) { cancel_work_sync(&ucsi->connector[i].work); - - if (ucsi->connector[i].wq) { - struct ucsi_work *uwork; - - mutex_lock(&ucsi->connector[i].lock); - /* - * queue delayed items immediately so they can execute - * and free themselves before the wq is destroyed - */ - list_for_each_entry(uwork, &ucsi->connector[i].partner_tasks, node) - mod_delayed_work(ucsi->connector[i].wq, &uwork->work, 0); - mutex_unlock(&ucsi->connector[i].lock); - destroy_workqueue(ucsi->connector[i].wq); - } - - ucsi_unregister_partner(&ucsi->connector[i]); - ucsi_unregister_altmodes(&ucsi->connector[i], - UCSI_RECIPIENT_CON); - ucsi_unregister_port_psy(&ucsi->connector[i]); - - usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_sink_caps); - ucsi->connector[i].port_sink_caps = NULL; - usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_source_caps); - ucsi->connector[i].port_source_caps = NULL; - usb_power_delivery_unregister(ucsi->connector[i].pd); - ucsi->connector[i].pd = NULL; - typec_unregister_port(ucsi->connector[i].port); + ucsi_unregister_port(&ucsi->connector[i]); lockdep_unregister_key(&ucsi->connector[i].lock_key); } From 0583f2fbf8f86ae3a0ce054f96783dd83e65d9bb Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 9 Jul 2026 02:09:04 +0000 Subject: [PATCH 11/30] usb: gadget: udc: bdc: free IRQ and drain func_wake_notify before teardown The Broadcom BDC UDC driver registers its IRQ handler with devm_request_irq() in bdc_udc_init(), so the IRQ is released by devm only after bdc_remove() returns. devm releases resources in reverse LIFO order, but bdc_remove() runs bdc_udc_exit() and bdc_hw_exit() -> bdc_mem_free() manually before returning: bdc_udc_exit() tears down individual endpoint objects via bdc_free_ep(), while bdc_hw_exit() -> bdc_mem_free() frees and NULLs the DMA-coherent status-report ring (bdc->srr.sr_bds) and kfree()s bdc->bdc_ep_array. Both happen while the IRQ handler (bdc_udc_interrupt, requested with IRQF_SHARED) remains deliverable in the window up to the post-remove devm free_irq(). On receipt of a shared interrupt in that window, bdc_udc_interrupt() dereferences bdc->srr.sr_bds[bdc->srr.dqp_index] (NULL or freed DMA) and dispatches sr_handler callbacks that index into bdc_ep_array, causing a NULL-deref or use-after-free. The same window affects the delayed_work bdc->func_wake_notify, which is armed from the IRQ handler via bdc_sr_uspc() -> handle_link_state_change() -> schedule_delayed_work() and may self-rearm from its own callback bdc_func_wake_timer(). No cancel exists anywhere in the driver, so a queued work item that fires after bdc_remove() returns and the bdc structure is devm-freed dereferences freed memory. Replace devm_request_irq() with request_irq() and add an explicit free_irq(bdc->irq, bdc) in bdc_remove(). Clear BDC_GIE before free_irq() to stop the device from asserting interrupts, then free_irq() drains any in-flight handler, then cancel_delayed_work_sync() drains the func_wake_notify delayed work. This ordering ensures the IRQ handler and delayed work cannot interfere with the subsequent endpoint and DMA teardown in bdc_udc_exit() and bdc_hw_exit(). Wire the matching free_irq() into the bdc_udc_init() error path so the IRQ is released on probe failure, and route the bdc_init_ep() failure through err0 instead of returning directly. This issue was found by an in-house static analysis tool. Fixes: efed421a94e6 ("usb: gadget: Add UDC driver for Broadcom USB3.0 device controller IP BDC") Cc: stable Assisted-by: Codex:gpt-5.5 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260709020904.502611-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_core.c | 20 ++++++++++++++++++++ drivers/usb/gadget/udc/bdc/bdc_udc.c | 7 ++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 438201dc96ca..a8dbaef54aba 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -585,9 +585,29 @@ static int bdc_probe(struct platform_device *pdev) static void bdc_remove(struct platform_device *pdev) { struct bdc *bdc; + unsigned long flags; + u32 temp; bdc = platform_get_drvdata(pdev); dev_dbg(bdc->dev, "%s ()\n", __func__); + /* + * Disable the device interrupt source before freeing the IRQ: + * clear BDC_GIE so the controller stops asserting interrupts, + * then free_irq drains any in-flight handler. + */ + spin_lock_irqsave(&bdc->lock, flags); + temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp &= ~BDC_GIE; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + spin_unlock_irqrestore(&bdc->lock, flags); + free_irq(bdc->irq, bdc); + /* + * Drain func_wake_notify after free_irq: the IRQ handler arms this + * delayed_work via bdc_sr_uspc -> handle_link_state_change -> + * schedule_delayed_work (self-rearmed in bdc_func_wake_timer), so + * the IRQ must be released first to prevent re-arm after cancel. + */ + cancel_delayed_work_sync(&bdc->func_wake_notify); bdc_udc_exit(bdc); bdc_hw_exit(bdc); bdc_phy_exit(bdc); diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c index 23826fd7a8e6..7a12219edac6 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_udc.c +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -530,8 +530,8 @@ int bdc_udc_init(struct bdc *bdc) bdc->gadget.name = BRCM_BDC_NAME; - ret = devm_request_irq(bdc->dev, bdc->irq, bdc_udc_interrupt, - IRQF_SHARED, BRCM_BDC_NAME, bdc); + ret = request_irq(bdc->irq, bdc_udc_interrupt, IRQF_SHARED, + BRCM_BDC_NAME, bdc); if (ret) { dev_err(bdc->dev, "failed to request irq #%d %d\n", @@ -542,7 +542,7 @@ int bdc_udc_init(struct bdc *bdc) ret = bdc_init_ep(bdc); if (ret) { dev_err(bdc->dev, "bdc init ep fail: %d\n", ret); - return ret; + goto err0; } ret = usb_add_gadget_udc(bdc->dev, &bdc->gadget); @@ -571,6 +571,7 @@ int bdc_udc_init(struct bdc *bdc) err1: usb_del_gadget_udc(&bdc->gadget); err0: + free_irq(bdc->irq, bdc); bdc_free_ep(bdc); return ret; From 5650c18d93a1db7e27cb5a40b394747eb4686d5b Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 9 Jul 2026 15:07:17 +0000 Subject: [PATCH 12/30] usb: gadget: f_midi: cancel pending IN work before freeing the midi object The f_midi driver embeds a work item (midi->work) whose handler, f_midi_in_work(), dereferences the enclosing struct f_midi through container_of(). This work is armed from two sites: f_midi_complete(), on a normal IN-endpoint completion, and f_midi_in_trigger(), on an ALSA rawmidi output-stream start. Neither f_midi_disable() nor f_midi_unbind() cancels midi->work. f_midi_disable() only disables the endpoints and drains the in_req_fifo; it does not synchronize the work item, and the sound card is released asynchronously to the final free of the midi object. The midi object is reference-counted (midi->free_ref) and is freed in f_midi_free() only once both the usb_function reference and the rawmidi private_data reference have been dropped. In f_midi_unbind(), f_midi_disable() runs before the sound card is released, so while the USB endpoints are already disabled the rawmidi device is still usable by an open substream. A concurrent userspace write on such a substream can reach f_midi_in_trigger() and queue midi->work again after f_midi_disable() has returned. A work item armed this way may still be pending when the last reference drops and f_midi_free() proceeds to kfree(midi), letting f_midi_in_work() dereference the struct after it has been freed, a use-after-free. For this reason cancelling midi->work in f_midi_disable() would not be sufficient: the ALSA trigger path can rearm the work after disable() returns. Cancelling at the refcount-zero free site is the boundary after which neither arming source can survive, because by then both references that keep the midi object alive have been dropped: the USB endpoints are already disabled and the rawmidi device has been released. Fix this by calling cancel_work_sync(&midi->work) in the refcount-zero block of f_midi_free(), before the embedded work_struct is freed along with the rest of the structure. opts->lock is a sleeping mutex, so calling cancel_work_sync() under it is permitted, and the handler takes midi->transmit_lock rather than opts->lock, so no self-deadlock can occur while it waits for a running instance of the work to finish. This issue was found by an in-house static analysis tool. Fixes: 8653d71ce3763 ("usb/gadget: f_midi: Replace tasklet with work") Cc: stable Assisted-by: Codex:gpt-5.5 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260709150717.399083-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 4d9e4bd700d8..fba8cf787d6c 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -1309,6 +1309,7 @@ static void f_midi_free(struct usb_function *f) opts = container_of(f->fi, struct f_midi_opts, func_inst); mutex_lock(&opts->lock); if (!--midi->free_ref) { + cancel_work_sync(&midi->work); kfree(midi->id); kfifo_free(&midi->in_req_fifo); kfree(midi); From c2e819be6a5c7f34344926b4bd7e3dfca58cf48a Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Thu, 9 Jul 2026 21:56:22 +0100 Subject: [PATCH 13/30] usb: gadget: printer: fix infinite loop in printer_read() printer_read() uses the same variable for the requested copy size and the number of bytes actually copied to user space. copy_to_user() returns the number of bytes not copied, so when it fails to copy anything, the computed copied length becomes zero. In that case len, buf, current_rx_bytes and current_rx_buf are left unchanged. If RX data is available and the user buffer remains unwritable, the read loop can repeat indefinitely. Track the copied length separately and return -EFAULT, or the number of bytes already copied, if an iteration makes no progress. Fixes: b185f01a9ab7 ("usb: gadget: printer: factor out f_printer") Cc: stable Reviewed-by: Peter Chen Signed-off-by: Melbin K Mathew Link: https://patch.msgid.link/20260709205622.55700-1-mlbnkm1@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_printer.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 837f753d0cae..1857d786110b 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -431,7 +431,7 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) { struct printer_dev *dev = fd->private_data; unsigned long flags; - size_t size; + size_t size, not_copied, copied; size_t bytes_copied; struct usb_request *req; /* This is a pointer to the current USB rx request. */ @@ -524,10 +524,12 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) else size = len; - size -= copy_to_user(buf, current_rx_buf, size); - bytes_copied += size; - len -= size; - buf += size; + not_copied = copy_to_user(buf, current_rx_buf, size); + copied = size - not_copied; + + bytes_copied += copied; + len -= copied; + buf += copied; spin_lock_irqsave(&dev->lock, flags); @@ -542,6 +544,17 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) if (dev->interface < 0) goto out_disabled; + if (!copied) { + dev->current_rx_req = current_rx_req; + dev->current_rx_bytes = current_rx_bytes; + dev->current_rx_buf = current_rx_buf; + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return bytes_copied ? bytes_copied : -EFAULT; + } + + size = copied; + /* If we not returning all the data left in this RX request * buffer then adjust the amount of data left in the buffer. * Othewise if we are done with this RX request buffer then From 42a97c0480f96a2977e6d51ce512adc780f1ef5d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 8 Jul 2026 16:31:35 +0200 Subject: [PATCH 14/30] USB: serial: keyspan_pda: fix data loss on receive throttling Killing the interrupt-in urb when the line disciple requests throttling may lead to data loss if an ongoing transfer is cancelled. Instead set a flag to prevent the completion handler from resubmitting the urb until the port is unthrottled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 44 +++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index f05bcce60600..dd4cfd17f7ad 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -33,6 +33,8 @@ struct keyspan_pda_private { struct work_struct unthrottle_work; struct usb_serial *serial; struct usb_serial_port *port; + bool throttled; + bool throttle_req; }; static int keyspan_pda_write_start(struct usb_serial_port *port); @@ -148,6 +150,7 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) int retval; int status = urb->status; struct keyspan_pda_private *priv; + bool throttled = false; unsigned long flags; priv = usb_get_serial_port_data(port); @@ -209,16 +212,24 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) } exit: - retval = usb_submit_urb(urb, GFP_ATOMIC); - if (retval) - dev_err(&port->dev, - "%s - usb_submit_urb failed with result %d\n", - __func__, retval); + spin_lock_irqsave(&port->lock, flags); + if (priv->throttle_req) { + priv->throttled = true; + throttled = true; + } + spin_unlock_irqrestore(&port->lock, flags); + + if (!throttled) { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, "failed to resubmit in urb: %d\n", retval); + } } static void keyspan_pda_rx_throttle(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); /* * Stop receiving characters. We just turn off the URB request, and @@ -228,16 +239,29 @@ static void keyspan_pda_rx_throttle(struct tty_struct *tty) * send an XOFF, although it might make sense to foist that off upon * the device too. */ - usb_kill_urb(port->interrupt_in_urb); + spin_lock_irq(&port->lock); + priv->throttle_req = true; + spin_unlock_irq(&port->lock); } static void keyspan_pda_rx_unthrottle(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + bool throttled; + int ret; - /* just restart the receive interrupt URB */ - if (usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL)) - dev_dbg(&port->dev, "usb_submit_urb(read urb) failed\n"); + spin_lock_irq(&port->lock); + throttled = priv->throttled; + priv->throttled = false; + priv->throttle_req = false; + spin_unlock_irq(&port->lock); + + if (throttled) { + ret = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (ret) + dev_err(&port->dev, "failed to submit in urb: %d\n", ret); + } } static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) @@ -577,6 +601,8 @@ static int keyspan_pda_open(struct tty_struct *tty, spin_lock_irq(&port->lock); priv->tx_room = rc; + priv->throttled = false; + priv->throttle_req = false; spin_unlock_irq(&port->lock); rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); From fad0fd120e29041b3e6cdf41bb12e3184fb524a2 Mon Sep 17 00:00:00 2001 From: Tim Pambor Date: Sat, 11 Jul 2026 17:36:30 +0000 Subject: [PATCH 15/30] USB: serial: ftdi_sio: add support for E+H FXA291 The Commubox FXA291 by Endress+Hauser AG is a USB serial converter based on FT232B which is used to communicate with field devices. It enumerates using the FTDI vendor ID and a custom PID. usb 1-9: New USB device found, idVendor=0403, idProduct=e510, bcdDevice= 4.00 usb 1-9: New USB device strings: Mfr=1, Product=2, SerialNumber=0 usb 1-9: Product: FXA291 usb 1-9: Manufacturer: Endress+Hauser usb 1-9: SerialNumber: 00000000 ftdi_sio 1-9:1.0: FTDI USB Serial Device converter detected usb 1-9: Detected FT232B usb 1-9: FTDI USB Serial Device converter now attached to ttyUSB0 Signed-off-by: Tim Pambor Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 2 ++ drivers/usb/serial/ftdi_sio_ids.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 88dd32da82c2..c6ffa23bcc8f 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1072,6 +1072,8 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 3) }, /* Abacus Electrics */ { USB_DEVICE(FTDI_VID, ABACUS_OPTICAL_PROBE_PID) }, + /* Endress+Hauser AG devices */ + { USB_DEVICE(FTDI_VID, FTDI_EH_FXA291_PID) }, { } /* Terminating entry */ }; diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index 6c76cfebfd0e..9c83c17853c8 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -313,6 +313,11 @@ #define FTDI_ELV_UDF77_PID 0xFB5E /* USB DCF Funkuhr (UDF 77) */ #define FTDI_ELV_UIO88_PID 0xFB5F /* USB-I/O Interface (UIO 88) */ +/* + * Endress+Hauser AG product ids (FTDI_VID) + */ +#define FTDI_EH_FXA291_PID 0xE510 + /* * EVER Eco Pro UPS (http://www.ever.com.pl/) */ From 3b4ca2e01c1dd8c00b675b794732945f460a471b Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Thu, 16 Jul 2026 11:33:41 +0800 Subject: [PATCH 16/30] USB: storage: add NO_ATA_1X quirk for Longmai USB Key The Longmai Technologies USB Key (0x04b4:0xb708) advertises itself as a SCSI/Bulk-only mass storage device but does not correctly handle ATA pass-through commands. When such a command (ATA_12 or ATA_16) is sent to the device it fails to respond and the transfer eventually times out, leaving the device unusable. Add an unusual_devs entry for this device that sets the US_FL_NO_ATA_1X flag, so usb-storage short-circuits ATA pass-through commands and returns INVALID COMMAND OPERATION CODE (0x20 0x05 0x24 0x00) instead of forwarding them to the device. Information about the device in /sys/kernel/debug/usb/devices: T: Bus=02 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 12 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=06 Prot=50 MxPS=64 #Cfgs= 1 P: Vendor=04b4 ProdID=b708 Rev= 1.00 S: Manufacturer=Longmai Technologies S: Product=USB Key C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=100mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Reported-by: Ai Chao Cc: stable Signed-off-by: Huang Wei Acked-by: Alan Stern Link: https://patch.msgid.link/20260716033341.2830872-1-huangwei@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 255968f9ca42..ac22fa318734 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -395,6 +395,13 @@ UNUSUAL_DEV( 0x04b3, 0x4001, 0x0110, 0x0110, USB_SC_DEVICE, USB_PR_CB, NULL, US_FL_MAX_SECTORS_MIN), +/* Reported by Ai Chao */ +UNUSUAL_DEV( 0x04b4, 0xb708, 0x0000, 0xffff, + "Longmai Technologies", + "USB Key", + USB_SC_SCSI, USB_PR_BULK, NULL, + US_FL_NO_ATA_1X), + /* * Reported by Simon Levitt * This entry needs Sub and Proto fields From e0b291fe117964037e0ba382eff4bb365d531c3a Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Thu, 11 Jun 2026 15:12:01 +0800 Subject: [PATCH 17/30] usb: core: port: Deattach Type-C connector on component unbind connector_unbind() is the mirror of connector_bind(), but it is missing the symmetric call to typec_deattach() that connector_bind() makes via: if (port_dev->child) typec_attach(port_dev->connector, &port_dev->child->dev); When a Thunderbolt dock is unplugged, two teardown paths race: 1. The component framework calls connector_unbind() first, which sets port_dev->connector = NULL without calling typec_deattach(). This leaves port->usb2_dev/port->usb3_dev in struct typec_port pointing at the USB device that is about to be freed. 2. usb_disconnect() then calls typec_deattach(port_dev->connector, ...), but port_dev->connector is already NULL, so the call is a no-op and port->usb2_dev is never cleared. 3. Concurrently, UCSI detects a PD partner-disconnect event and calls typec_unregister_partner(), which reads port->usb2_dev (now a dangling pointer to freed memory) and passes it to typec_partner_unlink_device() -> sysfs_remove_link() -> dev_name() on the freed device, corrupting the typec/UCSI partner state. This corruption leaves the Thunderbolt tunnel in an inconsistent state on the next dock hot-plug. On affected hardware the dock's I225/igc NIC fails to enumerate: AER fires a slot reset while the igc driver is still initialising ("PCIe link lost"), and the subsequent igc_reset attempt hits igc_rd32 on an already-detached device: igc 0000:2e:00.0 eth0: PCIe link lost, device now detached igc: Failed to read reg 0x0! WARNING: CPU: 9 PID: 129 at drivers/net/ethernet/intel/igc/igc_main.c:7005 igc_rd32+0xa4/0xc0 [igc] Call Trace: igc_disable_pcie_master+0x16/0xa0 [igc] igc_reset_hw_base+0x14/0x170 [igc] igc_reset+0x63/0x110 [igc] igc_io_slot_reset+0x9e/0xd0 [igc] report_slot_reset+0x5d/0xc0 pcie_do_recovery+0x209/0x400 aer_isr_one_error_type+0x235/0x430 aer_isr+0x4e/0x80 irq_thread+0xf4/0x1f0 4. UCSI later handles the PD partner-disconnect and calls typec_unregister_partner(), which still sees the stale port->usb2_dev and tries to remove its sysfs link a second time: kernfs: can not remove 'typec', no directory WARNING: CPU: 6 PID: 55 at fs/kernfs/dir.c:1706 kernfs_remove_by_name_ns+0xe9/0xf0 Workqueue: events ucsi_handle_connector_change [typec_ucsi] Call Trace: sysfs_remove_link+0x19/0x50 typec_unregister_partner+0x6e/0x120 [typec] ucsi_unregister_partner+0x107/0x150 [typec_ucsi] ucsi_handle_connector_change+0x3ec/0x490 [typec_ucsi] process_one_work+0x18e/0x3e0 worker_thread+0x2e3/0x420 kthread+0x10a/0x230 ret_from_fork+0x121/0x140 ret_from_fork_asm+0x1a/0x30 With worse timing the same stale pointer is dereferenced after the backing memory is freed, turning the warning into a use-after-free. Fix the asymmetry: call typec_deattach() before clearing port_dev->connector, matching what connector_bind() does on the bind side. typec_partner_deattach() is already protected by port->partner_link_lock, so it serialises safely with the concurrent typec_unregister_partner() path. Fixes: 11110783f5ea ("usb: Inform the USB Type-C class about enumerated devices") Cc: stable Signed-off-by: Chia-Lin Kao (AceLan) Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260611071201.1235545-1-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/port.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index b1364f0c384c..b4452b665f59 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -740,6 +740,8 @@ static void connector_unbind(struct device *dev, struct device *connector, void sysfs_remove_link(&connector->kobj, dev_name(dev)); sysfs_remove_link(&dev->kobj, "connector"); + if (port_dev->child) + typec_deattach(port_dev->connector, &port_dev->child->dev); port_dev->connector = NULL; } From c947360ae63eee1c9eacc030dd6f5a53f717addf Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 13 Jul 2026 19:47:11 +0800 Subject: [PATCH 18/30] usb: musb: omap2430: Do not put borrowed of_node in probe omap2430_probe() stores pdev->dev.of_node in a local np variable. This is a borrowed pointer and the probe function does not take a reference to it. The success and error paths nevertheless call of_node_put(np). This drops a reference that is owned by the platform device, and can leave pdev->dev.of_node with an unbalanced reference count. Do not put the borrowed platform device node from omap2430_probe(). References taken for the child MUSB device are handled by the device core, and the ctrl-module phandle reference is still released separately. Fixes: ffbe2feac59b ("usb: musb: omap2430: Fix probe regression for missing resources") Cc: stable Reviewed-by: Johan Hovold Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260713114711.955253-1-lgs201920130244@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/omap2430.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 333ab79f0ca9..6e749faac33c 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -454,7 +454,6 @@ static int omap2430_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to register musb device\n"); goto err_disable_rpm; } - of_node_put(np); return 0; @@ -464,7 +463,6 @@ static int omap2430_probe(struct platform_device *pdev) if (!IS_ERR(glue->control_otghs)) put_device(glue->control_otghs); err_put_musb: - of_node_put(np); platform_device_put(musb); return ret; From 4e0197fbb0eec588795d5431716a244d9ac8fa93 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Wed, 15 Jul 2026 16:59:05 +0200 Subject: [PATCH 19/30] usb: core: sysfs: add lock to bos_descriptors_read() Add a lock to the function bos_descriptors_read(). This function accesses udev->bos, which could be simultaneously freed in usb_reset_and_verify_device(), a function that is commonly called in drivers all over the kernel. Assisted-by: gkh_clanker_t1000 Signed-off-by: Griffin Kroah-Hartman Acked-by: Alan Stern Cc: stable Link: https://patch.msgid.link/20260715-usb_core_patches_3-v1-1-53021f5576fd@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/sysfs.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index a07866f1060c..d22dc78457d7 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -899,10 +899,15 @@ bos_descriptors_read(struct file *filp, struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct usb_device *udev = to_usb_device(dev); - struct usb_host_bos *bos = udev->bos; + struct usb_host_bos *bos; struct usb_bos_descriptor *desc; size_t desclen, n = 0; + int rc; + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + bos = udev->bos; if (bos) { desc = bos->desc; desclen = le16_to_cpu(desc->wTotalLength); @@ -911,6 +916,7 @@ bos_descriptors_read(struct file *filp, struct kobject *kobj, memcpy(buf, (void *) desc + off, n); } } + usb_unlock_device(udev); return n; } static const BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */ From fc3afb5728e297994863f8a2a01b88a920bbf53e Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 16 Jul 2026 18:41:26 +0800 Subject: [PATCH 20/30] usb: chipidea: fix usage_count leak when autosuspend_delay is negative The probe() calls pm_runtime_use_autosuspend(), but remove() does not call pm_runtime_dont_use_autosuspend(). This can lead to a usage_count leak if autosuspend_delay is set to a negative value. The pm_runtime_use_autosuspend() also notes that it's important to undo this with pm_runtime_dont_use_autosuspend() at driver exit time. Fixes: 1f874edcb731 ("usb: chipidea: add runtime power management support") Cc: stable Assisted-by: Claude:claude-sonnet-4.6 Signed-off-by: Xu Yang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260716104126.2763454-1-xu.yang_2@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 07563be0013f..b53672f3ea63 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -1250,6 +1250,7 @@ static void ci_hdrc_remove(struct platform_device *pdev) usb_role_switch_unregister(ci->role_switch); if (ci->supports_runtime_pm) { + pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); From d5e5cd3654d2b5359a12ea6586120f05b28634ee Mon Sep 17 00:00:00 2001 From: Jinchao Wang Date: Thu, 16 Jul 2026 06:42:17 -0400 Subject: [PATCH 21/30] usb: gadget: dummy_hcd: prevent fifo_req reuse during giveback dummy_hcd embeds a single shared usb_request (dum->fifo_req) that the "emulated single-request FIFO" fast-path in dummy_queue() reuses for small IN transfers: it copies the caller's request into it (req->req = *_req) and queues it, treating list_empty(&fifo_req.queue) as "the slot is free". The completion side (dummy_timer/transfer/nuke/dummy_dequeue) follows the standard pattern: list_del_init(&req->queue) unlinks the request, then the lock is dropped and usb_gadget_giveback_request() invokes req->complete(). But list_del_init() makes fifo_req.queue look empty *before* the completion callback returns, so a concurrent dummy_queue() on another CPU sees the slot as free, reuses fifo_req and runs req->req = *_req -- overwriting req->complete while dummy_timer is mid-calling it. The indirect call then jumps to a clobbered pointer, causing a general protection fault / page fault in dummy_timer (syzkaller extid faf3a6cf579fc65591ca). The clobbering write is an in-bounds memcpy on a live shared object, so KASAN cannot flag it. Add a fifo_req_busy bit covering the shared request's whole lifetime: set it in dummy_queue() when the FIFO fast-path takes fifo_req (making it the fast-path guard, replacing the list_empty(&fifo_req.queue) test), and clear it after the completion callback has returned, via a dummy_giveback() helper used at all four gadget-request giveback sites. The shared slot can no longer be reused until its completion callback has finished. Reported-by: syzbot+faf3a6cf579fc65591ca@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=faf3a6cf579fc65591ca Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Signed-off-by: Jinchao Wang Reviewed-by: Alan Stern Link: https://patch.msgid.link/5db8bba5b3499a86cd2e776f9918126b68b2508b.1784198306.git.wangjinchao600@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 40 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index f47903461ed5..c0e40fa6dde5 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -278,6 +278,7 @@ struct dummy { unsigned ints_enabled:1; unsigned udc_suspended:1; unsigned pullup:1; + unsigned fifo_req_busy:1; /* * HOST side support @@ -329,6 +330,26 @@ static inline struct dummy *gadget_dev_to_dummy(struct device *dev) /* DEVICE/GADGET SIDE UTILITY ROUTINES */ +/* + * Give back a gadget request with dum->lock dropped around the callback. + * If @req is the shared fifo_req, clear fifo_req_busy afterward: the flag + * was set in dummy_queue() when the shared request was taken and must stay + * set until its completion callback has returned; list_del_init() alone + * makes the request look idle while the callback is still running. + * Caller holds dum->lock and has already done list_del_init() + status. + */ +static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + struct dummy_request *req) +{ + bool fifo = req == &dum->fifo_req; + + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); + if (fifo) + dum->fifo_req_busy = 0; +} + /* called with spinlock held */ static void nuke(struct dummy *dum, struct dummy_ep *ep) { @@ -339,9 +360,7 @@ static void nuke(struct dummy *dum, struct dummy_ep *ep) list_del_init(&req->queue); req->req.status = -ESHUTDOWN; - spin_unlock(&dum->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dum->lock); + dummy_giveback(dum, &ep->ep, req); } } @@ -728,10 +747,11 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, /* implement an emulated single-request FIFO */ if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && - list_empty(&dum->fifo_req.queue) && + !dum->fifo_req_busy && list_empty(&ep->queue) && _req->length <= FIFO_SIZE) { req = &dum->fifo_req; + dum->fifo_req_busy = 1; req->req = *_req; req->req.buf = dum->fifo_buf; memcpy(dum->fifo_buf, _req->buf, _req->length); @@ -785,9 +805,7 @@ static int dummy_dequeue(struct usb_ep *_ep, struct usb_request *_req) dev_dbg(udc_dev(dum), "dequeued req %p from %s, len %d buf %p\n", req, _ep->name, _req->length, _req->buf); - spin_unlock(&dum->lock); - usb_gadget_giveback_request(_ep, _req); - spin_lock(&dum->lock); + dummy_giveback(dum, _ep, req); } spin_unlock_irqrestore(&dum->lock, flags); return retval; @@ -1523,9 +1541,7 @@ static int transfer(struct dummy_hcd *dum_hcd, struct urb *urb, if (req->req.status != -EINPROGRESS) { list_del_init(&req->queue); - spin_unlock(&dum->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dum->lock); + dummy_giveback(dum, &ep->ep, req); /* requests might have been unlinked... */ rescan = 1; @@ -1910,9 +1926,7 @@ static enum hrtimer_restart dummy_timer(struct hrtimer *t) dev_dbg(udc_dev(dum), "stale req = %p\n", req); - spin_unlock(&dum->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dum->lock); + dummy_giveback(dum, &ep->ep, req); ep->already_seen = 0; goto restart; } From 67c92c6419ea6dbc5b1f3e9691aecea956e3e81c Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Mon, 13 Jul 2026 16:43:21 +0800 Subject: [PATCH 22/30] usb: typec: ucsi: Detect and skip duplicate altmodes from buggy firmware Some firmware implementations incorrectly return the same altmode multiple times at different offsets when queried via UCSI_GET_ALTERNATE_MODES. This causes sysfs duplicate filename errors and kernel call traces when the driver attempts to register the same altmode twice: sysfs: cannot create duplicate filename '/devices/.../typec/port0/port0.0/partner' typec-thunderbolt port0-partner.1: failed to create symlinks typec-thunderbolt port0-partner.1: probe with driver typec-thunderbolt failed with error -17 The matching rules differ by recipient: - UCSI_RECIPIENT_CON (port) and UCSI_RECIPIENT_SOP_P (plug): Two altmodes with identical SVID and VDO are byte-for-byte duplicates and the second has no observable function, so drop it. - UCSI_RECIPIENT_SOP (partner): The typec class binds each partner altmode to a port altmode of the same SVID via altmode_match()/device_find_child(), which returns the first port altmode with a matching SVID. If the partner advertises more altmodes for SVID X than the port advertises, the surplus partner altmode(s) collapse onto an already-paired port altmode and trigger the "duplicate filename .../partner" sysfs error during typec_altmode_create_links(). Use the port-side altmode count for SVID X as the authoritative cap and reject any partner altmode that would exceed it. This preserves legitimate multi-Mode partner altmodes (vendor SVIDs that the port really does advertise more than once) while filtering the firmware-generated duplicates that have no port counterpart, and is therefore stricter than a plain SVID+VDO comparison (which still admits the Thunderbolt case where firmware reports the same SVID twice with different VDOs) without being over-broad like a plain SVID match (which would falsely drop legitimate vendor multi-Mode entries). If a duplicate is detected, skip it and emit a clean warning instead of generating a kernel call trace: ucsi_acpi USBC000:00: con2: Firmware bug: duplicate partner altmode SVID 0x8087 at offset 1, ignoring. ucsi_acpi USBC000:00: con2: VDO mismatch: 0x8087a043 vs 0x00000001 The duplicate detection logic lives in a reusable helper ucsi_altmode_is_duplicate() and is invoked from ucsi_register_altmodes(). It applies to all three recipient types: partner (SOP), port (CON), and plug (SOP_P) altmodes. Fixes: a79f16efcd00 ("usb: typec: ucsi: Add support for the partner USB Modes") Cc: stable Signed-off-by: Chia-Lin Kao (AceLan) unchanged: still SVID+VDO exact-dup match. Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260713084323.287516-1-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 132 ++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f56897e23df8..0db43521af3f 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -529,6 +529,129 @@ static int ucsi_register_altmode(struct ucsi_connector *con, return ret; } +static void ucsi_dump_duplicate_altmode(struct ucsi_connector *con, + u8 recipient, u16 svid, + u32 existing_vdo, u32 new_vdo, + int offset) +{ + static const char * const recipient_names[] = { + [UCSI_RECIPIENT_CON] = "port", + [UCSI_RECIPIENT_SOP] = "partner", + [UCSI_RECIPIENT_SOP_P] = "plug", + [UCSI_RECIPIENT_SOP_PP] = "cable plug prime", + }; + + dev_warn(con->ucsi->dev, + "con%d: Firmware bug: duplicate %s altmode SVID 0x%04x at offset %d, ignoring but please contact the BIOS vendor to fix this issue.\n", + con->num, recipient_names[recipient], svid, offset); + + if (existing_vdo != new_vdo) + dev_warn(con->ucsi->dev, + "con%d: VDO mismatch: 0x%08x vs 0x%08x\n", + con->num, existing_vdo, new_vdo); +} + +/* Count altmodes in @altmodes that advertise @svid. */ +static int ucsi_altmode_count_svid(struct typec_altmode **altmodes, u16 svid) +{ + int count = 0; + int k; + + for (k = 0; k < UCSI_MAX_ALTMODES; k++) { + if (!altmodes[k]) + break; + if (altmodes[k]->svid == svid) + count++; + } + + return count; +} + +/* + * Check if an altmode is a duplicate. Some firmware implementations + * incorrectly return the same altmode multiple times, causing sysfs errors. + * Returns true if the altmode should be skipped. + * + * The matching rules differ by recipient: + * + * - UCSI_RECIPIENT_CON (port) and UCSI_RECIPIENT_SOP_P (plug): + * Two altmodes with identical SVID and VDO are byte-for-byte duplicates + * and the second has no observable function. Drop them. + * + * - UCSI_RECIPIENT_SOP (partner): + * The typec class binds each partner altmode to a port altmode of the + * same SVID via altmode_match()/device_find_child(), which returns the + * first port altmode with a matching SVID. If the partner advertises + * more altmodes for SVID X than the port advertises, the surplus + * partner altmode(s) collapse onto an already-paired port altmode and + * trigger a "duplicate filename .../partner" sysfs error during + * typec_altmode_create_links(). Use the port-side altmode count for + * SVID X as the authoritative cap and reject any partner altmode that + * would exceed it. This preserves legitimate multi-Mode partner + * altmodes (e.g. vendor SVIDs that the port really does advertise + * twice) while filtering the firmware-generated duplicates that have + * no port counterpart. + */ +static bool ucsi_altmode_is_duplicate(struct ucsi_connector *con, u8 recipient, + const struct ucsi_altmode *alt_batch, int batch_idx, + u16 svid, u32 vdo, int offset) +{ + struct typec_altmode **altmodes; + int port_count, partner_count; + int k; + + /* Check for duplicates within the current batch first */ + for (k = 0; k < batch_idx; k++) { + if (alt_batch[k].svid == svid && alt_batch[k].mid == vdo) { + ucsi_dump_duplicate_altmode(con, recipient, svid, + vdo, vdo, offset); + return true; + } + } + + switch (recipient) { + case UCSI_RECIPIENT_SOP: + /* + * Cap partner altmodes per SVID by the port-side count: + * any further partner altmode for that SVID would alias an + * already-paired port altmode and break typec sysfs. + */ + port_count = ucsi_altmode_count_svid(con->port_altmode, svid); + partner_count = ucsi_altmode_count_svid(con->partner_altmode, + svid); + if (port_count && partner_count >= port_count) { + ucsi_dump_duplicate_altmode(con, recipient, svid, + con->partner_altmode[partner_count - 1]->vdo, + vdo, offset); + return true; + } + return false; + case UCSI_RECIPIENT_CON: + altmodes = con->port_altmode; + break; + case UCSI_RECIPIENT_SOP_P: + altmodes = con->plug_altmode; + break; + default: + return false; + } + + /* CON and SOP_P: drop only exact SVID+VDO duplicates. */ + for (k = 0; k < UCSI_MAX_ALTMODES; k++) { + if (!altmodes[k]) + break; + + if (altmodes[k]->svid != svid || altmodes[k]->vdo != vdo) + continue; + + ucsi_dump_duplicate_altmode(con, recipient, svid, + altmodes[k]->vdo, vdo, offset); + return true; + } + + return false; +} + static int ucsi_register_altmodes_nvidia(struct ucsi_connector *con, u8 recipient) { @@ -653,6 +776,15 @@ static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient) if (!alt[j].svid) return 0; + /* + * Check for duplicates in current batch and already + * registered altmodes. Skip if duplicate found. + */ + if (ucsi_altmode_is_duplicate(con, recipient, alt, j, + alt[j].svid, alt[j].mid, + i - num + j)) + continue; + memset(&desc, 0, sizeof(desc)); desc.vdo = alt[j].mid; desc.svid = alt[j].svid; From f1aa17f72f9b9589bd724dc826c5b17d164193d1 Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Mon, 13 Jul 2026 16:43:22 +0800 Subject: [PATCH 23/30] usb: typec: ucsi: Add duplicate detection to nvidia registration path Extend the duplicate altmode detection to ucsi_register_altmodes_nvidia() which is used when a driver provides the update_altmodes() callback. This ensures all drivers benefit from duplicate detection, whether they use the standard registration path or the nvidia path with update_altmodes callback. Without this fix, drivers using the nvidia path (like yoga_c630) would still encounter duplicate altmode registration errors from buggy firmware. Fixes: a79f16efcd00 ("usb: typec: ucsi: Add support for the partner USB Modes") Cc: stable Signed-off-by: Chia-Lin Kao (AceLan) Link: https://patch.msgid.link/20260713084323.287516-2-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 0db43521af3f..1ae4224d2dfc 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -706,19 +706,25 @@ ucsi_register_altmodes_nvidia(struct ucsi_connector *con, u8 recipient) /* now register altmodes */ for (i = 0; i < max_altmodes; i++) { - memset(&desc, 0, sizeof(desc)); - if (multi_dp) { - desc.svid = updated[i].svid; - desc.vdo = updated[i].mid; - } else { - desc.svid = orig[i].svid; - desc.vdo = orig[i].mid; - } - desc.roles = TYPEC_PORT_DRD; + struct ucsi_altmode *altmode_array = multi_dp ? updated : orig; - if (!desc.svid) + if (!altmode_array[i].svid) return 0; + /* + * Check for duplicates in current array and already + * registered altmodes. Skip if duplicate found. + */ + if (ucsi_altmode_is_duplicate(con, recipient, altmode_array, i, + altmode_array[i].svid, + altmode_array[i].mid, i)) + continue; + + memset(&desc, 0, sizeof(desc)); + desc.svid = altmode_array[i].svid; + desc.vdo = altmode_array[i].mid; + desc.roles = TYPEC_PORT_DRD; + ret = ucsi_register_altmode(con, &desc, recipient); if (ret) return ret; From 8706409bd45fb89350f213b8baac5f16a43bb4a4 Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Mon, 13 Jul 2026 16:43:23 +0800 Subject: [PATCH 24/30] usb: typec: ucsi: yoga_c630: Remove redundant duplicate altmode handling This reverts commit e0c48e42d818 ("usb: typec: ucsi: yoga-c630: remove duplicate AltModes"). The yoga_c630 driver previously implemented its own duplicate altmode detection in yoga_c630_ucsi_update_altmodes() to work around buggy EC firmware that returns duplicate AltModes instead of empty ones. With the introduction of the common ucsi_altmode_is_duplicate() helper in both the standard and nvidia registration paths, duplicate detection is now handled automatically in the core UCSI code. This makes the yoga_c630-specific implementation added in commit e0c48e42d818 ("usb: typec: ucsi: yoga-c630: remove duplicate AltModes") redundant. Remove yoga_c630_ucsi_update_altmodes() and its callback to eliminate code duplication and simplify the driver. Note that this causes the driver to switch back from the nvidia registration path to the standard path, which is the original behavior before commit e0c48e42d818 ("usb: typec: ucsi: yoga-c630: remove duplicate AltModes"). Both paths now include duplicate detection, ensuring the firmware bug is still properly handled. Signed-off-by: Chia-Lin Kao (AceLan) Link: https://patch.msgid.link/20260713084323.287516-3-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_yoga_c630.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c index 1be18d101842..bb7b09bc7c1c 100644 --- a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c +++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c @@ -139,28 +139,6 @@ static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi, return ret; } -static bool yoga_c630_ucsi_update_altmodes(struct ucsi *ucsi, - u8 recipient, - struct ucsi_altmode *orig, - struct ucsi_altmode *updated) -{ - int i; - - if (orig[0].svid == 0 || recipient != UCSI_RECIPIENT_SOP) - return false; - - /* EC is nice and repeats altmodes again and again. Ignore copies. */ - for (i = 1; i < UCSI_MAX_ALTMODES; i++) { - if (orig[i].svid == orig[0].svid) { - dev_dbg(ucsi->dev, "Found duplicate altmodes, starting from %d\n", i); - memset(&orig[i], 0, (UCSI_MAX_ALTMODES - i) * sizeof(*orig)); - break; - } - } - - return false; -} - static void yoga_c630_ucsi_update_connector(struct ucsi_connector *con) { if (con->num == 1) @@ -174,7 +152,6 @@ static const struct ucsi_operations yoga_c630_ucsi_ops = { .read_message_in = yoga_c630_ucsi_read_message_in, .sync_control = yoga_c630_ucsi_sync_control, .async_control = yoga_c630_ucsi_async_control, - .update_altmodes = yoga_c630_ucsi_update_altmodes, .update_connector = yoga_c630_ucsi_update_connector, }; From 71132cedd1ecbc4032d76e9928c18a10f7e39b80 Mon Sep 17 00:00:00 2001 From: Diego Fernando Mancera Gomez Date: Fri, 17 Jul 2026 02:07:04 -0600 Subject: [PATCH 25/30] usb: atm: ueagle-atm: reject descriptors that confuse probe and disconnect uea_probe() distinguishes a pre-firmware device from a post-firmware one using the USB id (UEA_IS_PREFIRM()), and stores a different object as the interface data in each case: a 'struct completion' for a pre-firmware device (to be waited on in .disconnect()), or a 'struct usbatm_data' for a post-firmware one. uea_disconnect() instead tells the two apart by the number of interfaces of the active configuration (a pre-firmware device exposes a single interface, ADI930 has 2 and eagle has 3), and casts the interface data accordingly. Because the two handlers use different criteria, a crafted device that advertises a pre-firmware id together with a multi-interface descriptor (or a post-firmware id with a single interface) makes them disagree: the small 'struct completion' stored by uea_probe() is then passed to usbatm_usb_disconnect(), which casts it to 'struct usbatm_data' and takes instance->serialize, reading past the end of the allocation: BUG: KASAN: slab-out-of-bounds in __mutex_lock+0x152a/0x1b80 Read of size 8 at addr ffff8880470e2c60 by task kworker/1:2/982 ... __mutex_lock+0x152a/0x1b80 usbatm_usb_disconnect+0x70/0x820 uea_disconnect+0x133/0x2c0 usb_unbind_interface+0x1dd/0x9e0 ... which belongs to the cache kmalloc-96 of size 96 The buggy address is located 0 bytes to the right of allocated 96-byte region [ffff8880470e2c00, ffff8880470e2c60) Reject such inconsistent descriptors in uea_probe() so that both handlers always make the same pre/post-firmware decision. Reported-by: syzbot+e62a973f8322b3bbe3ac@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e62a973f8322b3bbe3ac Fixes: e2674dfbed8a ("usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()") Signed-off-by: Diego Fernando Mancera Gomez Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260717080704.1264-1-diegomancera.dev@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/ueagle-atm.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 4e71ed679a76..4266a0cb7e3b 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -2549,6 +2549,7 @@ static struct usbatm_driver uea_usbatm_driver = { static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *usb = interface_to_usbdev(intf); + bool single_iface = usb->config->desc.bNumInterfaces == 1; int ret; uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n", @@ -2557,6 +2558,22 @@ static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) le16_to_cpu(usb->descriptor.bcdDevice), chip_name[UEA_CHIP_VERSION(id)]); + /* + * uea_probe() decides between the pre-firmware and post-firmware case + * from the USB id and stores a different object as interface data in + * each case: a struct completion for a pre-firmware device, a struct + * usbatm_data for a post-firmware one. uea_disconnect() instead tells + * the two apart by the number of interfaces (a pre-firmware device + * exposes a single interface, ADI930 has 2 and eagle has 3). A crafted + * device advertising a pre-firmware id together with a multi-interface + * descriptor (or the other way around) makes the two disagree, so that + * usbatm_usb_disconnect() treats the small completion object as a + * struct usbatm_data and reads out of bounds. Reject such inconsistent + * descriptors so both paths make the same decision. + */ + if (UEA_IS_PREFIRM(id) != single_iface) + return -ENODEV; + usb_reset_device(usb); if (UEA_IS_PREFIRM(id)) { From 52beeed5e5d257e1da3e2d2f2fb25bc1e3cdb6d2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:52:20 +0800 Subject: [PATCH 26/30] USB: serial: mxuport: validate firmware header size mxuport_probe() reads version bytes at fixed offsets after request_firmware() succeeds. Firmware loading success does not prove that the blob reaches the highest version offset. Reject short firmware images before reading the version bytes. This is source-level parser hardening; no affected device or crash was observed. Reviewed-by: Andrew Lunn Signed-off-by: Pengpeng Hou Fixes: ee467a1f2066 ("USB: serial: add Moxa UPORT 12XX/14XX/16XX driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index e3c5a1b97542..088d5dd8abb5 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -1080,6 +1080,13 @@ static int mxuport_probe(struct usb_serial *serial, /* Use the firmware already in the device */ err = 0; } else { + if (fw_p->size <= VER_ADDR_3) { + dev_err(&serial->interface->dev, + "Firmware %s is too short\n", buf); + err = -EINVAL; + goto out; + } + local_ver = ((fw_p->data[VER_ADDR_1] << 16) | (fw_p->data[VER_ADDR_2] << 8) | fw_p->data[VER_ADDR_3]); From 6fcd91ce2a0787cd4bdf6a0b3cd4884566a3cdba Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:48:17 +0800 Subject: [PATCH 27/30] USB: serial: io_ti: reject oversized boot-mode firmware do_boot_mode() copies the firmware payload, excluding its four-byte prefix, into a fixed 15.5 KiB staging buffer. check_fw_sanity() already proves that the image contains its seven-byte header and validates the declared image length and checksum, but it does not impose this boot-mode destination limit. Reject images whose payload does not fit before allocating and filling the staging buffer. Fixes: d12b219a228e ("edgeport-ti: use request_firmware()") Signed-off-by: Pengpeng Hou Signed-off-by: Johan Hovold --- drivers/usb/serial/io_ti.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index 07c0eff3bef4..cc28f5869a3e 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -1464,6 +1464,12 @@ static int do_boot_mode(struct edgeport_serial *serial, /* Allocate a 15.5k buffer + 3 byte header */ buffer_size = (((1024 * 16) - 512) + sizeof(struct ti_i2c_image_header)); + if (fw->size - 4 > buffer_size) { + dev_err(dev, "%s - firmware image is too large\n", + __func__); + return -EINVAL; + } + buffer = kmalloc(buffer_size, GFP_KERNEL); if (!buffer) return -ENOMEM; From 55645e4f3c6022ffb160ad3617d2b624eaa38501 Mon Sep 17 00:00:00 2001 From: Chukun Pan Date: Wed, 8 Jul 2026 18:00:01 +0800 Subject: [PATCH 28/30] USB: serial: option: add TDTECH MT5710-CN Add support for the TDTECH MT5710-CN (5G redcap) module based on the Huawei HiSilicon Balong chip. T: Bus=01 Lev=02 Prnt=02 Port=00 Cnt=01 Dev#= 3 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=3466 ProdID=3301 Rev=ff.ff S: Manufacturer=TD Tech Ltd. S: Product=TDTECH MT571X S: SerialNumber=0123456789ABCDEF C:* #Ifs= 6 Cfg#= 1 Atr=c0 MxPwr= 0mA A: FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=0d Prot=00 I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=0d Prot=00 Driver=cdc_ncm E: Ad=82(I) Atr=03(Int.) MxPS= 16 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm I:* If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=13 Driver=option E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=12 Driver=option E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=1c Driver=option E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=14 Driver=option E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Interface: ECM / NCM + DIAG + AT + SERIAL + GPS Signed-off-by: Chukun Pan Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 7275f4e7f569..580f06f5ce5e 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2496,6 +2496,7 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x1003, 0xff), /* Rolling RW135R-GL (laptop MBIM) */ .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x3466, 0x3301, 0xff) }, /* TDTECH MT5710-CN */ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Global */ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0x00, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x40) }, From faaddd811c5099f11a5f52e68a6b31a5898cda4f Mon Sep 17 00:00:00 2001 From: Sunho Park Date: Tue, 14 Jul 2026 19:42:30 +0900 Subject: [PATCH 29/30] USB: serial: io_edgeport: cap received transmit credits The interrupt-status packet reports transmit credits returned by the device. edge_interrupt_callback() adds the 16-bit value to txCredits without checking maxTxCredits. edge_write() uses txCredits minus the software FIFO count as the amount of data that fits. Since the FIFO is allocated with maxTxCredits bytes, txCredits exceeding maxTxCredits can cause OOB write in ring buffer. Cap accumulated credits at maxTxCredits. Conforming devices should never hit the cap. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Sunho Park Signed-off-by: Johan Hovold --- drivers/usb/serial/io_edgeport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 34ccf7820537..eaa8f00716c8 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -646,7 +646,8 @@ static void edge_interrupt_callback(struct urb *urb) if (edge_port && edge_port->open) { spin_lock_irqsave(&edge_port->ep_lock, flags); - edge_port->txCredits += txCredits; + edge_port->txCredits = min(edge_port->txCredits + txCredits, + edge_port->maxTxCredits); spin_unlock_irqrestore(&edge_port->ep_lock, flags); dev_dbg(dev, "%s - txcredits for port%d = %d\n", From fb0bf289f5d529336ef490c8273e88a8a8b29f69 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Fri, 17 Jul 2026 10:46:14 +0000 Subject: [PATCH 30/30] usb: typec: ucsi: Correct teardown ordering in ucsi_init() error path The commit 7aa7d4bf9d3f ("usb: typec: ucsi: Fix race condition and ordering in port unregistration") consolidated port teardown into the ucsi_unregister_port() helper. However, it introduced an ordering problem in the ucsi_init() error path. Fix this by ensuring ucsi_unregister_port() is called before we unregister their corresponding lockdep keys. Cc: stable@vger.kernel.org Fixes: 7aa7d4bf9d3f ("usb: typec: ucsi: Fix race condition and ordering in port unregistration") Reported-by: "Borah, Chaitanya Kumar" Closes: https://lore.kernel.org/all/22064276-6c56-411a-9f20-6917ceeb865f@intel.com/ Signed-off-by: Andrei Kuchynski Tested-by: Chaitanya Kumar Borah Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260717104614.325250-1-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 1ae4224d2dfc..49f1c53721bb 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -2142,11 +2142,11 @@ static int ucsi_init(struct ucsi *ucsi) return 0; err_unregister: + for (con = connector; con->port; con++) + ucsi_unregister_port(con); for (i = 0; i < ucsi->cap.num_connectors; i++) lockdep_unregister_key(&connector[i].lock_key); - for (con = connector; con->port; con++) - ucsi_unregister_port(con); kfree(connector); err_reset: memset(&ucsi->cap, 0, sizeof(ucsi->cap));