From 90cd2072964052834227c05e8ecba898ce015a05 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Mon, 25 May 2026 09:48:57 +0200 Subject: [PATCH 001/163] usb: typec: ucsi: Move long delayed work on system_dfl_long_wq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the code enqueue work items using {queue|mod}_delayed_work(), using system_long_wq. This workqueue should be used when long works are expected and it is a per-cpu workqueue. The function(s) end up calling __queue_delayed_work(), which set a global timer that could fire anywhere, enqueuing the work where the timer fired. Unbound works could benefit from scheduler task placement, to optimize performance and power consumption. Long work shouldn't stick to a single CPU. Recently, a new unbound workqueue specific for long running work has been added:     c116737e972e ("workqueue: Add system_dfl_long_wq for long unbound works") Since the workqueue work doesn't rely on per-cpu variables, there is no obvious reason that justify the use of a per-cpu workqueue. So change system_long_wq with system_dfl_long_wq so that the work may benefit from scheduler task placement. Signed-off-by: Marco Crivellari Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260525074857.30816-1-marco.crivellari@suse.com 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 92166a3725b1..881b44663c30 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -2040,7 +2040,7 @@ static void ucsi_init_work(struct work_struct *work) return; } - queue_delayed_work(system_long_wq, &ucsi->work, + queue_delayed_work(system_dfl_long_wq, &ucsi->work, UCSI_ROLE_SWITCH_INTERVAL); } } @@ -2164,7 +2164,7 @@ int ucsi_register(struct ucsi *ucsi) UCSI_BCD_GET_MINOR(ucsi->version), UCSI_BCD_GET_SUBMINOR(ucsi->version)); - queue_delayed_work(system_long_wq, &ucsi->work, 0); + queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); ucsi_debugfs_register(ucsi); return 0; From eed73a65ab609b79d53de88cccc34b36dfe753c4 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 11 Jun 2026 22:22:02 +0000 Subject: [PATCH 002/163] usb: typec: ucsi: unregister debugfs entries on teardown ucsi_register() creates per-instance debugfs entries, but ucsi_unregister() keeps them around until ucsi_destroy(). Drivers like ucsi_glink that unregister/register the same UCSI instance across remoteproc restart then try to create an already existing debugfs directory and log: debugfs: 'pmic_glink.ucsi.0' already exists in 'ucsi' Unregister debugfs entries as part of ucsi_unregister(), and clear ucsi->debugfs after freeing it so repeated unregister paths remain safe. Assisted-by: Codex:GPT-5.5 Signed-off-by: Bjorn Andersson Fixes: df0383ffad64 ("usb: typec: ucsi: Add debugfs for ucsi commands") Tested-by: Konrad Dybcio # X1E80100 CRD Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260611-usci-unregister-debugfs-v1-1-f4a518a94f27@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/debugfs.c | 1 + drivers/usb/typec/ucsi/ucsi.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index ff33a5e7c6b0..a124105b6226 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -162,6 +162,7 @@ void ucsi_debugfs_unregister(struct ucsi *ucsi) debugfs_remove_recursive(ucsi->debugfs->dentry); kfree(ucsi->debugfs); + ucsi->debugfs = NULL; } void ucsi_debugfs_init(void) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 881b44663c30..607d662b6cb4 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -2186,6 +2186,8 @@ void ucsi_unregister(struct ucsi *ucsi) cancel_delayed_work_sync(&ucsi->work); cancel_work_sync(&ucsi->resume_work); + ucsi_debugfs_unregister(ucsi); + /* Disable notifications */ ucsi->ops->async_control(ucsi, cmd); From 14618b21ea9d83956d69027cac80936f03c10034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Thu, 25 Jun 2026 19:49:22 -0300 Subject: [PATCH 003/163] usb: rust: Use pin_init::zeroed for usb_device_id initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All types in `bindings` implement `Zeroable` if they can. This enables using `pin_init::zeroed()` for `usb_device_id` initialization instead of relying on `..unsafe { MaybeUninit::zeroed().assume_init() }`. This change improves readability and removes unnecessary unsafe blocks. Link: https://github.com/Rust-for-Linux/linux/issues/1189 Suggested-by: Benno Lossin Signed-off-by: Nicolás Antinori Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260625224927.404258-1-nico.antinori.7@gmail.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/usb.rs | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 7aff0c82d0af..bbf85366d2c9 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -24,11 +24,8 @@ }; use core::{ marker::PhantomData, - mem::{ - offset_of, - MaybeUninit, // - }, - ptr::NonNull, + mem::offset_of, + ptr::NonNull, // }; /// An adapter for the registration of USB drivers. @@ -130,8 +127,7 @@ pub const fn from_id(vendor: u16, product: u16) -> Self { match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, idVendor: vendor, idProduct: product, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -143,8 +139,7 @@ pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16 idProduct: product, bcdDevice_lo: bcd_lo, bcdDevice_hi: bcd_hi, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -155,8 +150,7 @@ pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { bDeviceClass: class, bDeviceSubClass: subclass, bDeviceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -167,8 +161,7 @@ pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self bInterfaceClass: class, bInterfaceSubClass: subclass, bInterfaceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -180,8 +173,7 @@ pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) - idVendor: vendor, idProduct: product, bInterfaceClass: class, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -193,8 +185,7 @@ pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: idVendor: vendor, idProduct: product, bInterfaceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -206,8 +197,7 @@ pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) idVendor: vendor, idProduct: product, bInterfaceNumber: number, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -227,8 +217,7 @@ pub const fn from_device_and_interface_info( bInterfaceClass: class, bInterfaceSubClass: subclass, bInterfaceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } } From 227db98088756740645491fe29f8701c164badf6 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 29 Jun 2026 22:57:29 +0000 Subject: [PATCH 004/163] usb: typec: tcpm: Defensively bound altmode array accesses While svdm_consume_modes() already prevents mode_data.altmodes from exceeding ALTMODE_DISCOVERY_MAX during SVDM discovery, defensively bounding array iteration indices against ALTMODE_DISCOVERY_MAX in altmode registration and unregistration helpers guarantees protection against out-of-bounds accesses in the event of memory corruption. Ensure that tcpm_register_plug_altmodes() is also bounded alongside tcpm_register_partner_altmodes() and tcpm_unregister_altmodes. Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Badhri Jagan Sridharan Reviewed-by: RD Babiera Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260629225729.2749896-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 7ef746a90a17..c9ac9381b17c 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -2029,7 +2029,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) if (!port->partner) return; - for (i = 0; i < modep->altmodes; i++) { + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { altmode = typec_partner_register_altmode(port->partner, &modep->altmode_desc[i]); if (IS_ERR(altmode)) { @@ -2047,9 +2047,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port) struct typec_altmode *altmode; int i; - typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); + typec_plug_set_num_altmodes(port->plug_prime, + min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); - for (i = 0; i < modep->altmodes; i++) { + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { altmode = typec_plug_register_altmode(port->plug_prime, &modep->altmode_desc[i]); if (IS_ERR(altmode)) { @@ -4891,11 +4892,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) struct pd_mode_data *modep_prime = &port->mode_data_prime; int i; - for (i = 0; i < modep->altmodes; i++) { + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { typec_unregister_altmode(port->partner_altmode[i]); port->partner_altmode[i] = NULL; } - for (i = 0; i < modep_prime->altmodes; i++) { + for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { typec_unregister_altmode(port->plug_prime_altmode[i]); port->plug_prime_altmode[i] = NULL; } From d9dc19910321957d81fb9d8af4cc67ddbfb258bf Mon Sep 17 00:00:00 2001 From: Madhu M Date: Tue, 30 Jun 2026 18:54:35 +0530 Subject: [PATCH 005/163] usb: typec: ucsi: Fix debugfs response truncation beyond 16 bytes The current ucsi_data structure inside ucsi_debugfs_entry caps the response payload layout to exactly 16 bytes via low and high 64-bit fields. However, standard UCSI specifications define core data structures that require messages larger than this 16-byte boundary. Without this expansion, vital telemetry metrics cannot be captured. For example, the GET_CONNECTOR_STATUS -> Voltage Reading fields, and the GET_LPM_PPM_INFO -> HW Version fields reside starting at or beyond byte offset 16. Under the current implementation, reading the debugfs 'response' attribute truncates this extra data, rendering these extended operational metrics unreadable. Fix this by expanding the ucsi_data structure with an 'ext' field to provide structural capacity for payloads extending beyond 16 bytes. Update ucsi_resp_show() to print the extended field block directly prepended to the high/low data stream to ensure readability while maintaining structural continuity. Signed-off-by: Madhu M Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260630132435.458563-1-madhu.m@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/debugfs.c | 4 ++-- drivers/usb/typec/ucsi/ucsi.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index a124105b6226..77a0dd75edd3 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) if (ucsi->debugfs->status) return ucsi->debugfs->status; - seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high, - ucsi->debugfs->response.low); + seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, + ucsi->debugfs->response.high, ucsi->debugfs->response.low); return 0; } DEFINE_SHOW_ATTRIBUTE(ucsi_resp); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 325ed1e5ca80..97bb8892e489 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -466,6 +466,7 @@ struct ucsi_debugfs_entry { struct ucsi_data { u64 low; u64 high; + u64 ext; } response; int status; u8 message_out[MESSAGE_OUT_MAX_LEN]; From 1b981fb4827bc6c1ebe92bffff9505593361b5bf Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 12:41:50 +0300 Subject: [PATCH 006/163] usb: host: ohci-dbg: use kmalloc() for print buffer ochi-dbg allocates buffers for formatting of various dump outputs. These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). While on it, drop the NULL checks in debug_close(). buf is never NULL here because all the open handlers return -ENOMEM when alloc_buffer() fails, and kfree() can handle a NULL buf->page. Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260701-b4-usb-v2-1-272807df4b64@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-dbg.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 9e0e06bbc570..23dc9eddc06c 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf) int ret; if (!buf->page) - buf->page = (char *)get_zeroed_page(GFP_KERNEL); + buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf->page) { ret = -ENOMEM; @@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file) { struct debug_buffer *buf = file->private_data; - if (buf) { - if (buf->page) - free_page((unsigned long)buf->page); - kfree(buf); - } + kfree(buf->page); + kfree(buf); return 0; } From 8a1f9d85902d08bfab341d538395b6def692349a Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 12:41:51 +0300 Subject: [PATCH 007/163] usb: core: devices: use kmalloc() to allocate dump buffer usb_device_dump() allocates a buffer for formatting /sys/kernel/debug/usb/devices output text. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_pages() with kmalloc() and free_pages() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260701-b4-usb-v2-2-272807df4b64@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devices.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index a247da73f34d..6f0354aba38b 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -37,6 +37,7 @@ */ #include +#include #include #include #include @@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, return 0; /* allocate 2^1 pages = 8K (on i386); * should be more than enough for one device */ - pages_start = (char *)__get_free_pages(GFP_NOIO, 1); + pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO); if (!pages_start) return -ENOMEM; @@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, if (length > *nbytes) length = *nbytes; if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { - free_pages((unsigned long)pages_start, 1); + kfree(pages_start); return -EFAULT; } *nbytes -= length; @@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, } else *skip_bytes -= length; - free_pages((unsigned long)pages_start, 1); + kfree(pages_start); /* Now look at all of this device's children. */ usb_hub_for_each_child(usbdev, chix, childdev) { From 5e4eb5c96324b226220d29d025a334df28054ed3 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:47:14 +0800 Subject: [PATCH 008/163] usb: typec: mux: tusb1046: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Romain Gantois Link: https://patch.msgid.link/20260704124714.18404-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/tusb1046.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c index 3c1a4551c2fb..89b47aa56c7a 100644 --- a/drivers/usb/typec/mux/tusb1046.c +++ b/drivers/usb/typec/mux/tusb1046.c @@ -179,6 +179,7 @@ static const struct of_device_id tusb1046_match_table[] = { {.compatible = "ti,tusb1046"}, {}, }; +MODULE_DEVICE_TABLE(of, tusb1046_match_table); static struct i2c_driver tusb1046_driver = { .driver = { From 3c26b3fcf3df47c6acf789d91a31d9445d033e83 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 7 Jul 2026 15:51:30 +0100 Subject: [PATCH 009/163] dt-bindings: usb: renesas,usbhs: Document RZ/G3L SoC The USBHS IP block on RZ/G3L SoC is identitcal to the one found on the RZ/G3S device. Document the RZ/G3L USBHS IP block. Signed-off-by: Biju Das Acked-by: Conor Dooley Link: https://patch.msgid.link/20260707145135.247565-2-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/renesas,usbhs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml index dc74e70f1b92..13715b7c94c8 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml @@ -27,6 +27,7 @@ properties: - renesas,usbhs-r9a07g044 # RZ/G2{L,LC} - renesas,usbhs-r9a07g054 # RZ/V2L - renesas,usbhs-r9a08g045 # RZ/G3S + - renesas,usbhs-r9a08g046 # RZ/G3L - renesas,usbhs-r9a09g047 # RZ/G3E - renesas,usbhs-r9a09g056 # RZ/V2N - renesas,usbhs-r9a09g057 # RZ/V2H(P) From a7c0d84c596a7a6d579cbaac866af328da0778b3 Mon Sep 17 00:00:00 2001 From: Ihor Matushchak Date: Fri, 3 Jul 2026 14:01:36 +0200 Subject: [PATCH 010/163] usb: cdns3: plat: fix a typo in cdns3_plat_probe() Fixes typos in dev_err_probe(): 'cdn3,usb*-phy' -> 'cdns3,usb*-phy'. Signed-off-by: Ihor Matushchak Acked-by: Peter Chen Link: https://patch.msgid.link/20260703120136.19852-1-ihor.matushchak@foobox.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-plat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index bb5405460035..e3f32c3e9535 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); if (IS_ERR(cdns->usb2_phy)) return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), - "Failed to get cdn3,usb2-phy\n"); + "Failed to get cdns3,usb2-phy\n"); cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); if (IS_ERR(cdns->usb3_phy)) return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), - "Failed to get cdn3,usb3-phy\n"); + "Failed to get cdns3,usb3-phy\n"); ret = phy_init(cdns->usb2_phy); if (ret) From 62481c92444386d46b3c4a9c3dbcd4917f07d138 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Thu, 2 Jul 2026 20:45:02 +0200 Subject: [PATCH 011/163] ABI: sysfs-bus-usb: fix brace Remove single ')' Signed-off-by: Manuel Ebner Link: https://patch.msgid.link/20260702184500.208211-4-manuelebner@mailbox.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index af9b653422f1..baf4bf6d32ee 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -293,7 +293,7 @@ Description: Supported values are 0 - 15. More information on how besl values map to microseconds can be found in - USB 2.0 ECN Errata for Link Power Management, section 4.10) + USB 2.0 ECN Errata for Link Power Management, section 4.10 What: /sys/bus/usb/devices/.../rx_lanes Date: March 2018 From 83f4f829a5de161a6aa95a63ab4772cbddf4f77a Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Sat, 23 May 2026 12:38:36 +0530 Subject: [PATCH 012/163] usb: dwc3: xilinx: fix missing space before closing comment delimiter Add missing space before '*/' in an inline comment to follow the kernel coding style. Acked-by: Thinh Nguyen Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/8e6f53ad4eef8babb3c72ae9fac5130342760da9.1779518171.git.radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-xilinx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c index 9b9525592a85..02dc35e110b9 100644 --- a/drivers/usb/dwc3/dwc3-xilinx.c +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -194,7 +194,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) } if (priv_data->usb3_phy) { - /* Set PIPE Power Present signal in FPD Power Present Register*/ + /* Set PIPE Power Present signal in FPD Power Present Register */ writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); From cfdc9bde49b7f6530385739672cfc93343e91a42 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Sat, 23 May 2026 12:38:37 +0530 Subject: [PATCH 013/163] usb: dwc3: xilinx: use reset_control_reset() in versal init Replace reset_control_assert()/deassert() with reset_control_reset(). For dwc3-xilinx, reset_control_reset() routes via the zynqmp reset driver and uses PM_RESET_ACTION_PULSE, which performs assert and deassert in firmware. This results in a single SMC call issuing a reset pulse and taking the IP out of reset. Signed-off-by: Radhey Shyam Pandey Acked-by: Thinh Nguyen Link: https://patch.msgid.link/6a5e4e25d84d6abc971f1669739aae4d3146700f.1779518171.git.radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-xilinx.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c index 02dc35e110b9..b832505e1b04 100644 --- a/drivers/usb/dwc3/dwc3-xilinx.c +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) dwc3_xlnx_mask_phy_rst(priv_data, false); - /* Assert and De-assert reset */ - ret = reset_control_assert(crst); - if (ret < 0) { - dev_err_probe(dev, ret, "failed to assert Reset\n"); - return ret; - } - - ret = reset_control_deassert(crst); - if (ret < 0) { - dev_err_probe(dev, ret, "failed to De-assert Reset\n"); - return ret; - } + /* assert and deassert reset */ + ret = reset_control_reset(crst); + if (ret) + return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); dwc3_xlnx_mask_phy_rst(priv_data, true); dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); From fe0f370af9b2d990213d4022ea51c70841d1e564 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Tue, 19 May 2026 23:49:54 +0530 Subject: [PATCH 014/163] usb: typec: tipd: add error message for vendor ID read failure Log an error when the vendor ID read fails or returns zero, including the I2C error code and register value, and initialize the vendor ID variable to avoid logging an uninitialized value on read failure. Signed-off-by: Radhey Shyam Pandey Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/0aa487d3f054b34689e95760fefd72f7571f64c9.1779214249.git.radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d5ee0af9058b..465788f8490a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1744,7 +1744,7 @@ static int tps6598x_probe(struct i2c_client *client) struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; - u32 vid; + u32 vid = 0; int ret; data = i2c_get_match_data(client); @@ -1772,8 +1772,11 @@ static int tps6598x_probe(struct i2c_client *client) if (!device_is_compatible(tps->dev, "ti,tps25750")) { ret = tps6598x_read32(tps, TPS_REG_VID, &vid); - if (ret < 0 || !vid) + if (ret < 0 || !vid) { + dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", + ret, vid); return -ENODEV; + } } /* From c6cd2ebca283e889b2e561bc481c235baedc33d6 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Tue, 19 May 2026 23:49:55 +0530 Subject: [PATCH 015/163] usb: typec: tipd: demote missing IRQ message to debug Operating without an interrupt line and using the driver's polling path is valid. So move the log level to debug instead of warning. Signed-off-by: Radhey Shyam Pandey Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/7d14634eb5f4f7f2e217cd0080e3288eb63fc940.1779214249.git.radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 465788f8490a..b6335b36d384 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1854,7 +1854,7 @@ static int tps6598x_probe(struct i2c_client *client) IRQF_SHARED | IRQF_ONESHOT, dev_name(&client->dev), tps); } else { - dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); + dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL)); From 8c4871a4fa9776cca9185066253955cbd55e358b Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Tue, 19 May 2026 23:49:56 +0530 Subject: [PATCH 016/163] usb: typec: tipd: name TPS_REG_POWER_STATUS field masks Define named masks for Power Status fields (connection and source/sink) and reuse them consistently for both field extraction and value construction. This avoids raw bit usage, keeps the definitions aligned. No functional change. Reviewed-by: Heikki Krogerus Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/fdf373fd9d98ba68c72cfa9e89b4e9bddf06aea8.1779214249.git.radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/tps6598x.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 03edbb77bbd6..d4140f4da5bb 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -142,9 +142,13 @@ #define TPS_SYSTEM_POWER_STATE_S4 0x04 #define TPS_SYSTEM_POWER_STATE_S5 0x05 -/* TPS_REG_POWER_STATUS bits */ -#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x)) -#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x)) +/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ +#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) +#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) +#define TPS_POWER_STATUS_CONNECTION(x) \ + TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) +#define TPS_POWER_STATUS_SOURCESINK(x) \ + TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) #define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) #define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) From c0ae54724ebd989cc7313e9bd13d23118c509197 Mon Sep 17 00:00:00 2001 From: Madhu M Date: Tue, 23 Jun 2026 06:02:15 +0530 Subject: [PATCH 017/163] usb: typec: displayport: Reject DP Alt Mode VDO with no pin assignment for its capability Some docks/Type-C dongles expose a malformed DP Capabilities VDO: they claim a DFP_D (source) or UFP_D (sink) capability but leave the corresponding pin assignment field empty. Such a device can never have Alt Mode configured. Currently the driver still proceeds, which is misleading and offers no diagnostic. Per VESA DPAM v2.1a Section 5.4.1 (Table 5-6): Case 1 (receptacle): A DP Source device receptacle (DFP_D) declares its lane routing in the DP Source Pin field (Bits 15:8); a DP Sink device receptacle (UFP_D) declares its lanes in the DP Sink Pin field (Bits 23:16). Case 2 (direct-attach plug): A DP Sink device plug (UFP_D) declares its lane routing in the DP Source Pin field (Bits 15:8), and a DP Source device plug (DFP_D) declares its lanes in the DP Sink Pin field (Bits 23:16). In either case the field holds the supported pin assignment values (e.g. C/D/E); 00000000b means no pin assignment is supported for that capability. Reject such a DP Alt Mode VDO in dp_altmode_probe(): if the claimed capability has no matching pin assignments, fail probe with -ENODEV, releasing the SOP' plug reference on the error path. Signed-off-by: Madhu M Reviewed-by: Andrei Kuchynski Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260623003215.4077711-1-madhu.m@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 263a89c5f324..2a553cfcf61d 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt) struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); struct fwnode_handle *fwnode; struct dp_altmode *dp; + u32 cap = DP_CAP_CAPABILITY(alt->vdo); /* Port can only be DFP_U. */ if (typec_altmode_get_data_role(alt) != TYPEC_HOST) @@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt) return -ENODEV; } + /* + * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section + * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a + * USB-C receptacle must advertise at least one pin assignment for the + * capability it claims, otherwise Alt Mode can never be configured. + */ + if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || + (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { + typec_altmode_put_plug(plug); + return -ENODEV; + } + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); if (!dp) { typec_altmode_put_plug(plug); From 41d541e3718db01668a4cd29815ee4b3b55f76d2 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Wed, 24 Jun 2026 22:09:08 +0800 Subject: [PATCH 018/163] usb: gadget: r8a66597: avoid double free of ep0_req in probe error path If usb_add_gadget_udc() fails, r8a66597_probe() jumps to err_add_udc and frees ep0_req, then falls through to clean_up2 where ep0_req is freed again when it is non-NULL. Remove the redundant free from err_add_udc and keep the cleanup in clean_up2 so the request is released exactly once. Fixes: 776976a67ae2 ("usb: gadget: r8a66597-udc: cleanup error path") Issue found using a prototype static analysis tool and confirmed by code review. Signed-off-by: Hongyan Xu Signed-off-by: Slavin Liu <220245772@seu.edu.cn> Link: https://patch.msgid.link/20260624140908.1282-1-getshell@seu.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/r8a66597-udc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index e7a5d8553c0e..d190e16d43fc 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1951,7 +1951,6 @@ static int r8a66597_probe(struct platform_device *pdev) return 0; err_add_udc: - r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req); clean_up2: if (r8a66597->pdata->on_chip) clk_disable_unprepare(r8a66597->clk); From 4bc488bf8290dcf7ac221d286995563495c0ddc2 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Fri, 26 Jun 2026 14:27:00 +0000 Subject: [PATCH 019/163] usb: typec: Add helper to check cable altmode support Introduce typec_cable_altmode_unsupported function to evaluate whether an alternate mode is restricted based on the connected cable's properties. Implement validation logic that parses the cable's identity to catch incompatible setups early. Alternate modes are restricted over: - cables lacking an identity header - passive cables with USB 2.0 speed - active cables unless they have corresponding plugs The function returns false if the cable is not registered or the identifier is not set. Suggested-by: Heikki Krogerus Signed-off-by: Andrei Kuchynski Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260626142702.1941182-2-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 71 +++++++++++++++++++++++++++++++++++++++ include/linux/usb/typec.h | 1 + 2 files changed, 72 insertions(+) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 0977581ad1b6..e9f34eb14cef 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) } EXPORT_SYMBOL_GPL(typec_cable_is_active); +enum typec_cable_altmode_support { + CABLE_SUPPORT_UNKNOWN, + CABLE_SUPPORTED, + CABLE_NOT_SUPPORTED, +}; + +static enum typec_cable_altmode_support +typec_cable_check_altmode_support(struct typec_cable *cable, + struct typec_altmode *alt) +{ + struct typec_altmode *plug; + u32 speed; + + /* + * Check if the cable has an e-marker, supports modal operation, and the + * SOP' altmode nodes are created. + */ + plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + if (plug) { + typec_altmode_put_plug(plug); + return CABLE_SUPPORTED; + } + + /* The identity is not specified */ + if (!cable->identity) + return CABLE_SUPPORT_UNKNOWN; + + /* Non-e-marked cable */ + if (!cable->identity->id_header) + return CABLE_NOT_SUPPORTED; + + switch (PD_IDH_PTYPE(cable->identity->id_header)) { + case IDH_PTYPE_PCABLE: + speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); + if (speed == CABLE_USB2_ONLY) + return CABLE_NOT_SUPPORTED; + return CABLE_SUPPORTED; + case IDH_PTYPE_ACABLE: + /* + * Active cables must establish an SOP' communication + * node. Since that check failed at the beginning of + * this function, this active cable does not support + * this specific altmode. + */ + return CABLE_NOT_SUPPORTED; + } + + return CABLE_SUPPORT_UNKNOWN; +} + +/** + * typec_cable_altmode_unsupported - Check if a cable restricts altmode + * @alt: The Alternate Mode to evaluate + * + * Returns true if the connected cable is incapable of handling the altmode. + */ +bool typec_cable_altmode_unsupported(struct typec_altmode *alt) +{ + enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; + struct typec_cable *cable; + + cable = typec_cable_get(typec_altmode2port(alt)); + if (cable) { + support = typec_cable_check_altmode_support(cable, alt); + typec_cable_put(cable); + } + + return support == CABLE_NOT_SUPPORTED; +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); + /** * typec_cable_set_identity - Report result from Discover Identity command * @cable: The cable updated identity values diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index d61ec38216fa..10a783b738ef 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); struct typec_cable *typec_cable_get(struct typec_port *port); void typec_cable_put(struct typec_cable *cable); int typec_cable_is_active(struct typec_cable *cable); +bool typec_cable_altmode_unsupported(struct typec_altmode *alt); struct typec_plug *typec_register_plug(struct typec_cable *cable, struct typec_plug_desc *desc); From 07f0d51a8ea57b0d29c35fd0c81f4f452ff60f22 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Fri, 26 Jun 2026 14:27:01 +0000 Subject: [PATCH 020/163] usb: typec: thunderbolt: Check cable altmode support Update the probe function to utilize the new typec_cable_altmode_unsupported() helper. If the cable doesn't support Thunderbolt altmode, don't initialize altmode_ops and prevent altmode from being activated. Signed-off-by: Andrei Kuchynski Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260626142702.1941182-3-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/thunderbolt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c index 32250b94262a..2eccdddf1b1f 100644 --- a/drivers/usb/typec/altmodes/thunderbolt.c +++ b/drivers/usb/typec/altmodes/thunderbolt.c @@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) alt->desc = "Thunderbolt3"; typec_altmode_set_drvdata(alt, tbt); + + if (typec_cable_altmode_unsupported(alt)) + return 0; + typec_altmode_set_ops(alt, &tbt_altmode_ops); if (!alt->mode_selection && tbt_ready(alt)) { From ef14df36701cca2d272c18450d12de9d141e5beb Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Fri, 26 Jun 2026 14:27:02 +0000 Subject: [PATCH 021/163] usb: typec: displayport: Check cable altmode support Update the probe function to utilize the new typec_cable_altmode_unsupported() helper. If the cable doesn't support DisplayPort altmode, don't initialize altmode_ops and prevent altmode from being activated. A captive cable shouldn't be checked; it may not provide discoverable capability information, but it is inherently designed to support the device's requirements. Signed-off-by: Andrei Kuchynski Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260626142702.1941182-4-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 2a553cfcf61d..51c92dd887ab 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -803,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt) dp->alt = alt; alt->desc = "DisplayPort"; - typec_altmode_set_ops(alt, &dp_altmode_ops); if (plug) { plug->desc = "Displayport"; @@ -824,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt) if (plug) typec_altmode_set_drvdata(plug, dp); + if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) + return 0; + + typec_altmode_set_ops(alt, &dp_altmode_ops); if (!alt->mode_selection) { dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; schedule_work(&dp->work); From 2d835ee7762d6aa266562e69ecf606ac543aa234 Mon Sep 17 00:00:00 2001 From: Varadarajan Narayanan Date: Thu, 4 Jun 2026 14:35:28 +0530 Subject: [PATCH 022/163] dt-bindings: usb: qcom,snps-dwc3: Add ipq5210 to USB DWC3 IPQ5210 includes a Qualcomm DWC3 USB controller supported by the existing binding. Add its compatible string to the schema and include it in the matching conditional constraints. Signed-off-by: Varadarajan Narayanan Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260604090528.735236-1-varadarajan.narayanan@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 8201656b41ed..932d7aea43c5 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -29,6 +29,7 @@ properties: - qcom,glymur-dwc3-mp - qcom,ipq4019-dwc3 - qcom,ipq5018-dwc3 + - qcom,ipq5210-dwc3 - qcom,ipq5332-dwc3 - qcom,ipq5424-dwc3 - qcom,ipq6018-dwc3 @@ -203,6 +204,7 @@ allOf: compatible: contains: enum: + - qcom,ipq5210-dwc3 - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 @@ -497,6 +499,7 @@ allOf: compatible: contains: enum: + - qcom,ipq5210-dwc3 - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 then: From 7c87ef27af79b3f905aed2edf9f60ce2a75d38e3 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Mon, 8 Jun 2026 10:19:53 -0700 Subject: [PATCH 023/163] usb: dwc3: avoid probe deferral when USB power supply is not available The dwc3 driver currently defers probe if the USB power supply is not yet registered. On some platforms, even though charging and power supply functionality is available during normal operation, there may exist minimal booting modes (such as recovery or diagnostic environments) where the relevant USB power supply device is not registered. In such cases, probe deferral prevents USB gadget operation entirely. USB data functionality for basic operation does not inherently depend on the power supply framework, which is only required for enforcing VBUS current control. The configured VBUS current limit is typically enforced through the charger or PMIC power path. When charging functionality is unavailable, applying a current limit has no practical effect, reducing the benefit of strict probe-time enforcement in these environments. Instead of deferring probe, register a power supply notifier when the USB power supply is not yet available. Cache the requested VBUS current limit and apply it once the matching power supply becomes available, as notified through the registered callback. Signed-off-by: Elson Serrao Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260608171953.1717369-1-elson.serrao@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 99 +++++++++++++++++++++++++++++++++------ drivers/usb/dwc3/core.h | 6 +++ drivers/usb/dwc3/gadget.c | 15 +++++- 3 files changed, 104 insertions(+), 16 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 517aa7f1486d..033d5fb3c29e 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) ret, dwc->current_limit); } -static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) +static int dwc3_psy_notifier(struct notifier_block *nb, + unsigned long event, void *data) { - struct power_supply *usb_psy; - const char *usb_psy_name; + struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); + struct power_supply *psy = data; + unsigned long flags; + + if (dwc->usb_psy) + return NOTIFY_DONE; + + if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) + return NOTIFY_DONE; + + /* Explicitly get the reference for this psy */ + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return NOTIFY_DONE; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * The USB power_supply may already be set. This can happen if notifier + * callbacks for the USB power_supply race, or if a previous notifier + * callback has already successfully fetched and associated the instance. + * In such cases, release the newly acquired reference and ignore + * subsequent notifications until the notifier is unregistered. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return NOTIFY_DONE; + } + + dwc->usb_psy = psy; + if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) + schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); + + return NOTIFY_OK; +} + +static void dwc3_get_usb_power_supply(struct dwc3 *dwc) +{ + struct power_supply *psy; + unsigned long flags; int ret; - ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); + ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); if (ret < 0) - return NULL; - - usb_psy = power_supply_get_by_name(usb_psy_name); - if (!usb_psy) - return ERR_PTR(-EPROBE_DEFER); + return; INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); - return usb_psy; + + dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; + dwc->psy_nb.notifier_call = dwc3_psy_notifier; + ret = power_supply_reg_notifier(&dwc->psy_nb); + if (ret) { + dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); + dwc->psy_nb.notifier_call = NULL; + return; + } + + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return; + + /* Unregister the notifier now that we have the power supply */ + power_supply_unreg_notifier(&dwc->psy_nb); + dwc->psy_nb.notifier_call = NULL; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * It is possible that the notifier callback ran before we reached here + * and successfully fetched the power supply. In that case we need to + * release the above reference. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return; + } + + dwc->usb_psy = psy; + spin_unlock_irqrestore(&dwc->lock, flags); } int dwc3_core_probe(const struct dwc3_probe_data *data) @@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc3_get_software_properties(dwc, &data->properties); - dwc->usb_psy = dwc3_get_usb_power_supply(dwc); - if (IS_ERR(dwc->usb_psy)) - return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); + spin_lock_init(&dwc->lock); + + dwc3_get_usb_power_supply(dwc); if (!data->ignore_clocks_and_resets) { dwc->reset = devm_reset_control_array_get_optional_shared(dev); @@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc->num_usb3_ports = 1; } - spin_lock_init(&dwc->lock); mutex_init(&dwc->mutex); pm_runtime_get_noresume(dev); @@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) err_assert_reset: reset_control_assert(dwc->reset); err_put_psy: + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); if (dwc->usb_psy) power_supply_put(dwc->usb_psy); @@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) dwc3_free_event_buffers(dwc); + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) { cancel_work_sync(&dwc->vbus_draw_work); power_supply_put(dwc->usb_psy); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index e0dee9d28740..07e0e1b8e804 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1059,6 +1059,8 @@ struct dwc3_glue_ops { * @role_switch_default_mode: default operation mode of controller while * usb role is USB_ROLE_NONE. * @usb_psy: pointer to power supply interface. + * @usb_psy_name: name of the USB power supply + * @psy_nb: power supply notifier block * @vbus_draw_work: Work to set the vbus drawing limit * @current_limit: How much current to draw from vbus, in milliAmperes. * @usb2_phy: pointer to USB2 PHY @@ -1251,9 +1253,13 @@ struct dwc3 { enum usb_dr_mode role_switch_default_mode; struct power_supply *usb_psy; + const char *usb_psy_name; + struct notifier_block psy_nb; struct work_struct vbus_draw_work; unsigned int current_limit; +#define DWC3_CURRENT_UNSPECIFIED UINT_MAX + u32 fladj; u32 ref_clk_per; u32 irq_gadget; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 3d4ca68e584c..c36d2a949231 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3124,15 +3124,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) { struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; if (dwc->usb2_phy) return usb_phy_set_power(dwc->usb2_phy, mA); - if (!dwc->usb_psy) - return -EOPNOTSUPP; + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc->usb_psy) { + if (!dwc->psy_nb.notifier_call) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -EOPNOTSUPP; + } + dwc->current_limit = mA; + spin_unlock_irqrestore(&dwc->lock, flags); + dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); + return 0; + } dwc->current_limit = mA; schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); return 0; } From f23526d9cd97a0a60af252bbda3912035c21d7c3 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 23 Jun 2026 11:00:00 +0100 Subject: [PATCH 024/163] usb: dwc3: qcom: make dwc3_qcom_glue_ops static The dwc3_qcom_glue_ops is not used outside of the file it is declared in , so make it static to avoid the following warning: drivers/usb/dwc3/dwc3-qcom.c:605:22: warning: symbol 'dwc3_qcom_glue_ops' was not declared. Should it be static? Signed-off-by: Ben Dooks Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260623100000.718126-1-ben.dooks@codethink.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index f43f73ac36ff..ac68b4218b56 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) pm_runtime_mark_last_busy(qcom->dev); } -struct dwc3_glue_ops dwc3_qcom_glue_ops = { +static struct dwc3_glue_ops dwc3_qcom_glue_ops = { .pre_set_role = dwc3_qcom_set_role_notifier, .pre_run_stop = dwc3_qcom_run_stop_notifier, }; From 7a2c88461db1c4519505093efca322f7764811dc Mon Sep 17 00:00:00 2001 From: Jakov Novak Date: Wed, 24 Jun 2026 13:46:15 +0200 Subject: [PATCH 025/163] usb: dwc3: fix kernel-doc string in struct dwc3_ep Building the documentation currently with make htmldocs gives the following errors: WARNING: ./drivers/usb/dwc3/core.h:803 Excess struct member 'regs' description in 'dwc3_ep' WARNING: ./drivers/usb/dwc3/core.h:803 Excess struct member 'regs' description in 'dwc3_ep' This is because the member variable regs of the struct dwc3_ep was removed in a previous patch. Remove regs from the kernel-doc string, fixing the warning. Fixes: abdd1eef04f0cb3b ("usb: dwc3: Remove of dep->regs") Signed-off-by: Jakov Novak Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260624114615.18593-1-jakovnovak30@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 07e0e1b8e804..608daeb7ef10 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -722,7 +722,6 @@ struct dwc3_event_buffer { * @cancelled_list: list of cancelled requests for this endpoint * @pending_list: list of pending requests for this endpoint * @started_list: list of started requests on this endpoint - * @regs: pointer to first endpoint register * @trb_pool: array of transaction buffers * @trb_pool_dma: dma address of @trb_pool * @trb_enqueue: enqueue 'pointer' into TRB array From 0f1dabb7af982dae9325a005297785a792b51223 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 13:56:12 +0800 Subject: [PATCH 026/163] usb: dwc3: am62: Propagate USB2 refclk enable failures The AM62 wrapper requires the USB2 ref clock, but dwc3_ti_init() ignores clk_prepare_enable() failures before marking the mode valid. Probe can then populate the child DWC3 device even though the wrapper clock transition failed. Resume has the same issue after context loss or direct refclk re-enable. Check and propagate the refclk enable errors so the wrapper does not publish or resume a child provider without parent readiness. Signed-off-by: Pengpeng Hou Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260624055612.43319-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-am62.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index e11d7643f966..632634d6e81e 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62) dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); - clk_prepare_enable(am62->usb2_refclk); + ret = clk_prepare_enable(am62->usb2_refclk); + if (ret) + return ret; /* Set mode valid bit to indicate role is valid */ reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); @@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev) { struct dwc3_am62 *am62 = dev_get_drvdata(dev); u32 reg; + int ret; reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); if (reg != USBSS_DEBUG_CFG_DISABLED) { /* lost power/context */ - dwc3_ti_init(am62); + ret = dwc3_ti_init(am62); + if (ret) + return ret; } else { dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); - clk_prepare_enable(am62->usb2_refclk); + ret = clk_prepare_enable(am62->usb2_refclk); + if (ret) + return ret; } if (device_may_wakeup(dev)) { From 0cde051e5465632f77b114b4b8f172df166453d1 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 13 Apr 2026 20:17:25 +0800 Subject: [PATCH 027/163] dt-bindings: usb: mtu3: add support mt8196 There are three USB controllers on mt8196, each controller's wakeup control is different, add some specific versions for them, and add compatilbe for mt8196. Acked-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chunfeng Yun Link: https://patch.msgid.link/20260413121727.4702-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml index 21fc6bbe954f..d148e938d647 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml @@ -28,6 +28,7 @@ properties: - mediatek,mt8188-mtu3 - mediatek,mt8192-mtu3 - mediatek,mt8195-mtu3 + - mediatek,mt8196-mtu3 - mediatek,mt8365-mtu3 - const: mediatek,mtu3 @@ -200,7 +201,10 @@ properties: 103 - used by mt8195, IP0, specific 1.03; 105 - used by mt8195, IP2, specific 1.05; 106 - used by mt8195, IP3, specific 1.06; - enum: [1, 2, 101, 102, 103, 105, 106] + 107 - used by mt8196, IP0, specific 1.07; + 108 - used by mt8196, IP1, specific 1.08; + 109 - used by mt8196, IP2, specific 1.09; + enum: [1, 2, 101, 102, 103, 105, 106, 107, 108, 109] mediatek,u3p-dis-msk: $ref: /schemas/types.yaml#/definitions/uint32 From b106bebc8dae8dd91b9578bc035c151a8ee8a651 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 13 Apr 2026 20:17:26 +0800 Subject: [PATCH 028/163] usb: mtu3: add support remote wakeup of mt8196 There are three USB controllers on mt8196, each controller's wakeup control is different, add some specific versions for them. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chunfeng Yun Link: https://patch.msgid.link/20260413121727.4702-2-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_host.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 7c657ea2dabd..8138b3f3096a 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -46,6 +46,14 @@ #define WC1_IS_P_95 BIT(12) #define WC1_IS_EN_P0_95 BIT(6) +/* mt8196 */ +#define PERI_WK_CTRL0_8196 0x08 +#define WC0_IS_EN_P0_96 BIT(0) +#define WC0_IS_EN_P1_96 BIT(7) + +#define PERI_WK_CTRL1_8196 0x10 +#define WC1_IS_EN_P2_96 BIT(0) + /* mt2712 etc */ #define PERI_SSUSB_SPM_CTRL 0x0 #define SSC_IP_SLEEP_EN BIT(4) @@ -59,6 +67,9 @@ enum ssusb_uwk_vers { SSUSB_UWK_V1_3, /* mt8195 IP0 */ SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ SSUSB_UWK_V1_6, /* mt8195 IP3 */ + SSUSB_UWK_V1_7, /* mt8196 IP0 */ + SSUSB_UWK_V1_8, /* mt8196 IP1 */ + SSUSB_UWK_V1_9, /* mt8196 IP2 */ }; /* @@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; break; + case SSUSB_UWK_V1_7: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; + msk = WC0_IS_EN_P0_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_8: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; + msk = WC0_IS_EN_P1_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_9: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; + msk = WC1_IS_EN_P2_96; + val = enable ? msk : 0; + break; case SSUSB_UWK_V2: reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; From 38ae123357127c1673ebd221ff58b66d7a6987e3 Mon Sep 17 00:00:00 2001 From: Kefan Bai Date: Mon, 1 Jun 2026 16:26:52 +0800 Subject: [PATCH 029/163] docs/zh_CN: usb: refine translated wording and formatting Refine the zh_CN USB translations for clarity and consistency. Improve wording, wrapping, and formatting across the translated USB documents. Link: https://lore.kernel.org/r/2026053149-flaky-shallow-2460@gregkh Suggested-by: Alex Shi Signed-off-by: Kefan Bai Link: https://patch.msgid.link/20260601082652.650303-1-baikefan@leap-io-kernel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/translations/zh_CN/usb/CREDITS | 147 ++++---- Documentation/translations/zh_CN/usb/acm.rst | 62 ++-- .../translations/zh_CN/usb/authorization.rst | 79 ++--- .../translations/zh_CN/usb/chipidea.rst | 63 ++-- Documentation/translations/zh_CN/usb/dwc3.rst | 37 +- Documentation/translations/zh_CN/usb/ehci.rst | 249 ++++++------- .../translations/zh_CN/usb/index.rst | 18 +- .../translations/zh_CN/usb/usbmon.rst | 332 ++++++++---------- 8 files changed, 435 insertions(+), 552 deletions(-) diff --git a/Documentation/translations/zh_CN/usb/CREDITS b/Documentation/translations/zh_CN/usb/CREDITS index c133b1a5daff..ccfeced03ea6 100644 --- a/Documentation/translations/zh_CN/usb/CREDITS +++ b/Documentation/translations/zh_CN/usb/CREDITS @@ -10,12 +10,10 @@ :校译: -简易 Linux USB 驱动的致谢名单: +Simple Linux USB 驱动项目致谢名单: -以下人员都为 Linux USB 驱动代码作出了贡献 -(按姓氏字母顺序排列)。我相信这份名单本应 -更长一些,但确实不容易维护。 -如需将自己加入名单,请提交补丁。 +以下人员都为 Linux USB 驱动代码作出过贡献(按姓氏字母顺序排列)。这份名 +单本该更长,只是确实不易维护;如果你也应列名其中,欢迎提交补丁把自己加上。 Georg Acher David Brownell @@ -41,123 +39,124 @@ 特别感谢: Inaky Perez Gonzalez - 感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd - 驱动中的大部分代码。我们从那项工作中学到了很多。 + 感谢他牵头开发 Linux USB 驱动,并编写了 uusbd 驱动的大部分代码,我们 + 从中学到了很多。 NetBSD 和 FreeBSD 的 USB 开发者们 感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。 -附加感谢: - 还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助 - (摘自 Inaky 驱动原始的 THANKS 文件): +另外还要感谢: - 以下公司曾帮助我们开发 Linux USB / UUSBD: + 以下公司和个人在硬件、支持、时间和开发工作上给予了帮助(摘自 Inaky + 驱动原始的 THANKS 文件): - - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面为我 - 提供支持。没想到能得到这么大的帮助。 + 以下公司曾为 Linux USB / UUSBD 的开发提供帮助: - - USAR Systems 向我们提供了他们出色的 USB 评估套件, - 使我们能够测试 Linux USB 驱动对最新 USB 规范的符合性。 - USAR Systems 认识到保持开放操作系统与时俱进的重要性, - 并以硬件支持这个项目。感谢! + - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面提供 + 了大力支持。 + + - USAR Systems 向我们提供了出色的 USB 评估套件,使我们得以测试 + Linux USB 驱动对最新 USB 规范的符合性。USAR Systems 也认识到, + 让开放操作系统跟上时代很重要,因此以硬件支持了这个项目,在此 + 致谢。 - 感谢英特尔提供的宝贵帮助。 - 我们与 Cherry 合作,使 Linux 成为首个内置 USB 支持的操作系统。 Cherry 是全球最大的键盘制造商之一。 - - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI-to-USB - 控制卡,用于测试 OHCI 实现。 + - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI 转 USB 控制卡, + 用于测试 OHCI 实现。 - - 由于他们对我们的支持,Keytronic 可以放心, - 他们的键盘能卖给至少 300 万 Linux 用户中的一部分。 + - 有了他们的支持,Keytronic 可以确信,其键盘能够销售给至少 300 万 + Linux 用户中的一部分。 - - ing büro h doran [http://www.ibhdoran.com]! - 在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的 - (我自己做的那个相当糟糕 :))。现在我知道该去哪里买漂亮的 USB - 配件了! + - 特别感谢 ing büro h doran [http://www.ibhdoran.com]。 + 在欧洲,想给主板配一个 PC 背板 USB 连接器几乎是不可能的(我自己 + 做的那个效果并不好)。现在我知道该去哪里购买合适的 USB 配件了。 - Genius Germany 捐赠了一只 USB 鼠标,用于测试鼠标启动协议; - 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢! + 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro,在此致谢。 - - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB 驱动。 - AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux 在内的 - 所有操作系统平台开放。 + - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB + 驱动。AVM 是主动式和被动式 ISDN 控制器及基于 CAPI 2.0 软件的领先 + 制造商。AVM B1 的主动式设计对包括 Linux 在内的所有操作系统平台 + 开放。 - - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱, - 使我们能够测试批量传输代码。 + - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,使我们能够测试 + 批量传输代码。 - 感谢 Logitech 捐赠了一只三轴 USB 鼠标。 - Logitech 负责设计、制造并销售各种人机接口设备, - 在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的 - 控制设备方面拥有悠久历史和丰富经验。 + Logitech 负责设计、制造并销售各种人机接口设备,在键盘、鼠标、轨迹球、 + 摄像头、扬声器,以及面向游戏和专业用途的控制设备方面拥有悠久历史和 + 丰富经验。 - 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、 - 摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户 - 能在自己喜欢的操作系统上获得支持,并让所有 Linux 用户都能使用 - Logitech 以及其他 USB 硬件。 + 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、摇杆和 + 扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户能在自己偏爱的 + 操作系统上获得支持,并让所有 Linux 用户都能使用 Logitech 及其他 + USB 硬件。 Logitech 也是 1999 年 2 月 11 日维也纳 Linux 大会的官方赞助商, 我们将在会上展示 Linux USB 工作的最新进展。 - - 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI 内部实现中 - 那些不为人知的角落。 + - 感谢 CATC 提供 USB Inspector,帮助我们看到 UHCI 内部实现中的那些 + 隐秘角落。 - 感谢 Entrega 为开发工作提供 PCI 转 USB 卡、集线器和转换器产品。 - - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档, - 让这个驱动得以写成。 + - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,让 + 这个驱动得以写成。 - - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明, - 以及驱动开发过程中的宝贵建议。 + - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,以及驱动 + 开发过程中的宝贵建议。 - 另外还要感谢以下个人(嘿,顺序不分先后 :)) + 另外还要感谢以下个人(排名不分先后): - - Oren Tirosh , - 他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。 + - Oren Tirosh + 他非常耐心地解答我反复提出的各种 USB 问题,并提供了许多有价值的 + 想法。 - - Jochen Karrer , - 指出了致命 bug,并给出了宝贵建议。 + - Jochen Karrer + 指出了严重问题,并给出了宝贵建议。 - - Edmund Humemberger ,他在公共关系与项目管理方面 - 为 Linux-USB 项目付出了巨大的努力。 + - Edmund Humemberger ,他在公共关系与项目管理方面为 + Linux-USB 项目付出了巨大的努力。 - - Alberto Menegazzi 正在着手编写 UUSBD 文档,加油! + - Alberto Menegazzi 正在着手编写 UUSBD 文档。 - - Ric Klaren 编写了很好的入门文档, - 与 Alberto 的作品形成良性竞争:)。 + - Ric Klaren 编写了很好的入门文档,与 + Alberto 的作品形成了良性互补。 - - Christian Groessler ,感谢他在那些棘手细节上的帮助。 + - Christian Groessler ,感谢他在诸多复杂细节上的帮助。 - - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持, - 并提供了大量的改进意见。 + - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,并提供了 + 大量的改进意见。 - - Fernando Herrera - 负责撰写、维护并不断补充那份期待已久、独一无二又精彩的 - UUSBD FAQ!太棒了! + - Fernando Herrera 负责撰写、维护并 + 持续补充那份期待已久、内容翔实的 UUSBD FAQ。 - - Rasca Gmelch 重新启用了 raw 驱动, - 指出了一些错误,并启动了 uusbd-utils 软件包。 + - Rasca Gmelch 重新启用了 raw 驱动,指出了一些错误,并 + 启动了 uusbd-utils 软件包。 - - Peter Dettori ,像疯了一样挖掘 bug, - 还提出了很多很酷的建议,太棒了! + - Peter Dettori ,持续发现问题,并提出了许多 + 有价值的建议。 - - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、 - MIT X 联盟、TeX 社区等等,谢谢你们! + - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、MIT X 联盟、 + TeX 社区等,在此一并致谢。 - - 特别感谢 Richard Stallman 创造了 Emacs! + - 特别感谢 Richard Stallman 创造了 Emacs。 - - 感谢 linux-usb 邮件列表的所有成员,读了那么多邮件——不开玩笑了, - 感谢你们提出的所有建议! + - 感谢 linux-usb 邮件列表的所有成员阅读了大量邮件,并提出了诸多 + 建议。 - 感谢 USB Implementers Forum 成员们的帮助与支持。 - - Nathan Myers ,感谢他的建议! - (希望你喜欢 Cibeles 的派对。) + - Nathan Myers ,感谢他的建议。(也希望你喜欢 + Cibeles 的派对。) - - 感谢 Linus Torvalds 创建、开发并管理 Linux。 + - 感谢 Linus Torvalds 创立、开发并维护 Linux。 - Mike Smith、Craig Keithley、Thierry Giron 和 Janet Schank - 感谢他们让我认识到标准 USB 集线器其实也没那么“标准”, - 这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。 + 感谢他们让我认识到,标准 USB 集线器其实一点也不“标准”;也正因 + 如此,我们才能在标准集线器驱动中加入厂商特定的处理。 diff --git a/Documentation/translations/zh_CN/usb/acm.rst b/Documentation/translations/zh_CN/usb/acm.rst index 51d6eb8f5660..b2e35787af45 100644 --- a/Documentation/translations/zh_CN/usb/acm.rst +++ b/Documentation/translations/zh_CN/usb/acm.rst @@ -20,33 +20,26 @@ Linux ACM 驱动 v0.16 0. 免责声明 ~~~~~~~~~~~ -本程序是自由软件;你可以在自由软件基金会发布的 -GNU 通用公共许可证第 2 版,或者(按你的选择) -任何后续版本的条款下重新发布和/或修改它。 +本程序是自由软件;你可以在自由软件基金会发布的 GNU 通用公共许可证第 2 版, +或者(按你的选择)任何后续版本的条款下重新发布和/或修改它。 -发布本程序是希望它能发挥作用,但它不附带任何担保; -甚至不包括对适销性或特定用途适用性的默示担保。 -详情见 GNU 通用公共许可证。 +发布本程序是希望它能发挥作用,但它不附带任何担保;甚至不包括对适销性或 +特定用途适用性的默示担保。详情见 GNU 通用公共许可证。 -你应该已经随本程序收到了 GNU 通用公共许可证的副本; -如果没有,请致信:Free Software Foundation, Inc., 59 -Temple Place, Suite 330, Boston, MA 02111-1307 USA。 +你应该已经随本程序收到了 GNU 通用公共许可证的副本;如果没有,请参见 +COPYING 文件。 -如需联系作者,可发送电子邮件至 vojtech@suse.cz, -或邮寄至: -Vojtech Pavlik, Ucitelska 1576, Prague 8, -182 00, Czech Republic。 +如需联系作者,可发送电子邮件至 vojtech@suse.cz,或邮寄至: +Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00, Czech Republic。 -为方便起见,软件包中已附带 GNU 通用公共许可证 -第 2 版:见 COPYING 文件。 +为方便起见,软件包中已附带 GNU 通用公共许可证第 2 版:见 COPYING 文件。 1. 使用方法 ~~~~~~~~~~~ -``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB -通信设备类抽象控制模型(USB CDC ACM)规范的 -USB 调制解调器和 USB ISDN 终端适配器。 +``drivers/usb/class/cdc-acm.c`` 驱动适用于符合 USB 通信设备类抽象控制模型 +(USB CDC ACM)规范的 USB 调制解调器和 USB ISDN 终端适配器。 -许多调制解调器支持此驱动,以下是我所知道的一些型号: +已知支持该驱动的调制解调器包括: - 3Com OfficeConnect 56k - 3Com Voice FaxModem Pro @@ -56,17 +49,16 @@ USB 调制解调器和 USB ISDN 终端适配器。 - Compaq 56k FaxModem - ELSA Microlink 56k -我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用: +已知有一款 ISDN 终端适配器可以配合 ACM 驱动使用: - 3Com USR ISDN Pro TA -一些手机也可以通过 USB 连接。 -我知道以下机型可以正常工作: +一些手机也可以通过 USB 连接,已知可用的机型有: - SonyEricsson K800i -遗憾的是,许多调制解调器和大多数 ISDN TA -都使用专有接口,因此无法与此驱动配合工作。 +遗憾的是,很多调制解调器和大多数 ISDN TA 都使用专有接口,因此无法配合该 +驱动工作。 购买前请先确认设备是否符合 ACM 规范。 要使用这些调制解调器,需要加载以下模块:: @@ -75,15 +67,13 @@ USB 调制解调器和 USB ISDN 终端适配器。 uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko cdc-acm.ko -之后就应该可以访问这些调制解调器了。 -应当可以使用 ``minicom``、``ppp`` 和 ``mgetty`` -与它们通信。 +之后就应该能访问这些调制解调器,并用 ``minicom``、``ppp`` 和 +``mgetty`` 与它们通信。 2. 验证驱动是否正常工作 ~~~~~~~~~~~~~~~~~~~~~~~ -第一步是检查 ``/sys/kernel/debug/usb/devices``, -其内容应该类似如下:: +第一步是查看 ``/sys/kernel/debug/usb/devices``,其内容应当类似下面这样:: T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2 B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0 @@ -112,11 +102,10 @@ USB 调制解调器和 USB ISDN 终端适配器。 E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms -这三行的存在很关键(以及 ``Cls=`` 字段里出现的 -``comm`` 和 ``data`` 类);它说明这是一个 ACM -设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。 -如果只看到 ``Cls=ff(vend.)``,那就无能为力了: -这说明你手上的设备使用的是厂商专有接口:: +关键是看这三行,再结合 ``Cls=`` 字段里出现的 ``comm`` 和 ``data`` 类,就 +能判断这是一台 ACM 设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。如果 +只看到 ``Cls=ff(vend.)``,那就说明这台设备使用的是厂商专有接口,ACM 驱动 +无法处理:: D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2 I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm @@ -142,6 +131,5 @@ USB 调制解调器和 USB ISDN 终端适配器。 usb.c: acm driver claimed interface c7b5f3f8 usb.c: acm driver claimed interface c7691fa0 -如果以上都正常,请启动 ``minicom``, -把它配置为连接 ``ttyACM`` 设备,然后 -尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。 +如果这些都正常,请启动 ``minicom``,把它配置为连接到 ``ttyACM`` 设备,然后 +尝试输入 ``at``。如果返回 ``OK``,说明驱动工作正常。 diff --git a/Documentation/translations/zh_CN/usb/authorization.rst b/Documentation/translations/zh_CN/usb/authorization.rst index 2aa311f6b967..e2ff2282bd03 100644 --- a/Documentation/translations/zh_CN/usb/authorization.rst +++ b/Documentation/translations/zh_CN/usb/authorization.rst @@ -10,34 +10,32 @@ :校译: -============================= -授权或禁止 USB 设备连接到系统 -============================= +=========================== +允许或禁止 USB 设备接入系统 +=========================== 版权 (C) 2007 Inaky Perez-Gonzalez 英特尔公司 -此功能允许你控制 USB 设备是否可以在系统中使用。 -借助它,你可以完全通过用户空间实现对 USB 设备的锁定。 +有了这项功能,你就可以控制 USB 设备是否允许在系统中使用,并把 USB 设备锁 +定机制完全放在用户空间实现。 -目前,当插入一个 USB 设备时,系统会对其进行配置, -其接口会立即向用户开放。 -有了这项改动,只有在 root 授权设备完成配置后, -设备才可被使用。 +目前,USB 设备一接入系统就会被立即配置,其接口也会立刻向用户开放。引入 +这项机制后,只有在 root 明确授权后,设备才会完成配置并允许使用。 用法 ==== -授权设备接入:: +授权设备接入系统:: $ echo 1 > /sys/bus/usb/devices/DEVICE/authorized -取消对设备的授权:: +取消设备授权:: $ echo 0 > /sys/bus/usb/devices/DEVICE/authorized -将新连接到 ``hostX`` 的设备默认设为未授权(即锁定):: +将连接到 ``hostX`` 的新设备默认设为未授权(即锁定):: $ echo 0 > /sys/bus/usb/devices/usbX/authorized_default @@ -45,15 +43,14 @@ $ echo 1 > /sys/bus/usb/devices/usbX/authorized_default -默认情况下,所有 USB 设备都是授权的。 -向 ``authorized_default`` 属性写入 ``2`` 会使内核 -默认只授权连接到内部 USB 端口的设备。 +默认情况下,所有 USB 设备都是授权的。向 ``authorized_default`` 属性写入 +``2`` 会使内核默认只授权连接到内部 USB 端口的设备。 -系统锁定示例(比较粗糙) ------------------------- +系统锁定示例(简化版) +---------------------- -假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入 -(例如某台带有外露 USB 端口的自助服务终端):: +假设你想做一个锁定机制,只允许 XYZ 类型的设备接入(例如一台带有外露 USB +端口的自助终端):: 启动系统 rc.local -> @@ -63,21 +60,18 @@ echo 0 > $host/authorized_default done -给 udev 挂一个脚本,用于处理新插入的 USB 设备:: +为 udev 配置一个脚本,用于处理新插入的 USB 设备:: if device_is_my_type $DEV then echo 1 > $device_path/authorized - done + fi -``device_is_my_type()`` 才是锁定方案真正见功夫的 -地方。仅仅检查 class、type 和 protocol 是否匹配 -某个值,是你能做出的最糟糕的安全验证之一; -对想绕过它的人来说,这反而是最容易利用的方案。 -如果你需要真正安全的办法,那就该使用加密、 -证书认证之类的机制。把 USB 存储设备当作 -“钥匙”的一个简单例子可以是:: +锁定方案是否可靠,关键全在 ``device_is_my_type()`` 的实现。仅仅检查 +class、type 和 protocol 是否匹配,几乎是最差的一种安全校验方式;对想绕过 +它的人来说,这种做法反而最容易伪造。如果你真要做安全控制,就该使用加密、 +证书认证之类的机制。把 USB 存储设备当作“钥匙”的一个简单示例可以写成:: function device_is_my_type() { @@ -87,7 +81,7 @@ sum=$(md5sum /mntpoint/.signature) if [ $sum = $(cat /etc/lockdown/keysum) ] then - echo "We are good, connected" + echo "验证通过,已连接" umount /mntpoint # 再做一些额外处理,让其他人也能使用它 else @@ -96,17 +90,16 @@ } -当然,这个例子很粗糙;真正要做的话, -你会想用基于 PKI 的证书校验,这样就不必依赖 -共享密钥之类的东西。不过你应该已经明白意思了。 -任何拿到设备仿真工具包的人都能伪造描述符和设备信息。 -别信这个。 +当然,这个例子仍然比较简化。真正落地时,更合适的做法是使用基于 PKI 的证 +书校验,这样就不必依赖共享密钥之类的机制了。不过意思已经很清楚:任何拿到 +设备仿真工具包的人,都能伪造描述符和设备信息,所以别把这类检查当成真正 +的安全保障。 接口授权 -------- -也有类似的方法用于允许或拒绝特定 USB 接口。 -这使得你可以只阻止某个 USB 设备中的部分接口。 +也可以用类似的方法允许或拒绝特定的 USB 接口。这样一来,你只需要阻止某个 +USB 设备中的部分接口。 授权接口:: @@ -126,14 +119,12 @@ $ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default -默认情况下, -``interface_authorized_default`` 位为 ``1``, -因此所有接口默认都处于已授权状态。 +默认情况下,``interface_authorized_default`` 位为 ``1``,因此所有接口默认 +都会处于授权状态。 注意: - 如果把一个先前未授权的接口改为已授权, - 则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe`` - 来手动触发驱动探测。 + 如果把一个先前未授权的接口改为已授权,则必须通过将 ``INTERFACE`` 写入 + ``/sys/bus/usb/drivers_probe`` 来手动触发驱动探测。 -对于需要多个接口的驱动程序,应先授权所有必需接口, -然后再触发驱动探测。这样做可以避免副作用。 +对于需要多个接口的驱动程序,应先授权所有必需接口,然后再触发驱动探测。 +这样做可以避免副作用。 diff --git a/Documentation/translations/zh_CN/usb/chipidea.rst b/Documentation/translations/zh_CN/usb/chipidea.rst index ea0dc3043189..ee5407a4ce44 100644 --- a/Documentation/translations/zh_CN/usb/chipidea.rst +++ b/Documentation/translations/zh_CN/usb/chipidea.rst @@ -17,18 +17,17 @@ ChipIdea 高速双角色控制器驱动 1. 如何测试 OTG FSM(HNP 和 SRP) --------------------------------- -下面以两块 Freescale i.MX6Q Sabre SD 开发板为例, -说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。 +下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,演示如何通过 sysfs 属性 +测试 OTG 的 HNP 和 SRP 功能。 -1.1 如何使能 OTG FSM +1.1 如何启用 OTG FSM -------------------- 1.1.1 在 ``menuconfig`` 中选择 ``CONFIG_USB_OTG_FSM``,并重新编译内核 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -重新构建内核镜像和模块。如果想查看 OTG FSM 的 -一些内部变量,可以挂载 ``debugfs``;其中有两个文件 -可以显示 OTG FSM 变量以及部分控制器寄存器的值:: +重新构建内核镜像和模块。如果想查看 OTG FSM 的内部变量,可以挂载 +``debugfs``;其中有两个文件,分别显示 OTG FSM 的变量和部分控制器寄存器值:: cat /sys/kernel/debug/ci_hdrc.0/otg cat /sys/kernel/debug/ci_hdrc.0/registers @@ -44,11 +43,10 @@ ChipIdea 高速双角色控制器驱动 1.2 测试步骤 ------------ -1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电, - 并加载 gadget 类驱动(例如 ``g_mass_storage``)。 +1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,并加载 gadget 类驱动(例如 + ``g_mass_storage``)。 -2) 用 USB 线连接两块开发板: - 一端是 micro A 插头,另一端是 micro B 插头。 +2) 用 USB 线连接两块开发板:一端是 micro A 插头,另一端是 micro B 插头。 插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。 @@ -66,32 +64,28 @@ ChipIdea 高速双角色控制器驱动 echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req - 或者,通过引入 HNP 轮询,B 端主机可以知道 - A 端外设希望切换为主机角色,因此这次角色切换 - 也可以通过 A 端外设响应 B 端主机的轮询, - 在 A 侧触发。 - 这可以通过在 A 设备上执行下面的命令来完成:: + 或者,也可以借助 HNP 轮询,让 B 端主机知道 A 端外设希望切回主机角色。 + 因此,这次切换也可以由 A 侧触发,也就是由 A 端外设响应 B 端主机的轮询 + 来完成。可在 A 设备上执行下面的命令:: echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req A 设备应切回主机角色并枚举 B 设备。 -5) 拔掉 B 设备(拔掉 micro B 插头), - 并在 10 秒内重新插入; +5) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒内重新插入。 A 设备应重新枚举 B 设备。 -6) 拔掉 B 设备(拔掉 micro B 插头), - 并在 10 秒后重新插入; +6) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒后重新插入。 A 设备不应重新枚举 B 设备。 - 如果 A 设备希望使用总线: + 如果 A 设备还想继续使用总线: 在 A 设备上执行:: echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req - 如果 B 设备希望使用总线: + 如果 B 设备想使用总线: 在 B 设备上执行:: @@ -111,40 +105,41 @@ ChipIdea 高速双角色控制器驱动 echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req - A 设备应恢复 USB 总线并枚举 B 设备。 + A 设备应恢复 USB 总线,并枚举 B 设备。 1.3 参考文档 ------------ -《On-The-Go and Embedded Host Supplement -to the USB Revision 2.0 Specification +《On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification July 27, 2012 Revision 2.0 version 1.1a》 2. 如何将 USB 用作系统唤醒源 ---------------------------- -下面是在 i.MX6 平台上把 USB 用作系统唤醒源的示例。 +下面给出在 i.MX6 平台上将 USB 用作系统唤醒源的示例。 -2.1 使能核心控制器的唤醒功能:: +2.1 启用核心控制器的唤醒功能:: echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup -2.2 使能 glue 层的唤醒功能:: +2.2 启用 glue 层的唤醒功能:: echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup -2.3 使能 PHY 的唤醒功能(可选):: +2.3 启用 PHY 的唤醒功能(可选):: echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup -2.4 使能根集线器的唤醒功能:: +2.4 启用根集线器的唤醒功能:: echo enabled > /sys/bus/usb/devices/usb1/power/wakeup -2.5 使能相关设备的唤醒功能:: +2.5 启用相关设备的唤醒功能:: echo enabled > /sys/bus/usb/devices/1-1/power/wakeup -如果系统只有一个 USB 端口, -而你希望在该端口上启用 USB 唤醒功能, -可以使用下面的脚本:: +如果系统只有一个 USB 端口,而你希望在该端口上启用 USB 唤醒功能,可以使用 +下面的脚本:: - for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done; + for i in $(find /sys -name wakeup | grep usb) + do + echo enabled > $i + done diff --git a/Documentation/translations/zh_CN/usb/dwc3.rst b/Documentation/translations/zh_CN/usb/dwc3.rst index 3468ce50c5ba..9584cbbf6d03 100644 --- a/Documentation/translations/zh_CN/usb/dwc3.rst +++ b/Documentation/translations/zh_CN/usb/dwc3.rst @@ -18,46 +18,43 @@ DWC3 驱动 待办 ~~~~ -阅读时如果想顺手认领点任务,可以从下面挑一项 :) +如果你愿意接手其中一项任务,可以从下面选择: - 将中断处理程序改为每个端点各自使用线程化 IRQ - 事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。 - 当前代码会一直自旋等待命令完成,这种设计并不好。 + 实践表明,某些 DWC3 命令大约需要 ``~1 ms`` 才能完成。当前代码会一直自旋 + 等待命令完成,这并不是好办法。 实现思路: - - DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。 - 中断号在探测(``probe``)阶段分配,并归属于该设备。 - 如果硬件通过 ``MSI`` 为每个端点提供独立中断, - 那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。 + - DWC 核心实现了一个按端点分发中断的 IRQ 控制器。中断号在探测 + (``probe``)阶段分配,并归属于该设备。如果硬件通过 ``MSI`` 为每个 + 端点提供独立中断,那么这个“虚拟”IRQ 控制器就可以被真实的端点中断 + 取代。 - - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源, - 在调用 ``usb_ep_disable()`` 时释放中断资源。 - 最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。 + - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,在调用 + ``usb_ep_disable()`` 时释放中断资源。最坏情况下需要 32 个中断,最少是 + ``ep0/1`` 的两个中断。 - ``dwc3_send_gadget_ep_cmd()`` 将在 ``wait_for_completion_timeout()`` 中休眠,直到命令完成。 - 中断处理程序分为以下几个部分: - 设备级主中断处理程序 - 遍历每个事件,并对其调用 ``generic_handle_irq()``。 - 从 ``generic_handle_irq()`` 返回后,确认事件计数器,使中断最终消失。 + 遍历每个事件,并调用 ``generic_handle_irq()`` 处理。返回后再确认 + 事件计数器,让中断最终消失。 - 设备级线程化处理程序 无。 - 端点中断的主处理程序 - 读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。 - 事件保存在每个端点的数据结构中。 - 还要注意,一旦把某项工作交给线程处理, - 就不要再在主处理程序里处理它, - 以免出现优先级反转之类的问题。 + 读取事件并尽量处理;凡是需要睡眠的操作都交给线程处理。事件保存在 + 每个端点的数据结构中。一旦某项工作已经交给线程处理,主处理程序里就 + 不要再碰它,以免出现优先级反转之类的问题。 - 端点中断的线程化处理程序 处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。 - 延迟: + 延迟: - 不应增加延迟,因为中断线程具有较高优先级, - 会在普通用户态任务之前运行 + 不应增加额外延迟,因为中断线程优先级较高,会在普通用户任务之前运行 (除非用户更改了调度优先级)。 diff --git a/Documentation/translations/zh_CN/usb/ehci.rst b/Documentation/translations/zh_CN/usb/ehci.rst index e05e493a30d3..c4c52303b13e 100644 --- a/Documentation/translations/zh_CN/usb/ehci.rst +++ b/Documentation/translations/zh_CN/usb/ehci.rst @@ -14,45 +14,37 @@ EHCI 驱动 ========= -2002年12月27日 +2002 年 12 月 27 日 -EHCI 驱动用于通过支持 USB 2.0 的主机控制器 -硬件与高速 USB 2.0 设备通信。USB 2.0 兼容 -USB 1.1 标准,它定义了三种传输速率: +EHCI 驱动用于借助支持 USB 2.0 的主机控制器,与高速 USB 2.0 设备通信。USB +2.0 向下兼容 USB 1.1,并定义了三种传输速率: - “高速”(High Speed)480 Mbit/sec(60 MByte/sec) - “全速”(Full Speed)12 Mbit/sec(1.5 MByte/sec) - “低速”(Low Speed)1.5 Mbit/sec -USB 1.1 仅支持全速与低速。 -高速设备可以在 USB 1.1 系统上使用, -但速度会降到 USB 1.1 的速率。 +USB 1.1 仅支持全速与低速。高速设备可以在 USB 1.1 系统上使用,但速度会 +降到 USB 1.1 的速率。 -USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们 -插入 EHCI 控制器时,会被交由 USB 1.1 的伴随 -(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。 +USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们插入 EHCI 控制器时,会交给 +USB 1.1 的伴随(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。 -当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过 -集线器中的事务转换器(Transaction Translator,TT) -与 EHCI 控制器交互,该转换器将低速或全速事务转换为 -高速分割事务,从而避免浪费传输带宽。 +当 USB 1.1 设备插入 USB 2.0 集线器时,它们会通过集线器里的事务转换器 +(Transaction Translator,TT)与 EHCI 控制器通信。该转换器会把低速或全 +速事务转换为高速分割事务,从而避免浪费传输带宽。 -截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行 -(按字母顺序):Intel、NEC、Philips 和 VIA。 -其他供应商的 EHCI 实现正在陆续问世; -预计该驱动在这些实现上也可正常运行。 +截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行(按字母顺序): +Intel、NEC、Philips 和 VIA。随着其他供应商的 EHCI 实现陆续问世,预计该 +驱动在那些实现上也能正常运行。 -自 2001 年年中起,usb-storage 设备就已可用 -(在 2.4 版该驱动上速度相当不错), -集线器则直到 2001 年底才开始可用,而其他类型的高速设备 -似乎要等到更多系统内置 USB 2.0 后才会出现。 -这类新系统从 2002 年初开始上市, -并在 2002 年下半年变得更加常见。 +自 2001 年年中起,usb-storage 设备就已可用(在 2.4 版该驱动上速度表现相当 +不错),集线器则直到 2001 年底才开始可用。其他类型的高速设备似乎要等到 +更多系统内置 USB 2.0 后才会出现。这类新系统从 2002 年初开始上市,并在 +2002 年下半年变得更加常见。 -注意,USB 2.0 支持并不只是 EHCI 本身。 -它还需要对 Linux-USB 核心 API 作出其他修改, -包括 hub 驱动;不过这些修改并不需要真正改变 -暴露给 USB 设备驱动的基本 ``usbcore`` API。 +注意,USB 2.0 的支持并不只靠 EHCI 本身。它还需要对 Linux-USB 核心 API +做其他修改,包括 hub 驱动;不过这些修改并不需要真正改变向 USB 设备驱动 +暴露的基本 ``usbcore`` API。 - David Brownell @@ -61,58 +53,46 @@ USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们 功能 ==== -该驱动会定期在 x86 硬件上进行测试, -也已在 PPC 硬件上使用,因此大小端问题应当已经解决。 -因此可以认为,它已经处理好了所有必要的 PCI 细节, -所以即便在 DMA 映射有些特殊的系统上, -I/O 也应能正常运行。 +该驱动长期在 x86 硬件上接受测试,也在 PPC 平台上使用过,因此大小端问题应 +该都已解决。再加上各种必要的 PCI 细节都已处理妥当,即便在 DMA 映射较特 +殊的系统上,I/O 也应能正常工作。 传输类型 -------- -截至本文撰写时,该驱动应当已经能够很好地处理 -所有控制传输、批量传输和中断传输, -包括通过 USB 2.0 集线器中的事务转换器 -与 USB 1.1 设备通信;但仍可能存在 bug。 +截至本文撰写时,该驱动应当已经能够稳定处理所有控制传输、批量传输和中断传 +输,包括经由 USB 2.0 集线器里的事务转换器访问 USB 1.1 设备;不过仍可能 +存在 bug。 -高速等时(ISO)传输支持也已可用,但截至本文撰写时, -还没有 Linux 驱动使用这项支持。 +高速等时(ISO)传输支持也已可用,不过截至本文撰写时,还没有 Linux 驱动真 +正使用它。 -目前尚不支持通过事务转换器实现全速等时传输。 -需要注意,ISO 传输的 split transaction 支持 -与高速 ISO 传输几乎无法共用代码, -因为 EHCI 用不同的数据结构表示它们。 -因此,目前大多数 USB 音频和视频设备 -还不能通过高速总线连接使用。 +目前尚不支持通过事务转换器实现全速等时传输。需要注意,ISO 传输的分割 +事务支持与高速 ISO 传输几乎无法共用代码,因为 EHCI 用不同的数据结构表示 +它们。因此,目前大多数 USB 音频和视频设备还无法在高速总线上使用。 驱动行为 -------- -所有类型的传输都可以排队。 -这意味着来自一个接口驱动的控制传输 -(或通过 usbfs 发出的控制传输)不会干扰 -另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期, -而不必担心中断处理开销导致的数据丢失。 +所有类型的传输都可以排队提交。这意味着某个接口驱动发出的控制传输(或经由 +usbfs 提交的控制传输)不会干扰其他驱动的控制传输,而中断传输可以按 1 帧 +周期运行,不必担心中断处理开销导致数据丢失。 -EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 -该驱动不需要了解那些驱动的任何细节; -一个原本就能正常工作的 OHCI 或 UHCI 驱动, -并不会因为 EHCI 驱动也存在而需要更改。 +EHCI 根集线器代码会将 USB 1.1 设备交给其伴随控制器处理。该驱动无需了解 +那些驱动的任何细节;一个原本就能正常工作的 OHCI 或 UHCI 驱动,也不会因为 +EHCI 驱动存在而需要修改。 -电源管理方面还有一些问题; -当前挂起/恢复的行为还不完全正确。 +电源管理方面仍有一些问题;当前挂起/恢复行为还不完全正确。 -此外,在调度周期性事务 -(中断和等时传输)时还采取了一些简化处理。 -这些简化会限制可调度的周期性事务数量, -并且无法使用小于一帧的轮询间隔。 +此外,在调度周期性事务(中断和等时传输)时还采取了一些简化处理。这些 +简化会限制可调度的周期性事务数量,并且无法使用小于一帧的轮询间隔。 使用方式 ======== -假设有一个 EHCI 控制器(位于 PCI 卡或主板上), -并且已将此驱动编译为模块,可这样加载:: +假设系统中有一个 EHCI 控制器(位于 PCI 卡或主板上),并且此驱动是以模块形 +式编译的,那么可以这样加载:: # modprobe ehci-hcd @@ -120,27 +100,24 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 # rmmod ehci-hcd -还应加载一个伴随控制器驱动, -例如 ``ohci-hcd`` 或 ``uhci-hcd``。 -如果 EHCI 驱动出现任何问题,只需卸载它的模块, -随后该伴随控制器驱动就会接手 -此前由 EHCI 驱动处理的所有设备 -(但速度会降低)。 +还应加载一个伴随控制器驱动,例如 ``ohci-hcd`` 或 ``uhci-hcd``。如果 EHCI +驱动出了问题,只要卸载它的模块,伴随控制器驱动就会接管此前由 EHCI 驱动处 +理的全部设备(只是速度会降低)。 模块参数(传给 ``modprobe``)包括: log2_irq_thresh(默认值 0): - 默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧 - (125 微秒)。最大值 6 表示 2^6 = 64 个微帧。 + 默认中断延迟的 log2 值,单位为微帧。默认值 0 表示 1 个微帧 + (125 微秒),最大值 6 表示 2^6 = 64 个微帧。 该值控制 EHCI 控制器发出中断的频率。 -如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持, -则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件: +如果你在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,那么任一 EHCI 控 +制器对应的 ``sysfs`` 目录下都会看到三个文件: ``async`` 转储异步调度,用于控制传输和批量传输。它会显示每个活动的 ``qh`` 以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。 - (可以在 ``usb-storage`` 做磁盘 I/O 时看它;顺便观察请求队列!) + (可以在 ``usb-storage`` 执行磁盘 I/O 时查看;也可顺便观察请求队列。) ``periodic`` 转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。 @@ -151,111 +128,81 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 这些文件的内容有助于定位驱动问题。 -设备驱动通常不需要关心自己是否运行在 EHCI 之上, -但它们可能想检查 -``usb_device->speed == USB_SPEED_HIGH``。 -高速设备能做到全速(或低速)设备做不到的事, -例如高带宽的周期性传输(中断或 ISO 传输)。 -另外,设备描述符中的某些值 -(例如周期性传输的轮询间隔) -在高速模式下使用不同的编码方式。 +设备驱动通常不需要关心自己是否运行在 EHCI 之上,但有时可能会想检查 +``usb_device->speed == USB_SPEED_HIGH``。高速设备能做到全速(或低速)设备 +做不到的事,例如高带宽的周期性传输(中断或 ISO 传输)。另外,设备描述符 +中的某些值(例如周期性传输的轮询间隔)在高速模式下使用不同的编码方式。 -不过,一定要让设备驱动经过 USB 2.0 集线器的测试。 -当使用事务转换器时,这些集线器报告某些故障 -(例如断开连接)的方式会不同; -已经见过一些驱动在遇到与 OHCI 或 UHCI -所报告的不同故障时表现不佳。 +不过,设备驱动一定要在 USB 2.0 集线器后面测一遍。使用事务转换器时,这些 +集线器报告某些故障(例如断开连接)的方式会有所不同;已经见过一些驱动在 +遇到与 OHCI 或 UHCI 不同的故障时表现不佳。 性能 ==== -USB 2.0 吞吐量主要受两个因素制约: -主机控制器处理请求的速度,以及设备响应这些请求的速度。 -480 Mbit/sec 的“原始传输率”对所有设备都成立, -但总吞吐量还会受到诸如单个高速包之间的延迟、 -驱动是否足够聪明,以及系统整体负载等因素的影响。 -延迟也是性能考量因素。 +USB 2.0 的吞吐量主要受两个因素制约:主机控制器处理请求的速度,以及设备响 +应这些请求的速度。480 Mbit/sec 的“原始传输率”对所有设备都一样,但整体吞 +吐量还会受到诸如高速包之间的间隔、驱动实现是否足够高效以及系统总体负载等 +因素影响。延迟同样是需要考虑的性能指标。 -批量传输最常用于关注吞吐量的场景。 -需要记住的是,批量传输总是以 512 字节包为单位, -而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。 -8 个 USB 2.0 微帧构成一个 USB 1.1 帧; -一个微帧的时长是 1 毫秒 / 8 = 125 微秒。 +批量传输通常用于看重吞吐量的场景。需要记住的是,批量传输总是以 512 字节包 +为单位,而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。8 个 USB 2.0 微 +帧构成一个 USB 1.1 帧,因此一个微帧的时长就是 125 微秒。 -因此,只要硬件和设备驱动软件都允许, -批量传输可提供超过 50 MByte/sec 的带宽。 -周期性传输模式(等时和中断)允许使用更大的包大小, -从而可以逼近所宣称的 480 Mbit/sec 传输率。 +因此,只要硬件和驱动实现都足够成熟,批量传输就可以提供 50 MByte/sec 以上 +的带宽。周期性传输模式(等时和中断)允许使用更大的包大小,从而可以逼近所 +宣称的 480 Mbit/sec 传输率。 硬件性能 -------- -截至本文撰写时,单个 USB 2.0 设备的最大传输速率 -通常约为 20 MByte/sec。 -这当然会随着时间改变:一些设备现在更快,一些更慢。 +截至本文撰写时,单个 USB 2.0 设备的最大传输速率通常约为 20 MByte/sec。 +这种情况当然会随时间变化:有些设备现在更快,有些则更慢。 -第一代 NEC EHCI 实现似乎存在 -大约 28 MByte/sec 的硬件瓶颈。 -虽然这对单个 20 MByte/sec 的设备显然已经够用, -但把三个这样的设备挂到同一总线上, -并不能得到 60 MByte/sec。 -问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问, -因此它每个微帧只会尝试 6 次(也许是 7 次) -USB 事务,而不是 13 次。 -(对一个比其他产品早上市一年的芯片来说, -这是个合理的妥协!) +第一代 NEC EHCI 实现似乎存在大约 28 MByte/sec 的硬件瓶颈。虽然这对单个 +20 MByte/sec 的设备显然已经够用,但把三个这样的设备挂到同一总线上,并不 +能得到 60 MByte/sec。问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问, +因此它每个微帧只会尝试 6 次(也许是 7 次)USB 事务,而不是 13 次。 +(对一款比其他产品早上市一年的芯片来说,这样的取舍也算合理。) -预计较新的实现会在这方面做得更好, -通过投入更多芯片面积来解决这个问题, -使新的主板芯片组更接近 60 MByte/sec 的目标。 -这既包括 NEC 的更新实现,也包括其他厂商的芯片。 +预计较新的实现会在这方面做得更好,通过投入更多芯片面积来解决这个问题, +使新的主板芯片组更接近 60 MByte/sec 的目标。这既包括 NEC 的更新实现,也 +包括其他厂商的芯片。 -主机从 EHCI 控制器收到“请求已完成”中断的最小延迟 -为一个微帧(125 微秒)。该延迟可以调节; -驱动提供了一个模块选项。默认情况下, -``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制 -或批量请求时,通常可以在不到 250 微秒内得知它已完成 -(具体取决于传输大小)。 +主机从 EHCI 控制器收到“请求已完成”中断的最小延迟为一个微帧 +(125 微秒)。该延迟可以调节;驱动提供了一个模块选项。 +默认情况下,``ehci-hcd`` 使用最小延迟,这意味着发出控制或批量请求后,通 +常不到 250 微秒就能得知它已经完成(具体取决于传输大小)。 软件性能 -------- -即便只是要达到 20 MByte/sec 的传输速率, -Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。 -这意味着要发出较大的请求, -或者在需要发出一连串小请求时使用批量请求排队。 -如果驱动未做到这一点,那么会直接从性能结果上表现出来。 +即便只是要达到 20 MByte/sec 的传输速率,Linux-USB 设备驱动也必须让 EHCI +队列始终保持满载。这意味着要发出较大的请求,或者在需要发出一连串小请求 +时使用批量请求排队。如果驱动做不到这一点,性能就会明显受影响。 -在典型情况下,使用 ``usb_bulk_msg()`` -以 4 KB 块循环写出, -会浪费超过一半的 USB 2.0 带宽。 -I/O 完成与驱动发出下一次请求之间的延迟, -通常会比一次 I/O 本身耗时更长。 -如果同样的循环改用 16 KB 块,会好一些; -若使用一连串 128 KB 块,则浪费会少得多。 +在典型场景下,如果使用 ``usb_bulk_msg()`` 以 4 KB 块循环写出,会浪费超过 +一半的 USB 2.0 带宽。I/O 完成与驱动发出下一次请求之间的空档,往往比一次 +I/O 本身耗时还长。如果同样的循环改用 16 KB 块,情况会好一些;若使用一连串 +128 KB 块,则浪费会少得多。 + +但与其依赖这么大的 I/O 缓冲区来提升同步 I/O 的效率,不如直接向主机控制器 +排队提交多个(批量)请求,然后等待它们全部完成(或在出错时取消)。这种 +URB 排队方式对所有 USB 1.1 主机控制器驱动同样适用。 -但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效, -不如直接向主机控制器排入多个(批量)请求, -然后等待它们全部完成(或在出错时取消)。 -这种 URB 排队方式对所有 USB 1.1 -主机控制器驱动也同样适用。 - - -在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API; -它们会把 scatterlist 中的所有缓冲区都排入队列。 -它们还使用 scatterlist 的 DMA 映射 -(其中可能应用 IOMMU)并减少中断次数, -这些都有助于让高速传输尽可能快地运行。 +在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;它们会把 scatterlist +中的所有缓冲区都排入队列。它们还使用 scatterlist 的 DMA 映射(其中可能 +应用 IOMMU)并减少中断次数,这些都有助于让高速传输尽可能快地运行。 待办: 中断传输和等时(ISO)传输的性能问题。 - 这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。 + 这些周期性传输都是完全调度的,因此主要问题可能在于如何触发高带宽模式。 待办: - 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数, - 可以分配超过标准 80% 的周期性带宽。 + 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,可以分配超过标准 + 80% 的周期性带宽。 后续将对此进行说明。 diff --git a/Documentation/translations/zh_CN/usb/index.rst b/Documentation/translations/zh_CN/usb/index.rst index eb5aca0c13ec..df99814c6497 100644 --- a/Documentation/translations/zh_CN/usb/index.rst +++ b/Documentation/translations/zh_CN/usb/index.rst @@ -1,4 +1,14 @@ .. SPDX-License-Identifier: GPL-2.0 + +.. only:: subproject and latex + + .. raw:: latex + + \renewcommand{\thesection}{} + \renewcommand{\thesubsection}{} + \kerneldocCJKon + \kerneldocBeginSC{ + .. include:: ../disclaimer-zh_CN.rst :Original: Documentation/usb/index.rst @@ -24,7 +34,7 @@ USB 支持 ehci usbmon -Todolist: +待翻译文档: * functionfs * functionfs-desc @@ -52,3 +62,9 @@ Todolist: ==== * :ref:`genindex` + +.. only:: subproject and latex + + .. raw:: latex + + }\kerneldocEndSC diff --git a/Documentation/translations/zh_CN/usb/usbmon.rst b/Documentation/translations/zh_CN/usb/usbmon.rst index 11b6d5b59dce..db6030cd64a1 100644 --- a/Documentation/translations/zh_CN/usb/usbmon.rst +++ b/Documentation/translations/zh_CN/usb/usbmon.rst @@ -16,67 +16,56 @@ usbmon 简介 ==== -小写形式的 ``usbmon`` 指的是内核中的一项功能, -用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具 -``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。 -类似地,人们希望使用 usbdump 或 USBMon -(首字母大写)之类的工具来检查 -usbmon 生成的原始跟踪数据。 +小写的 ``usbmon`` 指的是内核中的一项功能,用于收集 USB 总线上的 I/O 跟踪 +信息。它类似于网络监控工具 ``tcpdump(1)`` 或 Ethereal 使用的数据包套接 +字。通常会用 usbdump 或 USBMon(首字母大写)之类的工具来查看 usbmon 生成 +的原始跟踪数据。 -usbmon 报告的是各个外设驱动 -向主机控制器驱动(HCD)发出的请求。 -因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息 -可能无法精确对应实际的总线事务。 -这和 tcpdump 的情况是一样的。 +usbmon 记录的是各个设备驱动向主机控制器驱动(HCD)发出的请求。因此,如果 +HCD 自身有 bug,usbmon 输出的跟踪信息就未必能和真实的总线事务一一对应。 +这和 tcpdump 的情况类似。 -目前实现了两种 API: ``text`` 和 ``binary``。 -二进制 API 通过 ``/dev`` 命名空间中的字符设备提供, -并且属于 ABI。文本 API 自内核 2.6.35 起已废弃, -但为了方便仍然可用。 +目前实现了两种 API:``text`` 和 ``binary``。二进制 API 通过 ``/dev`` 下的 +字符设备提供,是 ABI 的一部分。文本 API 自内核 2.6.35 起已废弃,但为了 +兼容和使用方便,至今仍然保留。 如何使用 usbmon 收集原始文本跟踪信息 ==================================== -与数据包套接字不同,usbmon 提供了一种接口, -可以输出文本格式的跟踪信息。这样做有两个目的: -第一,在更完善的格式最终确定之前, -它作为工具间通用的跟踪交换格式; -第二,在不使用工具的情况下,人们也可以直接阅读这些信息。 +与数据包套接字不同,usbmon 还提供了一个输出文本格式跟踪信息的接口。这样 +做主要有两个目的:一是在更完善的格式最终确定之前,将其作为工具间通用的跟 +踪交换格式;二是在没有工具时也能直接阅读这些信息。 -要收集原始文本跟踪信息,请按以下步骤进行操作。 +要收集原始文本跟踪信息,按下面的步骤做即可。 1. 准备 ------- -挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块 -(如果它是作为模块构建的)。如果 usbmon 已经编入内核, -那么第二步可以省略。 +挂载 debugfs(内核配置里必须启用它),并加载 usbmon 模块(如果它是以模块 +方式构建的)。如果 usbmon 已经编译进内核,这一步就可以省略。 命令示例:: - # mount -t debugfs none_debugs /sys/kernel/debug + # mount -t debugfs none /sys/kernel/debug # modprobe usbmon # -确认总线套接字是否存在:: +确认 ``usbmon`` 目录下是否有这些条目:: # ls /sys/kernel/debug/usb/usbmon 0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u # -现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包, -并跳到第 3 步; -也可以先按第 2 步找到目标设备所在的总线。 -这样可以过滤掉那些持续输出数据的烦人设备。 +现在,你可以直接用 ``0u`` 捕获所有总线上的数据包,然后跳到第 3 步;也可 +以先按第 2 步找出目标设备所在的总线。这样可以把那些持续产生流量的设备过 +滤掉。 2. 查找目标设备连接的是哪条总线 ------------------------------- -运行 ``cat /sys/kernel/debug/usb/devices``, -找到对应设备的 T 行。通常可以通过厂商字符串来查找。 -如果有许多类似设备,可以拔掉其中一个, -再比较前后两次 ``/sys/kernel/debug/usb/devices`` -的输出。T 行里会包含总线编号。 +运行 ``cat /sys/kernel/debug/usb/devices``,找到对应设备的 T 行。通常可以通过 +厂商字符串来查找。如果有很多相似设备,可以拔掉其中一个,再比较前后两次 +``/sys/kernel/debug/usb/devices`` 的输出。T 行里会包含总线编号。 示例:: @@ -86,8 +75,8 @@ usbmon 报告的是各个外设驱动 S: Manufacturer=ATEN S: Product=UC100KM V2.00 -``Bus=03`` 表示它位于 3 号总线上。或者, -也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。 +``Bus=03`` 表示它位于 3 号总线上。或者,也可以查看 ``lsusb`` 的输出,并从 +对应条目里找到总线编号。 示例如下:: @@ -97,133 +86,110 @@ usbmon 报告的是各个外设驱动 3. 启动 cat 命令 ---------------- -如果只监听单条总线,可执行:: +如果只监听单条总线,执行:: # cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out -否则,如果要监听所有总线,则执行:: +否则,如果要监听所有总线,执行:: # cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out -此进程会一直读取,直到被终止。 -由于输出通常会很长,因此更推荐将输出重定向到某个位置。 +这个进程会一直运行到被终止为止。由于输出通常会很长,最好把它重定向到文件 +或其他位置。 4. 在 USB 总线上执行期望的操作 ------------------------------ -此处需要执行一些会产生 USB 流量的动作, -比如插入 U 盘、拷贝文件、操作摄像头等。 +这里做一些会产生 USB 流量的操作即可,比如插入 U 盘、拷贝文件、操作摄像头 +等。 5. 停止 cat ----------- -这一步通常通过键盘中断(Control-C)完成。 +这一步通常按下键盘中断(Control-C)即可完成。 -此时输出文件(本例中为 ``/tmp/1.mon.out``) -可以保存、通过电子邮件发送,或使用文本编辑器查看。 -如果使用最后一种方式,请确保文件不会大到编辑器无法打开。 +此时,输出文件(本例中为 ``/tmp/1.mon.out``)可以保存下来,通过电子邮件发 +送,也可以用文本编辑器查看。如果要用文本编辑器查看,请确保文件大小不会 +大到编辑器无法处理。 原始文本数据格式 ================ -目前支持两种格式:原始格式,也就是 ``1t`` 格式, -以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。 -``1u`` 格式增加了一些字段,例如 ISO 帧描述符、 -``interval`` 等。它生成的行会稍长一些, -但在其他方面是 ``1t`` 格式的完整超集。 +目前支持两种格式:原始的 ``1t`` 格式和 ``1u`` 格式。``1t`` 格式在内核 +2.6.21 中已被废弃。``1u`` 格式增加了一些字段,例如 ISO 帧描述符和 +``interval``。它生成的行会稍长一些,但除此之外,它是 ``1t`` 格式的完整 +超集。 -如果程序需要区分上述两种格式, -可以查看 ``address`` 字段(见下文)。 -如果其中有两个冒号,就是 ``1t`` 格式; -否则是 ``1u`` 格式。 +如果程序需要区分上述两种格式,可以查看 ``address`` 字段(见下文)。如果 +其中有两个冒号,就是 ``1t`` 格式;否则是 ``1u`` 格式。 -任何文本格式的数据由一系列事件组成, -如 URB 提交、URB 回调、提交错误等。 -每个事件对应单独的一行文本, -由使用空白符间隔的若干字段组成。 -字段的数量与位置可能取决于事件类型, -但以下字段对所有类型都通用: +任何文本格式的数据都由一系列事件构成,例如 URB 提交、URB 回调和提交错 +误。每个事件占一行,由若干以空白符分隔的字段组成。字段数量和位置会随事件 +类型变化,但下面这些字段对所有类型都通用: -下面按从左到右的顺序列出这些共有字段: +下面按从左到右的顺序说明这些通用字段: -- URB Tag。用于标识 URB,通常是 URB 结构体在内核中的地址 - (以十六进制表示), - 但也可能是序号或其他合理的唯一字符串。 +- URB 标识(URB Tag)。用于标识 URB,通常是 URB 结构体在内核中的地址 + (十六进制),也可能是序号或其他足以唯一标识 URB 的字符串。 -- 时间戳(微秒),十进制数字。 - 时间戳的精度取决于可用时钟, - 因此可能远差于 - 1 微秒(例如实现使用的是 jiffies)。 +- 时间戳(微秒),十进制数字。时间戳的精度取决于可用时钟,所以可能远低于 + 1 微秒(例如实现使用 jiffies 时)。 -- 事件类型。它表示的是事件的格式,而不是 URB 的类型。 - 可用值为:``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。 +- 事件类型。它表示的是这一行事件的格式,而不是 URB 的类型。可用值为: + ``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。 -- ``Address`` 字段(以前称作 ``pipe``)。 - 它包含四个由冒号分隔的字段: - URB 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下: +- ``Address`` 字段(以前称为 ``pipe``)。它包含四个由冒号分隔的字段:URB + 类型及方向、总线号、设备地址和端点号。类型与方向按下面的方式编码: - == == ========================== - Ci Co 控制输入和输出 - Zi Zo 等时输入和输出 - Ii Io 中断输入和输出 - Bi Bo 批量输入和输出 - == == ========================== + == == ==================== + Ci Co 控制输入与输出 + Zi Zo 等时输入与输出 + Ii Io 中断输入与输出 + Bi Bo 批量输入与输出 + == == ==================== - 总线号、设备地址和端点号使用十进制,但可能有前导零。 + 总线号、设备地址和端点号都是十进制数,但可能有前导零,方便人阅读。 -- URB 状态字段。这个字段要么是一个字母, - 要么是几个由冒号分隔的数字: - URB 状态、``interval``、``start frame`` 和 ``error count``。 - 与 ``address`` 字段不同,除了状态外,其余字段都是可选的。 - ``interval`` 只会为中断和等时 URB 打印;``start frame`` 只会为 - 等时 URB 打印;错误计数只会在等时回调事件中打印。 +- URB 状态字段。这个字段要么是一个字母,要么是几个用冒号分隔的数字,依次 + 表示 URB 状态、``interval``、``start frame`` 和 ``error count``。与 + ``address`` 字段不同,除状态外,其余字段都可能省略。``interval`` 只会在 + 中断和等时 URB 中打印;``start frame`` 只会在等时 URB 中打印;错误计数只 + 会在等时回调事件中打印。 - 状态字段是一个十进制数字,有时为负数, - 对应 URB 的 ``status`` 字段。 - 对于提交事件,这个字段本身没有实际意义, - 但为了便于脚本解析,它仍然存在。 - 当发生错误时,该字段包含错误码。 + 状态字段是一个十进制数,有时为负数,对应 URB 的 ``status`` 字段。对于提 + 交事件,这个字段本身并无实际语义,但为了便于脚本解析仍会保留。发生错误 + 时,这里填的是错误码。 - 在提交控制包时,这个字段包含的是 ``Setup Tag``, - 而不是一组数字。 - 判断 ``Setup Tag`` 是否存在很容易,因为它从来不是数字。 - 因此,如果脚本在这个字段里发现的是一组数字, - 就会继续读取数据长度(等时 URB 除外)。 - 如果发现的是其他内容,比如一个字母, - 那么脚本会先读取 ``Setup`` 包,再读取数据长度或等时描述符。 + 如果是控制包的提交事件,这个字段里放的不是一组数字,而是 ``Setup Tag``。 + 这很容易分辨,因为 ``Setup Tag`` 永远不是数字。所以脚本如果在这里读到一 + 组数字,就会继续读取数据长度(等时 URB 除外);如果读到的是字母之类的内 + 容,就要先读取 ``Setup`` 包,再读取数据长度或等时描述符。 -- ``Setup`` 包由 5 个字段组成: - ``bmRequestType``、``bRequest``、``wValue``、 - ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。 - 如果 ``Setup Tag`` 为 ``s``,就可以安全地解码这些字段。 - 否则,说明 Setup 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。 +- ``Setup`` 包由 5 个字段组成:``bmRequestType``、``bRequest``、``wValue``、 + ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。如果 ``Setup Tag`` + 是 ``s``,就可以安全解码这些字段。否则,说明 Setup 包虽然存在,但并未被 + 捕获,此时各字段中会填入占位内容。 - 等时传输帧描述符的数量及其内容: - 如果一个等时传输事件带有一组描述符,首先打印该 URB 中描述符的总数, - 然后为每个描述符打印一个字段,最多打印 5 个字段。 - 每个字段由三个用冒号分隔的十进制数字组成, - 分别表示状态(status)、偏移(offset)和长度(length)。 - 对于提交(submission),报告的是初始长度; - 对于回调(callback),报告的是实际长度。 + 如果某个等时传输事件带有描述符,会先打印该 URB 的描述符总数,再为每个描 + 述符打印一个字段,最多 5 个。每个字段由三个用冒号分隔的十进制数组成,依 + 次表示状态(status)、偏移(offset)和长度(length)。对于提交事件,报 + 告的是初始长度;对于回调事件,报告的是实际长度。 -- 数据长度: - 对于提交,表示请求的长度;对于回调,表示实际传输的长度。 +- 数据长度:对于提交,表示请求的长度;对于回调,表示实际传输的长度。 -- 数据标签: - 即使数据长度非零,usbmon 也不一定会捕获数据。 - 仅当标签为 ``=`` 时,才会有数据字段。 +- 数据标签:即使数据长度非零,usbmon 也不一定会捕获数据。只有标签为 + ``=`` 时,才会有数据字段。 -- 数据字段: - 以大端十六进制格式显示。注意,这些并不是真正的机器字, - 而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含 - 1 到 4 个字节。 - 收集的数据长度是有限的,可能小于数据长度字段中报告的值。 - 因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区, - 所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下, - 收集的数据长度可能大于数据长度字段的值。 +- 数据字段:以大端十六进制格式显示。注意,这些并不是真正的机器字,只是为 + 了便于阅读,把字节流按“字”分组显示。因此最后一个字可能只包含 1 到 4 个 + 字节。捕获的数据长度是有限的,可能小于数据长度字段中报告的值。对于等时 + 输入(Zi)完成事件,如果缓冲区里的接收数据比较稀疏,捕获数据的长度甚至 + 可能大于数据长度字段,因为后者只统计实际接收到的字节,而数据字段展示的 + 是整个传输缓冲区。 @@ -234,18 +200,19 @@ usbmon 报告的是各个外设驱动 d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 < d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000 -向地址为 5 的存储设备发送 -31 字节 Bulk 包装的 SCSI 命令 ``0x28`` -(``READ_10``)的输出批量传输:: +向地址为 5 的存储设备发送一个输出批量传输,其中 31 字节的 Bulk 封装用于承 +载 SCSI 命令 ``0x28``(``READ_10``)。为便于排版,下面的第一条记录按两行 +显示,但实际 usbmon 输出仍是一行:: - dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000 + dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = + 55534243 ad000000 00800000 80010a28 20000000 20000040 + 00000000 000000 dd65f0e8 4128379808 C Bo:1:005:2 0 31 > 原始二进制格式与 API ==================== -API 的整体架构与前文大体相同,只是事件以二进制格式传递。 -每个事件都通过下面的结构发送 -(这个名字是为了叙述方便而虚构的):: +API 的整体架构与前文大体相同,只是事件以二进制格式传递。每个事件都通过 +下面的结构发送(这个结构名只是为了叙述方便而虚构的):: struct usbmon_packet { @@ -275,29 +242,22 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 unsigned int ndesc; /* 60: 实际 ISO 描述符数量 */ }; /* 64 总长度 */ -可以用 ``read(2)``、``ioctl(2)``, -或者通过 ``mmap`` 访问缓冲区, -从字符设备接收这些事件。 -不过,出于兼容性原因,``read(2)`` -只返回前 48 个字节。 +可以用 ``read(2)``、``ioctl(2)``,或者通过 ``mmap`` 访问缓冲区,从字符设 +备接收这些事件。不过,出于兼容性原因,``read(2)`` 只返回前 48 个字节。 -字符设备通常命名为 ``/dev/usbmonN``, -其中 ``N`` 是 USB 总线号。 -编号为零的设备(``/dev/usbmon0``)比较特殊, -表示“所有总线”。 -请注意,具体命名策略由 Linux 发行版决定。 +字符设备通常命名为 ``/dev/usbmonN``,其中 ``N`` 是 USB 总线号。编号为零的 +设备(``/dev/usbmon0``)比较特殊,表示“所有总线”。具体命名策略由 Linux +发行版决定。 -如果你手动创建 ``/dev/usbmon0``, -请确保它归 root 所有,并且权限为 ``0600``。 -否则,非特权用户将能够窃听键盘流量。 +如果你手动创建 ``/dev/usbmon0``,请确保它归 root 所有,并且权限为 ``0600``。 +否则,非特权用户就能窃听键盘输入流量。 以下 ``MON_IOC_MAGIC`` 为 ``0x92`` 的 ioctl 调用可用: ``MON_IOCQ_URB_LEN``,定义为 ``_IO(MON_IOC_MAGIC, 1)`` -该调用返回下一个事件的数据长度。 -注意大多数事件不包含数据, -因此如果该调用返回零,并不意味着没有事件。 +该调用返回下一个事件的数据长度。注意大多数事件不包含数据,因此如果它返回 +零,并不意味着没有事件。 ``MON_IOCG_STATS``,定义为 ``_IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)`` @@ -309,18 +269,16 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 u32 dropped; }; -成员 ``queued`` 表示当前缓冲区中已经排队的事件数量, -而不是自上次重置以来处理过的事件数量。 +成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,而不是自上次重置以来 +处理过的事件数量。 -成员 ``dropped`` 表示自上次调用 -``MON_IOCG_STATS`` 以来丢失的事件数量。 +成员 ``dropped`` 表示自上次调用 ``MON_IOCG_STATS`` 以来丢失的事件数量。 ``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)`` -此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。 -大小可能会向下取整到下一个块(或页)。 -如果请求的大小超出该内核的 [未指定] 范围, -则调用会失败并返回 ``-EINVAL``。 +此调用设置缓冲区大小。参数是以字节为单位的缓冲区大小。大小可能会向下取整 +到下一个块(或页)。如果请求的大小超出当前内核允许的范围,则调用会失败并 +返回 ``-EINVAL``。 ``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)`` @@ -331,9 +289,8 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 ``MON_IOCX_GETX``,定义为 ``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)`` -如果内核缓冲区中没有事件, -这些调用就会一直等待,直到有事件到达, -然后返回第一个事件。 +如果内核缓冲区中没有事件,这些调用就会一直等待,直到有事件到达,然后返回 +第一个事件。 参数是指向以下结构的指针:: struct mon_get_arg { @@ -343,20 +300,18 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 }; -调用前,应填好 ``hdr``、``data`` 和 ``alloc``。 -调用返回后,``hdr`` 指向的区域中包含下一个事件的结构; -如果存在数据,那么数据缓冲区中也会包含相应数据。 -该事件会从内核缓冲区中移除。 +调用前,应填好 ``hdr``、``data`` 和 ``alloc``。调用返回后,``hdr`` 指向的 +内存区域中会写入下一个事件的结构;如果存在数据,数据缓冲区中也会填入相应 +内容。该事件会从内核缓冲区中移除。 -``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域, -``MON_IOCX_GETX`` 会复制 64 字节。 +``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,``MON_IOCX_GETX`` +会复制 64 字节。 ``MON_IOCX_MFETCH``,定义为 ``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)`` -当应用程序通过 ``mmap(2)`` 访问缓冲区时, -主要使用这个 ioctl。 -其参数是指向以下结构的指针:: +应用程序通过 ``mmap(2)`` 访问缓冲区时,主要使用这个 ioctl。其参数是指向 +以下结构的指针:: struct mon_mfetch_arg { uint32_t *offvec; /* 获取的事件偏移向量 */ @@ -365,41 +320,36 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 }; -该 ioctl 的操作分为三个阶段: +这个 ioctl 的流程分为三个阶段: -首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。 -实际丢弃的事件数量会写回 ``nflush``。 +首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。实际丢弃的事件数量会 +写回 ``nflush``。 -其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待, -直到缓冲区中出现事件。 +其次,除非设备以 ``O_NONBLOCK`` 打开,否则会一直等待,直到缓冲区中出现 +事件。 -第三,将最多 ``nfetch`` 个偏移量提取到 mmap -缓冲区,并存入 ``offvec`` 中。 -实际提取到的事件偏移数量会存回 ``nfetch``。 +第三,将最多 ``nfetch`` 个偏移量提取到 mmap 缓冲区,并存入 ``offvec`` 中。 +实际提取到的事件偏移数量会写回 ``nfetch``。 ``MON_IOCH_MFLUSH``,定义为 ``_IO(MON_IOC_MAGIC, 8)`` -此调用从内核缓冲区移除若干事件。 -其参数为要移除的事件数量。 -如果缓冲区中的事件少于请求数量, -则移除所有事件,且不报告错误。 -当没有事件时也可使用。 +此调用从内核缓冲区移除若干事件。其参数是要移除的事件数量。如果缓冲区中的 +事件少于请求数量,则移除全部现有事件,且不报告错误。即使当前没有事件,也 +可以调用。 ``FIONBIO`` 如果有需要,将来可能会实现 ``FIONBIO`` ioctl。 -除了 ``ioctl(2)`` 和 ``read(2)`` 之外, -二进制 API 的特殊文件也可以用 ``select(2)`` 和 -``poll(2)`` 轮询。 -但 ``lseek(2)`` 不起作用。 +除了 ``ioctl(2)`` 和 ``read(2)`` 之外,二进制 API 对应的特殊文件还可以用 +``select(2)`` 和 ``poll(2)`` 轮询,但 ``lseek(2)`` 不可用。 * 二进制 API 的内核缓冲区内存映射访问 -基本思想很简单: +基本思路很简单: -准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。 -然后执行类似下面伪代码的循环:: +准备时,先查询当前大小,再用 ``mmap(2)`` 映射缓冲区。之后运行与下面伪代码 +类似的循环:: struct mon_mfetch_arg fetch; struct usbmon_packet *hdr; @@ -411,7 +361,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 ioctl(fd, MON_IOCX_MFETCH, &fetch); // 同时处理错误 nflush = fetch.nfetch; // 完成后要刷新这么多包 for (i = 0; i < nflush; i++) { - hdr = (struct ubsmon_packet *) &mmap_area[vec[i]]; + hdr = (struct usbmon_packet *) &mmap_area[vec[i]]; if (hdr->type == '@') // 填充包 continue; caddr_t data = &mmap_area[vec[i]] + 64; @@ -421,7 +371,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 -因此,主要思想是每 N 个事件只执行一次 ioctl。 +因此,这里的核心思路就是每 N 个事件只执行一次 ioctl。 -虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端, -因此上面的伪代码无需任何合并操作。 +虽然缓冲区是环形的,但返回的头部和数据不会跨越缓冲区末端,因此上面的伪代 +码无需做任何拼接。 From 844d83d5964b87919b958ff48405188c6ddae9cc Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Tue, 19 May 2026 22:33:19 +0800 Subject: [PATCH 030/163] usb: gadget: uac: validate rate list length before storing UAC1 and UAC2 configfs rate-list attributes parse a comma-separated list of sampling rates and store each parsed value in fixed-size arrays. The arrays have UAC_MAX_RATES entries, but the store paths do not check that the input contains at most that many tokens before writing through opts->name##s[i++]. Writing more than ten rates therefore writes past the end of the p_srates[] or c_srates[] array in struct f_uac1_opts or struct f_uac2_opts. With CONFIG_UBSAN_BOUNDS enabled, writing an 11-entry rate list to the UAC1 p_srate attribute reports: UBSAN: array-index-out-of-bounds drivers/usb/gadget/function/f_uac1.c:1669:1 index 10 is out of range for type 'int [10]' __ubsan_handle_out_of_bounds.cold f_uac1_opts_p_srate_store configfs_write_iter vfs_write ksys_write do_syscall_64 The same reproducer against the UAC2 p_srate attribute reports: UBSAN: array-index-out-of-bounds drivers/usb/gadget/function/f_uac2.c:2087:1 index 10 is out of range for type 'int [10]' __ubsan_handle_out_of_bounds.cold f_uac2_opts_p_srate_store configfs_write_iter vfs_write ksys_write do_syscall_64 Reject additional tokens once UAC_MAX_RATES entries have been parsed. Also keep the original kstrdup() pointer for kfree(), because strsep() advances the parsing cursor. Freeing the advanced cursor leaks the original buffer on successful parses and can free an interior pointer on some error paths. Fixes: 695d39ffc2b5 ("usb: gadget: f_uac1: Support multiple sampling rates") Fixes: a7339e4f5788 ("usb: gadget: f_uac2: Support multiple sampling rates") Signed-off-by: Qing Ming Link: https://patch.msgid.link/20260519143319.147494-1-a0yami@mailbox.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uac1.c | 13 +++++++++---- drivers/usb/gadget/function/f_uac2.c | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 85c502e98f57..7a81cd176abd 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -1594,7 +1594,8 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - char *split_page = NULL; \ + char *buf = NULL; \ + char *split_page; \ int ret = -EINVAL; \ char *token; \ u32 num; \ @@ -1608,18 +1609,22 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ \ i = 0; \ memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ - split_page = kstrdup(page, GFP_KERNEL); \ + buf = kstrdup(page, GFP_KERNEL); \ + split_page = buf; \ while ((token = strsep(&split_page, ",")) != NULL) { \ ret = kstrtou32(token, 0, &num); \ if (ret) \ goto end; \ - \ + if (i >= UAC_MAX_RATES) { \ + ret = -EINVAL; \ + goto end; \ + } \ opts->name##s[i++] = num; \ ret = len; \ }; \ \ end: \ - kfree(split_page); \ + kfree(buf); \ mutex_unlock(&opts->lock); \ return ret; \ } \ diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 897787d0803c..d8cf710085a0 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -2012,7 +2012,8 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uac2_opts *opts = to_f_uac2_opts(item); \ - char *split_page = NULL; \ + char *buf = NULL; \ + char *split_page; \ int ret = -EINVAL; \ char *token; \ u32 num; \ @@ -2026,18 +2027,22 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ \ i = 0; \ memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ - split_page = kstrdup(page, GFP_KERNEL); \ + buf = kstrdup(page, GFP_KERNEL); \ + split_page = buf; \ while ((token = strsep(&split_page, ",")) != NULL) { \ ret = kstrtou32(token, 0, &num); \ if (ret) \ goto end; \ - \ + if (i >= UAC_MAX_RATES) { \ + ret = -EINVAL; \ + goto end; \ + } \ opts->name##s[i++] = num; \ ret = len; \ }; \ \ end: \ - kfree(split_page); \ + kfree(buf); \ mutex_unlock(&opts->lock); \ return ret; \ } \ From 621707dc67c9846fd876d7579ec951d92aa033f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 12 Jun 2026 15:57:57 +0100 Subject: [PATCH 031/163] usb: gadget: f_fs: Fix fence cleanup in ffs_dmabuf_transfer() error paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The error paths for endpoint-disabled (ESHUTDOWN) and request-allocation failure (ENOMEM) in ffs_dmabuf_transfer() jump to err_fence_put which calls dma_fence_put() on the fence. However, at that point the fence has only been kmalloc'd — dma_fence_init() has not been called yet, so the refcount and the fence ops are uninitialized. Calling dma_fence_put() on such an object leads to undefined behavior. Use kfree() instead, since the fence is just a plain allocation at this stage, and rename the label to err_fence_free to reflect the actual cleanup action. Fixes: 7b07a2a7ca02 ("usb: gadget: functionfs: Add DMABUF import interface") Signed-off-by: Nuno Sá Reviewed-by: Paul Cercueil Link: https://patch.msgid.link/20260612-fix-f_fs-fence-cleanup-v1-1-79f489b0efe9@analog.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 75912ce6ab55..ac8c53789ec2 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1682,13 +1682,13 @@ static int ffs_dmabuf_transfer(struct file *file, /* In the meantime, endpoint got disabled or changed. */ if (epfile->ep != ep) { ret = -ESHUTDOWN; - goto err_fence_put; + goto err_fence_free; } usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); if (!usb_req) { ret = -ENOMEM; - goto err_fence_put; + goto err_fence_free; } /* @@ -1736,9 +1736,9 @@ static int ffs_dmabuf_transfer(struct file *file, return ret; -err_fence_put: +err_fence_free: spin_unlock_irq(&epfile->ffs->eps_lock); - dma_fence_put(&fence->base); + kfree(fence); err_resv_unlock: dma_resv_unlock(dmabuf->resv); err_attachment_put: From c3249c462b30e003712cd80f05c26ae3ee29040c Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Fri, 19 Jun 2026 04:06:05 +0000 Subject: [PATCH 032/163] usb: gadget: f_fs: Add zero-length packet ioctl When transferring data from a USB gadget to a host, a transfer is considered complete when the host receives a short packet (a packet smaller than wMaxPacketSize). If the total transfer length is an exact multiple of wMaxPacketSize, a Zero-Length Packet (ZLP) must be appended to signal the end of the transfer. FunctionFS currently provides no mechanism for userspace to instruct the kernel to set the `req->zero` flag on transfers. Userspace workarounds, such as manually submitting separate 0-byte requests, may not be available for legacy protocols which must maintain write behavior compatibility when moved to functionfs implementations. To resolve this, introduce a new ioctl, FUNCTIONFS_ENDPOINT_ENABLE_ZLP, which takes a pointer to a __u32 flag. When enabled, all subsequent transfers on that endpoint will have the `req->zero` flag set, allowing the underlying USB Device Controller (UDC) hardware to automatically append a ZLP only when mathematically required. For logical transfers chunked across multiple requests, userspace can dynamically toggle this flag, enabling it only prior to submitting the final chunk. The flag defaults to false to maintain backward compatibility. Once enabled, the state is persistent for the lifetime of the endpoint and will not be reset by opening or closing the endpoint file descriptors. Assisted-by: Gemini-CLI:gemini-3.1-pro-preview Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20260619040609.4010746-4-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/functionfs.rst | 24 ++++++++++++++++++++++++ drivers/usb/gadget/function/f_fs.c | 25 ++++++++++++++++++++++++- include/uapi/linux/usb/functionfs.h | 23 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst index f7487b0d8057..582e53549d5b 100644 --- a/Documentation/usb/functionfs.rst +++ b/Documentation/usb/functionfs.rst @@ -72,6 +72,30 @@ have been written to their ep0's. Conversely, the gadget is unregistered after the first USB function closes its endpoints. +Endpoint IOCTLs +=============== + +FunctionFS supports additional IOCTLs that can be performed on data endpoints +(ie. not ep0). For a full list of these IOCTLs, please refer to the documentation +in ``include/uapi/linux/usb/functionfs.h``. + +One such IOCTL is: + + ``FUNCTIONFS_ENDPOINT_ENABLE_ZLP(__u32 *)`` + Enable or disable automatic zero-length packet (ZLP) appending for the + endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to + enable. When enabled, the kernel will automatically append a ZLP at the end + of a transfer if the payload length is an exact multiple of the endpoint's + max packet size. This is useful for compatibility with legacy protocols + which require automatic ZLP appending to data written from userspace. This + IOCTL can only be used on IN endpoints. It can be called at any time after + the FunctionFS instance is active, even before the host has connected or + enabled the endpoint. Returns zero on success, or a negative errno value on + error: + + * ``-ENODEV``: The FunctionFS instance is not active. + * ``-EINVAL``: The endpoint is not an IN endpoint. + * ``-EFAULT``: Invalid user space pointer for the argument. DMABUF interface ================ diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index ac8c53789ec2..70e7c18ce062 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -224,7 +224,7 @@ struct ffs_epfile { unsigned char in; /* P: ffs->eps_lock */ unsigned char isoc; /* P: ffs->eps_lock */ - unsigned char _pad; + u8 zlp_enabled; /* P: ffs->eps_lock */ /* Protects dmabufs */ struct mutex dmabufs_mutex; @@ -1114,6 +1114,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->buf = data; req->num_sgs = 0; } + + req->zero = epfile->zlp_enabled; req->length = data_len; io_data->buf = data; @@ -1165,6 +1167,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->buf = data; req->num_sgs = 0; } + + req->zero = epfile->zlp_enabled; req->length = data_len; io_data->buf = data; @@ -1708,6 +1712,7 @@ static int ffs_dmabuf_transfer(struct file *file, /* Now that the dma_fence is in place, queue the transfer. */ + usb_req->zero = epfile->zlp_enabled; usb_req->length = req->length; usb_req->buf = NULL; usb_req->sg = priv->sgt->sgl; @@ -1755,6 +1760,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, struct ffs_epfile *epfile = file->private_data; struct ffs_ep *ep; int ret; + __u32 enable_zlp = 0; if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; @@ -1787,6 +1793,23 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, return ffs_dmabuf_transfer(file, &req); } + /* + * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace + * to configure ZLP behavior immediately without blocking indefinitely + * while waiting for the USB host to connect and enable the endpoint. + */ + case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: + if (!epfile->in) + return -EINVAL; + + if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) + return -EFAULT; + + spin_lock_irq(&epfile->ffs->eps_lock); + epfile->zlp_enabled = !!enable_zlp; + spin_unlock_irq(&epfile->ffs->eps_lock); + + return 0; default: break; } diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index beef1752e36e..06134dca4e8a 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -414,4 +414,27 @@ struct usb_functionfs_event { #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ struct usb_ffs_dmabuf_transfer_req) +/* + * Enable or disable automatic zero-length packet (ZLP) appending for the + * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to + * enable. + * + * When enabled, the kernel will automatically append a ZLP at the end of + * a transfer if the payload length is an exact multiple of the endpoint's + * max packet size. + * + * This is useful for compatibility with legacy protocols which require + * automatic ZLP appending to data written from userspace. + * + * This ioctl can only be used on IN endpoints. It can be called at any time + * after the FunctionFS instance is active, even before the host has connected + * or enabled the endpoint. + * + * Returns zero on success, or a negative errno value on error: + * -ENODEV: The FunctionFS instance is not active. + * -EINVAL: The endpoint is not an IN endpoint. + * -EFAULT: Invalid user space pointer for the argument. + */ +#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ From 0f0ea552cd215a01baca1e99eec02e2f8df0c897 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Fri, 19 Jun 2026 04:06:06 +0000 Subject: [PATCH 033/163] usb: gadget: f_fs: Introduce rw_proxy file descriptors Currently, FunctionFS exposes each USB endpoint as a separate, unidirectional file descriptor (e.g., `ep1` for IN, `ep2` for OUT). While this mirrors the underlying hardware structure, it forces userspace daemons implementing bidirectional protocols to manage multiple file descriptors. When dealing with legacy protocols which require exposing a single, bi-directional fd to userspace, this becomes problematic. This patch introduces the `FUNCTIONFS_RW_PROXY_EPS` UAPI flag. When passed in the descriptor header during initialization, FunctionFS provisions a "rw_proxy" bidirectional file descriptor (e.g., `ep1_rw`) alongside every pair of IN/OUT endpoints. Implementation details: - RW proxy files act as a pure VFS alias, proxying operations directly to the base ffs_epfile instances. A `read()` proxies to the OUT endpoint's file, and a `write()` proxies to the IN file. - Because operations are proxied natively, they reuse the underlying base endpoint's lock (`epfile->mutex`) and tracking state. This serializes concurrent I/O, preventing buffer corruption or races even if userspace mixes transfers across both the rw_proxy and base files while allowing full-duplex synchronous operations to occur concurrently without serializing on a single lock. - Control operations (like IOCTLs) and intentional stalls (via reverse-direction I/O) must still be issued on the base endpoints, as the rw_proxy returns `-ENOTTY` for IOCTLs and cannot trigger stalls. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20260619040609.4010746-5-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/functionfs.rst | 56 +++++++++++++++++++ drivers/usb/gadget/function/f_fs.c | 87 ++++++++++++++++++++++++----- drivers/usb/gadget/function/u_fs.h | 8 ++- include/uapi/linux/usb/functionfs.h | 1 + 4 files changed, 136 insertions(+), 16 deletions(-) diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst index 582e53549d5b..b189cf5626ba 100644 --- a/Documentation/usb/functionfs.rst +++ b/Documentation/usb/functionfs.rst @@ -96,6 +96,58 @@ One such IOCTL is: * ``-ENODEV``: The FunctionFS instance is not active. * ``-EINVAL``: The endpoint is not an IN endpoint. * ``-EFAULT``: Invalid user space pointer for the argument. + +RW Proxy Endpoints +================== + +If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header +(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a +bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair +of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware +endpoints, allowing userspace to use a single file descriptor for both reading +(OUT) and writing (IN). + +This flag requires the total number of hardware endpoints to be an even number. +FunctionFS will automatically walk the provided endpoints and group them into +adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the +second pair). Each pair must consist of exactly one IN endpoint and one OUT +endpoint. + +For each valid pair, a rw_proxy file is created and named after the first +endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are +paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired, +"ep3_rw" is created. + +If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be +named using their physical endpoint address in hexadecimal instead of their +index. RW proxy files will inherit this naming convention. For example, if the +first endpoint of a pair maps to address 0x02, the rw_proxy file will be +named "ep02_rw". + +When this flag is enabled, userspace has the choice of performing data transfers +via the single rw_proxy file descriptor or the two base file descriptors. The +rw_proxy file descriptor acts as a pure VFS alias that proxies all operations +directly to the underlying base file descriptors. + +Because it is a pure proxy, there are no data races or buffer corruptions if +userspace uses both the rw_proxy endpoint and the base endpoints concurrently. +The native mutexes of the base endpoints perfectly serialize all concurrent +transfers. However, userspace should generally pick one method and stick to it +to avoid interleaving its own data stream. + +- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and + will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue + the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base + endpoint file descriptor. +- **Intentional Stalls:** The traditional mechanism for intentionally halting an + endpoint by issuing a reverse-direction data operation (e.g., attempting to + read from an IN endpoint) continues to work, but it must be issued on the + base endpoint. RW proxy endpoints cannot be used to trigger a stall because + they are fully bidirectional. + +Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported +via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF +transfers are required, users must use the standard base endpoints. DMABUF interface ================ @@ -103,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the userspace can attach DMABUF objects (externally created) to an endpoint, and subsequently use them for data transfers. +Note: The DMABUF interface is unsupported on rw_proxy endpoints. See +the RW Proxy Endpoints section for details on using DMABUF alongside +the ``FUNCTIONFS_RW_PROXY_EPS`` flag. + A userspace application can then use this interface to share DMABUF objects between several interfaces, allowing it to transfer data in a zero-copy fashion, for instance between IIO and the USB stack. diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 70e7c18ce062..d7ae4d75d5f5 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -159,7 +159,9 @@ struct ffs_epfile { struct mutex mutex; struct ffs_data *ffs; - struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ /* * Buffer for holding data from partial reads which may happen since @@ -219,12 +221,13 @@ struct ffs_epfile { struct ffs_buffer *read_buffer; #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) - char name[5]; + char name[8]; unsigned char in; /* P: ffs->eps_lock */ unsigned char isoc; /* P: ffs->eps_lock */ u8 zlp_enabled; /* P: ffs->eps_lock */ + bool is_rw_proxy; /* Protects dmabufs */ struct mutex dmabufs_mutex; @@ -978,9 +981,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, return ret; } -static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) +static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) { - struct ffs_epfile *epfile = file->private_data; struct ffs_ep *ep; int ret; @@ -1007,17 +1009,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) char *data = NULL; ssize_t ret, data_len = -EINVAL; int halt; + bool is_rw_proxy = epfile->is_rw_proxy; /* Are we still active? */ if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; - ep = ffs_epfile_wait_ep(file); + /* Proxy to base endpoint if rw_proxy */ + if (is_rw_proxy) + epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; + + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) return PTR_ERR(ep); /* Do we halt? */ - halt = (!io_data->read == !epfile->in); + halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); if (halt && epfile->isoc) return -EINVAL; @@ -1115,7 +1122,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->num_sgs = 0; } - req->zero = epfile->zlp_enabled; + req->zero = !io_data->read ? epfile->zlp_enabled : 0; req->length = data_len; io_data->buf = data; @@ -1168,7 +1175,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->num_sgs = 0; } - req->zero = epfile->zlp_enabled; + req->zero = !io_data->read ? epfile->zlp_enabled : 0; req->length = data_len; io_data->buf = data; @@ -1647,7 +1654,7 @@ static int ffs_dmabuf_transfer(struct file *file, priv = attach->importer_priv; - ep = ffs_epfile_wait_ep(file); + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) { ret = PTR_ERR(ep); goto err_attachment_put; @@ -1765,6 +1772,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; + if (epfile->is_rw_proxy) + return -ENOTTY; + switch (code) { case FUNCTIONFS_DMABUF_ATTACH: { @@ -1815,7 +1825,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, } /* Wait for endpoint to be enabled */ - ep = ffs_epfile_wait_ep(file); + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) return PTR_ERR(ep); @@ -2213,7 +2223,7 @@ static void ffs_data_closed(struct ffs_data *ffs) if (epfiles) ffs_epfiles_destroy(ffs->sb, epfiles, - ffs->eps_count); + ffs->epfiles_count); if (ffs->setup_state == FFS_SETUP_PENDING) __ffs_ep0_stall(ffs); @@ -2271,7 +2281,7 @@ static void ffs_data_clear(struct ffs_data *ffs) * copy of epfile will save us from use-after-free. */ if (epfiles) { - ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); ffs->epfiles = NULL; } @@ -2369,11 +2379,16 @@ static void functionfs_unbind(struct ffs_data *ffs) static int ffs_epfiles_create(struct ffs_data *ffs) { struct ffs_epfile *epfile, *epfiles; - unsigned i, count; + unsigned int i, count, epfiles_count; int err; count = ffs->eps_count; - epfiles = kzalloc_objs(*epfiles, count); + epfiles_count = count; + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) + epfiles_count += count / 2; + ffs->epfiles_count = epfiles_count; + + epfiles = kzalloc_objs(*epfiles, epfiles_count); if (!epfiles) return -ENOMEM; @@ -2395,6 +2410,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) } } + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + struct ffs_epfile *comp = epfiles + count; + + for (i = 0; i < count; i += 2, ++comp) { + struct ffs_epfile *ep1 = &epfiles[i]; + struct ffs_epfile *ep2 = &epfiles[i + 1]; + bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; + + comp->ffs = ffs; + comp->is_rw_proxy = true; + comp->epfile_in = ep1_in ? ep1 : ep2; + comp->epfile_out = ep1_in ? ep2 : ep1; + mutex_init(&comp->mutex); + mutex_init(&comp->dmabufs_mutex); + INIT_LIST_HEAD(&comp->dmabufs); + snprintf(comp->name, sizeof(comp->name), "%s_rw", + epfiles[i].name); + err = ffs_sb_create_file(ffs->sb, comp->name, + comp, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); + return err; + } + } + } + ffs->epfiles = epfiles; return 0; } @@ -2972,7 +3013,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, FUNCTIONFS_VIRTUAL_ADDR | FUNCTIONFS_EVENTFD | FUNCTIONFS_ALL_CTRL_RECIP | - FUNCTIONFS_CONFIG0_SETUP)) { + FUNCTIONFS_CONFIG0_SETUP | + FUNCTIONFS_RW_PROXY_EPS)) { ret = -ENOSYS; goto error; } @@ -3060,6 +3102,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, goto error; } + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + if (ffs->eps_count % 2) { + ret = -EINVAL; + goto error; + } + + for (i = 1; i < ffs->eps_count; i += 2) { + if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == + (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { + ret = -EINVAL; + goto error; + } + } + } + ffs->raw_descs_data = _data; ffs->raw_descs = raw_descs; ffs->raw_descs_length = data - raw_descs; diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 6a80182aadd7..c280c495fbd2 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -252,8 +252,14 @@ struct ffs_data { unsigned short strings_count; unsigned short interfaces_count; + + /* + * eps_count tracks the number of underlying hardware endpoints. + * epfiles_count tracks the total number of VFS endpoint files. + * When companion endpoints are active, epfiles_count > eps_count. + */ unsigned short eps_count; - unsigned short _pad1; + unsigned short epfiles_count; /* filled by __ffs_data_got_strings() */ /* ids in stringtabs are set in functionfs_bind() */ diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 06134dca4e8a..290308c9dd92 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -25,6 +25,7 @@ enum functionfs_flags { FUNCTIONFS_EVENTFD = 32, FUNCTIONFS_ALL_CTRL_RECIP = 64, FUNCTIONFS_CONFIG0_SETUP = 128, + FUNCTIONFS_RW_PROXY_EPS = 256, }; /* Descriptor of an non-audio endpoint */ From 057df423bf4747091cf942af57307627e941e1fb Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:55:21 +0100 Subject: [PATCH 034/163] usb_string_copy: Use kzalloc() to avoid leaking old data If the string is read while being updated (which is why the copy is done in place) and the new string is longer than the old one, then the reader can read memory that isnt part of either string. Use memcpy() to copy the known length string instead of strcpy. Signed-off-by: David Laight Link: https://patch.msgid.link/20260608095523.2606-37-david.laight.linux@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 183a25f65ac8..4518dc6bb5af 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy) if (copy) { str = copy; } else { - str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); if (!str) return -ENOMEM; } - strcpy(str, s); + memcpy(str, s, ret + 1); if (str[ret - 1] == '\n') str[ret - 1] = '\0'; *s_copy = str; From f63edb54d8f738f9c21e2068c777ae1c097df6b7 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 17 Jun 2026 20:50:43 -0400 Subject: [PATCH 035/163] usb: gadget: configfs: fix out-of-bounds read of qw_sign os_desc_qw_sign_show() passes OS_STRING_QW_SIGN_LEN as the input length to utf16s_to_utf8s(), but that argument counts UTF-16 code units while OS_STRING_QW_SIGN_LEN (14) is the byte size of qw_sign[]. The array holds only OS_STRING_QW_SIGN_LEN / 2 (7) code units, so the conversion reads up to 7 units (14 bytes) past the end of qw_sign[] into the following members of struct gadget_info when the stored signature fills the array without a NUL terminator, exposing those bytes through the configfs attribute. The store path halves the count for its input bound but passes the full byte count as the utf8s_to_utf16s() output limit; use the destination code-unit count in both directions. Fixes: 76180d716f91 ("usb: gadget: configfs: make qw_sign attribute symmetric") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260618005043.1581707-1-michael.bommarito@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 4518dc6bb5af..51df6d1d1487 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1177,7 +1177,7 @@ static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) struct gadget_info *gi = os_desc_item_to_gadget_info(item); int res; - res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN, + res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN / 2, UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1); page[res++] = '\n'; @@ -1199,7 +1199,7 @@ static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page, mutex_lock(&gi->lock); res = utf8s_to_utf16s(page, l, UTF16_LITTLE_ENDIAN, (wchar_t *) gi->qw_sign, - OS_STRING_QW_SIGN_LEN); + OS_STRING_QW_SIGN_LEN / 2); if (res > 0) res = len; mutex_unlock(&gi->lock); From 29741ca40b7b780ba14c4c3ffef190f908864fe9 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 16 Jun 2026 16:54:39 +0800 Subject: [PATCH 036/163] usb: typec: tcpm: fix EPR AVS APDO maximum voltage decoding pdo_epr_avs_apdo_max_voltage_mv() extracts the EPR AVS minimum-voltage field instead of the maximum-voltage field. As a result, an EPR AVS APDO with different minimum and maximum voltages is decoded as having identical limits. The currently visible effect is that tcpm_log_source_caps() reports a min-min voltage range. Extract PDO_EPR_AVS_APDO_MAX_VOLT in the maximum-voltage accessor. Fixes: f82890c98f3e ("tcpm: Parse and log AVS APDO") Signed-off-by: Xu Rao Link: https://patch.msgid.link/48301FCEC9F3CA14+20260616085439.987664-1-raoxu@uniontech.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 337a5485af7c..ee360dedeaa6 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -493,7 +493,7 @@ static inline unsigned int pdo_epr_avs_apdo_min_voltage_mv(u32 pdo) static inline unsigned int pdo_epr_avs_apdo_max_voltage_mv(u32 pdo) { - return FIELD_GET(PDO_EPR_AVS_APDO_MIN_VOLT, pdo) * 100; + return FIELD_GET(PDO_EPR_AVS_APDO_MAX_VOLT, pdo) * 100; } static inline unsigned int pdo_epr_avs_apdo_pdp_w(u32 pdo) From 6c562228d0ef75168ce9e310bc8c57d555317e29 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sat, 13 Jun 2026 16:51:52 -0700 Subject: [PATCH 037/163] usb: gadget: nokia: correct CONFIG_USB_GADGET_DEBUG_FILES macro name in comment A comment in drivers/usb/gadget/legacy/nokia.c incorrectly refers to CONFIG_USB_DEBUG instead of CONFIG_USB_GADGET_DEBUG_FILES. Correct it. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Link: https://patch.msgid.link/20260613235156.164531-1-enelsonmoore@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/nokia.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c index 2e15f9a32ce9..396605a84cd1 100644 --- a/drivers/usb/gadget/legacy/nokia.c +++ b/drivers/usb/gadget/legacy/nokia.c @@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; */ #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS -#endif /* CONFIG_USB_DEBUG */ +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); From 7a0d4f60da261f61dcb50c8e05fa53cc7d4d4427 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 11 Jun 2026 17:12:28 +0800 Subject: [PATCH 038/163] usb: gadget: u_audio: clean up capture endpoint on feedback failure u_audio_start_capture() enables the capture OUT endpoint, queues capture requests and marks the stream active before setting up the optional feedback endpoint. If feedback endpoint configuration or enablement fails, the function returns an error while the capture endpoint remains enabled and its requests may remain queued. The current code even leaves TODO comments at these return paths. Unwind the already started capture endpoint on these failures. Also set fb_ep_enabled only after usb_ep_enable() succeeds, so the software state matches the endpoint state. Signed-off-by: Xu Rao Link: https://patch.msgid.link/183621D513E0DE8B+20260611091229.4017443-1-raoxu@uniontech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index e53f2927b539..ca26bf9c8040 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev) ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); if (ret < 0) { dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); - return ret; // TODO: Clean up out_ep + goto err_out_ep; } - prm->fb_ep_enabled = true; ret = usb_ep_enable(ep_fback); if (ret < 0) { dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); - return ret; // TODO: Clean up out_ep + goto err_out_ep; } + prm->fb_ep_enabled = true; req_len = ep_fback->maxpacket; req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); @@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return 0; + +err_out_ep: + set_active(prm, false); + free_ep(prm, ep); + + return ret; } EXPORT_SYMBOL_GPL(u_audio_start_capture); From dd9483726d0f16c1a56879c3edb65128259a4e2b Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Thu, 18 Jun 2026 14:19:48 +0800 Subject: [PATCH 039/163] usb: ljca: bound bank_num in ljca_enumerate_gpio() ljca_enumerate_gpio() reads desc->bank_num from the device and loops valid_pin[i] = get_unaligned_le32(...) for i < bank_num. valid_pin[] holds only LJCA_MAX_GPIO_NUM / 32 = 2 entries. Two checks run before the loop. The reply length must match struct_size(desc, bank_desc, bank_num). The product pins_per_bank * bank_num must not exceed LJCA_MAX_GPIO_NUM. Neither one bounds bank_num against the size of valid_pin[]. The reply is capped at LJCA_MAX_PAYLOAD_SIZE (60) bytes, so the struct_size check limits bank_num to 9. A device that reports bank_num 9 with pins_per_bank 7 still passes both checks. gpio_num is 63 and the reply is 56 bytes. The loop then writes nine u32 into the two entry array and overruns valid_pin[] on the stack. A broken or malicious LJCA device can therefore overflow the stack. Reject a bank_num that does not fit valid_pin[]. Fixes: acd6199f195d ("usb: Add support for Intel LJCA device") Signed-off-by: Maoyi Xie Acked-by: Sakari Ailus Link: https://patch.msgid.link/178176358875.3352358.6059116660356914900@maoyixie.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usb-ljca.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c index c60121faa3da..a1876c05b7c2 100644 --- a/drivers/usb/misc/usb-ljca.c +++ b/drivers/usb/misc/usb-ljca.c @@ -596,6 +596,9 @@ static int ljca_enumerate_gpio(struct ljca_adapter *adap) if (gpio_num > LJCA_MAX_GPIO_NUM) return -EINVAL; + if (desc->bank_num > ARRAY_SIZE(valid_pin)) + return -EINVAL; + /* construct platform data */ gpio_info = kzalloc_obj(*gpio_info); if (!gpio_info) From 612551bf7cf7113b8bbd5e8c1eea86d9427c2f95 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sat, 20 Jun 2026 20:06:31 +0800 Subject: [PATCH 040/163] usb: fsl_qe_udc: check qe_alloc_request() failure in ch9getstatus() qe_alloc_request() may return NULL on allocation failure. ch9getstatus() passes the return value directly to container_of() and then immediately dereferences the resulting qe_req pointer. Check the allocation result before using it and stall the control request on failure. Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20260620120631.2894977-1-haoxiang_li2024@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_qe_udc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c index bf87285ad13c..603c77ff129f 100644 --- a/drivers/usb/gadget/udc/fsl_qe_udc.c +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, u16 index, u16 length) { u16 usb_status = 0; + struct usb_request *usb_req; struct qe_req *req; struct qe_ep *ep; int status = 0; @@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, } } - req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), - struct qe_req, req); + usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); + if (!usb_req) + goto stall; + + req = container_of(usb_req, struct qe_req, req); req->req.length = 2; req->req.buf = udc->statusbuf; *(u16 *)req->req.buf = cpu_to_le16(usb_status); From 83f0355cf48e4e69bf6d1b9776650ce2275f8ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Moreira?= Date: Tue, 23 Jun 2026 23:35:31 -0300 Subject: [PATCH 041/163] usb: core: devio: validate device and interface before buffer allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The proc_ioctl() function currently allocates a buffer using kmalloc() before checking the USB device state and resolving the interface number. If either validation fails, the function must free the buffer and return an error. Move these checks to the top of the function to fail early. This avoids unnecessary memory allocation and deallocation on error paths, removes the nested 'else' structure, and eliminates redundant kfree() calls, making the code cleaner and easier to maintain. Signed-off-by: André Moreira Link: https://patch.msgid.link/20260624023532.63009-1-andrem.33333@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index e191934623c7..8329d1c7d1b2 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) if (!connected(ps)) return -ENODEV; + if (ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + + intf = usb_ifnum_to_if(ps->dev, ctl->ifno); + if (!intf) + return -EINVAL; + /* alloc buffer */ size = _IOC_SIZE(ctl->ioctl_code); if (size > 0) { @@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) } } - if (ps->dev->state != USB_STATE_CONFIGURED) - retval = -EHOSTUNREACH; - else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) - retval = -EINVAL; - else switch (ctl->ioctl_code) { + switch (ctl->ioctl_code) { /* disconnect kernel driver from interface */ case USBDEVFS_DISCONNECT: From 97cee53a94be3bd4fd8fbed6071bd2f32dad1ab1 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Wed, 10 Jun 2026 20:10:22 +0800 Subject: [PATCH 042/163] usb: gadget: aspeed_udc: check endpoint DMA allocation ast_udc_probe() allocates a coherent DMA buffer used as the backing store for endpoint buffers. ast_udc_init_ep() derives per-endpoint buffer pointers from udc->ep0_buf, so a failed allocation is dereferenced during probe. Check the allocation before endpoint setup. The existing probe error path called ast_udc_remove(), which unregisters the gadget unconditionally and is not safe before usb_add_gadget_udc() succeeds. Add a local cleanup helper for probe failures so pre-registration failures only unwind the resources that were actually initialized. This was found by a local static analysis checker for unchecked allocator returns while scanning Linux 6.16. The change was checked by applying it to current mainline and by running checkpatch. I do not have access to Aspeed UDC hardware, so no runtime testing was performed. Fixes: 055276c13205 ("usb: gadget: add Aspeed ast2600 udc driver") Signed-off-by: Ruoyu Wang Reviewed-by: Andrew Jeffery Link: https://patch.msgid.link/20260610121022.3-1-ruoyuw560@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed_udc.c | 50 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 75f9c831b21a..54f81e668009 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -1431,25 +1431,12 @@ static void ast_udc_init_hw(struct ast_udc_dev *udc) ast_udc_write(udc, 0, AST_UDC_EP0_CTRL); } -static void ast_udc_remove(struct platform_device *pdev) +static void ast_udc_cleanup(struct platform_device *pdev) { struct ast_udc_dev *udc = platform_get_drvdata(pdev); unsigned long flags; u32 ctrl; - usb_del_gadget_udc(&udc->gadget); - if (udc->driver) { - /* - * This is broken as only some cleanup is skipped, *udev is - * freed and the register mapping goes away. Any further usage - * probably crashes. Also the device is unbound, so the skipped - * cleanup is never catched up later. - */ - dev_alert(&pdev->dev, - "Driver is busy and still going away. Fasten your seat belts!\n"); - return; - } - spin_lock_irqsave(&udc->lock, flags); /* Disable upstream port connection */ @@ -1469,6 +1456,26 @@ static void ast_udc_remove(struct platform_device *pdev) udc->ep0_buf = NULL; } +static void ast_udc_remove(struct platform_device *pdev) +{ + struct ast_udc_dev *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) { + /* + * This is broken as only some cleanup is skipped, *udev is + * freed and the register mapping goes away. Any further usage + * probably crashes. Also the device is unbound, so the skipped + * cleanup is never catched up later. + */ + dev_alert(&pdev->dev, + "Driver is busy and still going away. Fasten your seat belts!\n"); + return; + } + + ast_udc_cleanup(pdev); +} + static int ast_udc_probe(struct platform_device *pdev) { enum usb_device_speed max_speed; @@ -1521,6 +1528,12 @@ static int ast_udc_probe(struct platform_device *pdev) AST_UDC_NUM_ENDPOINTS, &udc->ep0_buf_dma, GFP_KERNEL); + if (!udc->ep0_buf) { + clk_disable_unprepare(udc->clk); + rc = -ENOMEM; + goto err; + } + udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.max_speed = USB_SPEED_HIGH; udc->creq = udc->reg + AST_UDC_SETUP0; @@ -1550,20 +1563,20 @@ static int ast_udc_probe(struct platform_device *pdev) udc->irq = platform_get_irq(pdev, 0); if (udc->irq < 0) { rc = udc->irq; - goto err; + goto err_cleanup; } rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0, KBUILD_MODNAME, udc); if (rc) { dev_err(&pdev->dev, "Failed to request interrupt\n"); - goto err; + goto err_cleanup; } rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget); if (rc) { dev_err(&pdev->dev, "Failed to add gadget udc\n"); - goto err; + goto err_cleanup; } dev_info(&pdev->dev, "Initialized udc in USB%s mode\n", @@ -1571,9 +1584,10 @@ static int ast_udc_probe(struct platform_device *pdev) return 0; +err_cleanup: + ast_udc_cleanup(pdev); err: dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc); - ast_udc_remove(pdev); return rc; } From babf965320a6be98624ab725c196c47422f1de2c Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 30 Jun 2026 12:51:44 -0700 Subject: [PATCH 043/163] usb: fsl-mph-dr-of: add regulator support Some devices have a GPIO that controls power to the USB bus. Add support for a vbus regulator to have the kernel control it automatically instead of having to rely on userspace. Acquire the regulator in the common probe path so that it works for all fsl-usb2-dr compatible controllers, not just MPC5121. Tested on a TP-LINK WDR4900v1 by adding roughly the following reg_power_usb: regulator { compatible = "regulator-fixed"; regulator-name = "power_usb"; regulator-min-microvolt = <5000000>; regulator-max-microvolt = <5000000>; gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>; enable-active-high; regulator-boot-on; }; uhubctl and rmmod both turn USB power off. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260630195144.88122-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fsl-mph-dr-of.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c index edfb63543029..7d46586e2e21 100644 --- a/drivers/usb/host/fsl-mph-dr-of.c +++ b/drivers/usb/host/fsl-mph-dr-of.c @@ -15,6 +15,7 @@ #include #include #include +#include struct fsl_usb2_dev_data { char *dr_mode; /* controller mode */ @@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) const struct of_device_id *match; const unsigned char *prop; static unsigned int idx; - int i; + int i, err; if (!of_device_is_available(np)) return -ENODEV; @@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) } } + err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); + if (err) + return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { if (!dev_data->drivers[i]) continue; From bf0bff4db5d95c8c207f0907828e6fcd53b11aac Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 25 Jun 2026 11:17:45 +0200 Subject: [PATCH 044/163] dt-bindings: usb: generic-ehci: Document clock-names in top-level Convention is to always have properties defined in top-level part of the binding and then customized (narrowed per variant) in "if:then:" blocks. The clock-names were mentioned only in such "if:then:" block for atmel,at91sam9g45-ehci, thus add the top-level part and disallow usage of clock-names for other devices. This has no practical impact as clock-names are not used by other variants, except in hisilicon/hi3798cv200.dtsi, but that SoC has it undocumented. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260625091744.109467-3-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-ehci.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 55a5aa7d7a54..ae9fb70d0212 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -98,6 +98,10 @@ properties: - if a USB DRD channel: first clock should be host and second one should be peripheral + clock-names: + minItems: 1 + maxItems: 4 + power-domains: maxItems: 1 @@ -186,6 +190,9 @@ allOf: required: - clocks - clock-names + else: + properties: + clock-names: false unevaluatedProperties: false From 713de18af511d5befff5f5b56f66c9882a3aecca Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 25 Jun 2026 11:17:46 +0200 Subject: [PATCH 045/163] dt-bindings: usb: generic-ohci: Document clock-names in top-level Convention is to always have properties defined in top-level part of the binding and then customized (narrowed per variant) in "if:then:" blocks. The clock-names were mentioned only in such "if:then:" block for atmel,at91rm9200-ohci, thus add the top-level part and disallow usage of clock-names for other devices. This has no practical impact as clock-names are not used by other variants, except in hisilicon/hi3798cv200.dtsi, but that SoC has it undocumented. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260625091744.109467-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-ohci.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index d42f448fa204..322808aaa283 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -83,6 +83,10 @@ properties: - if a USB DRD channel: first clock should be host and second one should be peripheral + clock-names: + minItems: 1 + maxItems: 4 + power-domains: maxItems: 1 @@ -182,6 +186,7 @@ allOf: else: properties: + clock-names: false atmel,vbus-gpio: false atmel,oc-gpio: false From 14d2ac442d660e112efc0ce87ad10085013ed2b1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:40 +0100 Subject: [PATCH 046/163] wifi: ath9k_htc: don't store usb_device_id usb_device_id is not guaranteed to live longer than probe due to presence of dynamic ID. All information apart from driver_data can be easily retrieved from usb_device, so just store driver_data. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-1-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/ath/ath9k/hif_usb.c | 12 ++++++------ drivers/net/wireless/ath/ath9k/hif_usb.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index 515267f48d80..a1fa900e21c4 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -1087,7 +1087,7 @@ static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev) } kfree(buf); - if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + if (IS_AR7010_DEVICE(hif_dev->id_info)) firm_offset = AR7010_FIRMWARE_TEXT; else firm_offset = AR9271_FIRMWARE_TEXT; @@ -1182,7 +1182,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev, if (MAJOR_VERSION_REQ == 1 && hif_dev->fw_minor_index == 3) { const char *filename; - if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + if (IS_AR7010_DEVICE(hif_dev->id_info)) filename = FIRMWARE_AR7010_1_1; else filename = FIRMWARE_AR9271; @@ -1198,7 +1198,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev, return -ENOENT; } else { - if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + if (IS_AR7010_DEVICE(hif_dev->id_info)) chip = "7010"; else chip = "9271"; @@ -1260,9 +1260,9 @@ static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context) ret = ath9k_htc_hw_init(hif_dev->htc_handle, &hif_dev->interface->dev, - hif_dev->usb_device_id->idProduct, + le16_to_cpu(hif_dev->udev->descriptor.idProduct), hif_dev->udev->product, - hif_dev->usb_device_id->driver_info); + hif_dev->id_info); if (ret) { ret = -EINVAL; goto err_htc_hw_init; @@ -1374,7 +1374,7 @@ static int ath9k_hif_usb_probe(struct usb_interface *interface, hif_dev->udev = udev; hif_dev->interface = interface; - hif_dev->usb_device_id = id; + hif_dev->id_info = id->driver_info; #ifdef CONFIG_PM udev->reset_resume = 1; #endif diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.h b/drivers/net/wireless/ath/ath9k/hif_usb.h index dc0b0fa5c325..b3e7b0fb54b8 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.h +++ b/drivers/net/wireless/ath/ath9k/hif_usb.h @@ -115,7 +115,7 @@ struct cmd_buf { struct hif_device_usb { struct usb_device *udev; struct usb_interface *interface; - const struct usb_device_id *usb_device_id; + int id_info; const void *fw_data; size_t fw_size; struct completion fw_done; From fc045acec1a501c97f84ae184aadce9dae4ba9b2 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:41 +0100 Subject: [PATCH 047/163] usb: usbtmc: don't store usb_device_id usb_device_id is not guaranteed to live longer than probe due to presence of dynamic ID. This stored ID is unused so remove it. Reviewed-by: Manuel Ebner Reviewed-by: Danilo Krummrich Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-2-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usbtmc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index af9ae55dae14..51cd9320a736 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -71,7 +71,6 @@ struct usbtmc_dev_capabilities { * allocated for each USBTMC device in the driver's probe function. */ struct usbtmc_device_data { - const struct usb_device_id *id; struct usb_device *usb_dev; struct usb_interface *intf; struct list_head file_list; @@ -2394,7 +2393,6 @@ static int usbtmc_probe(struct usb_interface *intf, return -ENOMEM; data->intf = intf; - data->id = id; data->usb_dev = usb_get_dev(interface_to_usbdev(intf)); usb_set_intfdata(intf, data); kref_init(&data->kref); From 934e1322f18c1b58bca431c0d5d01e002060c990 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:42 +0100 Subject: [PATCH 048/163] usb: serial: spcp8x5: don't store usb_device_id USB probe functions should not keep usb_device_id for longer than probe due to presence of dynamic ID removal. USB serial does not support ID removal, however in this case only driver_data is ever needed, there is no reason keeping the usb_device_id in the first place, so convert it as well. Reviewed-by: Manuel Ebner Reviewed-by: Danilo Krummrich Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-3-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/spcp8x5.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index c11d64bf08fb..0e7715a02df4 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -133,14 +133,14 @@ struct spcp8x5_private { static int spcp8x5_probe(struct usb_serial *serial, const struct usb_device_id *id) { - usb_set_serial_data(serial, (void *)id); + usb_set_serial_data(serial, (void *)id->driver_info); return 0; } static int spcp8x5_port_probe(struct usb_serial_port *port) { - const struct usb_device_id *id = usb_get_serial_data(port->serial); + unsigned int quirks = (unsigned int)(unsigned long)usb_get_serial_data(port->serial); struct spcp8x5_private *priv; priv = kzalloc_obj(*priv); @@ -148,7 +148,7 @@ static int spcp8x5_port_probe(struct usb_serial_port *port) return -ENOMEM; spin_lock_init(&priv->lock); - priv->quirks = id->driver_info; + priv->quirks = quirks; usb_set_serial_port_data(port, priv); From 91a8c8c718889fc8ccf5c38b750d790e9f36f92d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:43 +0100 Subject: [PATCH 049/163] media: as102: do not rely on id table address comparison The driver info should be retrieved using the driver_info field, not by address comparison. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-4-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/media/usb/as102/as102_usb_drv.c | 75 ++++++++++--------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/drivers/media/usb/as102/as102_usb_drv.c b/drivers/media/usb/as102/as102_usb_drv.c index a11024451ceb..be2f8be560fa 100644 --- a/drivers/media/usb/as102/as102_usb_drv.c +++ b/drivers/media/usb/as102/as102_usb_drv.c @@ -24,37 +24,35 @@ static void as102_usb_stop_stream(struct as102_dev_t *dev); static int as102_open(struct inode *inode, struct file *file); static int as102_release(struct inode *inode, struct file *file); +struct as102_dev_info { + const char *name; + /* + * eLNA configuration: devices built on the reference design work best + * with 0xA0, while custom designs seem to require 0xC0 + */ + uint8_t elna_cfg; +}; + +#define DRIVER_INFO(dev_name, dev_elna_cfg) \ + .driver_info = (kernel_ulong_t)&(const struct as102_dev_info){ \ + .name = (dev_name), \ + .elna_cfg = (dev_elna_cfg), \ + } + static const struct usb_device_id as102_usb_id_table[] = { - { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) }, - { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) }, - { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) }, - { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) }, - { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) }, + { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001), + DRIVER_INFO(AS102_REFERENCE_DESIGN, 0xA0) }, + { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID), + DRIVER_INFO(AS102_PCTV_74E, 0xC0) }, + { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID), + DRIVER_INFO(AS102_ELGATO_EYETV_DTT_NAME, 0xC0) }, + { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID), + DRIVER_INFO(AS102_NBOX_DVBT_DONGLE_NAME, 0xA0) }, + { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID), + DRIVER_INFO(AS102_SKY_IT_DIGITAL_KEY_NAME, 0xA0) }, { } /* Terminating entry */ }; -/* Note that this table must always have the same number of entries as the - as102_usb_id_table struct */ -static const char * const as102_device_names[] = { - AS102_REFERENCE_DESIGN, - AS102_PCTV_74E, - AS102_ELGATO_EYETV_DTT_NAME, - AS102_NBOX_DVBT_DONGLE_NAME, - AS102_SKY_IT_DIGITAL_KEY_NAME, - NULL /* Terminating entry */ -}; - -/* eLNA configuration: devices built on the reference design work best - with 0xA0, while custom designs seem to require 0xC0 */ -static uint8_t const as102_elna_cfg[] = { - 0xA0, - 0xC0, - 0xC0, - 0xA0, - 0xA0, - 0x00 /* Terminating entry */ -}; - struct usb_driver as102_usb_driver = { .name = DRIVER_FULL_NAME, .probe = as102_usb_probe, @@ -336,29 +334,18 @@ static int as102_usb_probe(struct usb_interface *intf, { int ret; struct as102_dev_t *as102_dev; - int i; - - /* This should never actually happen */ - if (ARRAY_SIZE(as102_usb_id_table) != - (sizeof(as102_device_names) / sizeof(const char *))) { - pr_err("Device names table invalid size"); - return -EINVAL; - } + const struct as102_dev_info *info = (const struct as102_dev_info *)id->driver_info; as102_dev = kzalloc_obj(struct as102_dev_t); if (as102_dev == NULL) return -ENOMEM; - /* Assign the user-friendly device name */ - for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) { - if (id == &as102_usb_id_table[i]) { - as102_dev->name = as102_device_names[i]; - as102_dev->elna_cfg = as102_elna_cfg[i]; - } - } - - if (as102_dev->name == NULL) + if (info) { + as102_dev->name = info->name; + as102_dev->elna_cfg = info->elna_cfg; + } else { as102_dev->name = "Unknown AS102 device"; + } /* set private callback functions */ as102_dev->bus_adap.ops = &as102_priv_ops; From ce8101c331956bbd3e20681331dfd22eb7c1c1ea Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:44 +0100 Subject: [PATCH 050/163] net: usb: pegasus: don't rely on id table pointer arithmetic The current code is broken when dynamic ID is involved; in such cases usb_device_id parameter of probe lives on the heap and the pointer arithmetic will get an index that is wildly out of bound. Instead of keeping a side table for additional information, use driver_info field of the usb_device_id. The dynamic ID parsing code needs to be updated for this; convert it to just write to the reserved entry for dynamic ID and remove the weird loop. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-5-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/net/usb/pegasus.c | 54 ++++++++++++++++----------------------- drivers/net/usb/pegasus.h | 3 --- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index 8700eeb8e22d..aba1a640fc26 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -43,21 +43,12 @@ static bool loopback; static bool mii_mode; static char *devid; -static struct usb_eth_dev usb_dev_id[] = { -#define PEGASUS_DEV(pn, vid, pid, flags) \ - {.name = pn, .vendor = vid, .device = pid, .private = flags}, -#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \ - PEGASUS_DEV(pn, vid, pid, flags) -#include "pegasus.h" -#undef PEGASUS_DEV -#undef PEGASUS_DEV_CLASS - {NULL, 0, 0, 0}, - {NULL, 0, 0, 0} -}; +static struct usb_eth_dev dynamic_id_info = {}; static struct usb_device_id pegasus_ids[] = { #define PEGASUS_DEV(pn, vid, pid, flags) \ - {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid}, + {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid, \ + .driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}}, /* * The Belkin F8T012xx1 bluetooth adaptor has the same vendor and product * IDs as the Belkin F5D5050, so we need to teach the pegasus driver to @@ -66,7 +57,8 @@ static struct usb_device_id pegasus_ids[] = { */ #define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \ {.match_flags = (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS), \ - .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass}, + .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass, \ + .driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}}, #include "pegasus.h" #undef PEGASUS_DEV #undef PEGASUS_DEV_CLASS @@ -402,12 +394,12 @@ static inline int reset_mac(pegasus_t *pegasus) if (i == REG_TIMEOUT) return -ETIMEDOUT; - if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS || - usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) { + if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS || + le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) { set_register(pegasus, Gpio0, 0x24); set_register(pegasus, Gpio0, 0x26); } - if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_ELCON) { + if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_ELCON) { __u16 auxmode; ret = read_mii_word(pegasus, 3, 0x1b, &auxmode); if (ret < 0) @@ -445,9 +437,9 @@ static int enable_net_traffic(struct net_device *dev, struct usb_device *usb) memcpy(pegasus->eth_regs, data, sizeof(data)); ret = set_registers(pegasus, EthCtrl0, 3, data); - if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS || - usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS2 || - usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) { + if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS || + le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS2 || + le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) { u16 auxmode; ret = read_mii_word(pegasus, 0, 0x1b, &auxmode); if (ret < 0) @@ -1153,7 +1145,7 @@ static int pegasus_probe(struct usb_interface *intf, struct usb_device *dev = interface_to_usbdev(intf); struct net_device *net; pegasus_t *pegasus; - int dev_index = id - pegasus_ids; + const struct usb_eth_dev *info = (const struct usb_eth_dev *)id->driver_info; int res = -ENOMEM; static const u8 bulk_ep_addr[] = { PEGASUS_USB_EP_BULK_IN | USB_DIR_IN, @@ -1178,7 +1170,6 @@ static int pegasus_probe(struct usb_interface *intf, goto out; pegasus = netdev_priv(net); - pegasus->dev_index = dev_index; pegasus->intf = intf; res = alloc_urbs(pegasus); @@ -1206,7 +1197,7 @@ static int pegasus_probe(struct usb_interface *intf, pegasus->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK); - pegasus->features = usb_dev_id[dev_index].private; + pegasus->features = info ? info->private : DEFAULT_GPIO_RESET; res = get_interrupt_interval(pegasus); if (res) goto out2; @@ -1235,7 +1226,7 @@ static int pegasus_probe(struct usb_interface *intf, queue_delayed_work(system_long_wq, &pegasus->carrier_check, CARRIER_CHECK_DELAY); dev_info(&intf->dev, "%s, %s, %pM\n", net->name, - usb_dev_id[dev_index].name, net->dev_addr); + info ? info->name : "(unknown)", net->dev_addr); return 0; out3: @@ -1325,8 +1316,9 @@ static struct usb_driver pegasus_driver = { static void __init parse_id(char *id) { - unsigned int vendor_id = 0, device_id = 0, flags = 0, i = 0; + unsigned int vendor_id = 0, device_id = 0, flags = 0; char *token, *name = NULL; + int dyn_id_index = ARRAY_SIZE(pegasus_ids) - 2; token = strsep(&id, ":"); if (token) @@ -1348,14 +1340,12 @@ static void __init parse_id(char *id) if (device_id > 0x10000 || device_id == 0) return; - for (i = 0; usb_dev_id[i].name; i++); - usb_dev_id[i].name = name; - usb_dev_id[i].vendor = vendor_id; - usb_dev_id[i].device = device_id; - usb_dev_id[i].private = flags; - pegasus_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; - pegasus_ids[i].idVendor = vendor_id; - pegasus_ids[i].idProduct = device_id; + dynamic_id_info.name = name; + dynamic_id_info.private = flags; + pegasus_ids[dyn_id_index].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + pegasus_ids[dyn_id_index].idVendor = vendor_id; + pegasus_ids[dyn_id_index].idProduct = device_id; + pegasus_ids[dyn_id_index].driver_info = (kernel_ulong_t)&dynamic_id_info; } static int __init pegasus_init(void) diff --git a/drivers/net/usb/pegasus.h b/drivers/net/usb/pegasus.h index a05b143155ba..ccdedcef52e7 100644 --- a/drivers/net/usb/pegasus.h +++ b/drivers/net/usb/pegasus.h @@ -85,7 +85,6 @@ typedef struct pegasus { unsigned features; u32 msg_enable; u32 wolopts; - int dev_index; int intr_interval; struct tasklet_struct rx_tl; struct delayed_work carrier_check; @@ -102,8 +101,6 @@ typedef struct pegasus { struct usb_eth_dev { char *name; - __u16 vendor; - __u16 device; __u32 private; /* LSB is gpio reset value */ }; From eb6cd6d3d8abeac5d7e8251b898067184afdad8a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:45 +0100 Subject: [PATCH 051/163] usb: xusbatm: don't rely on id table pointer arithmetic The current code is broken when dynamic ID is involved; in such cases usb_device_id parameter of probe lives on the heap and the pointer arithmetic will get an index that is wildly out of bound. xusbatm initialize the USB device IDs dynamically so it can just use driver_info too. Even with conversion, xusbatm still cannot support dynamic IDs, so also set no_dynamic_id. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-6-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/xusbatm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c index 0befbf63d1cc..5c1e1f521555 100644 --- a/drivers/usb/atm/xusbatm.c +++ b/drivers/usb/atm/xusbatm.c @@ -79,7 +79,7 @@ static int xusbatm_bind(struct usbatm_data *usbatm, struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *usb_dev = interface_to_usbdev(intf); - int drv_ix = id - xusbatm_usb_ids; + int drv_ix = id->driver_info; int rx_alt = rx_altsetting[drv_ix]; int tx_alt = tx_altsetting[drv_ix]; struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]); @@ -168,7 +168,8 @@ static struct usb_driver xusbatm_usb_driver = { .name = xusbatm_driver_name, .probe = xusbatm_usb_probe, .disconnect = usbatm_usb_disconnect, - .id_table = xusbatm_usb_ids + .id_table = xusbatm_usb_ids, + .no_dynamic_id = 1, }; static int __init xusbatm_init(void) @@ -190,6 +191,7 @@ static int __init xusbatm_init(void) xusbatm_usb_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; xusbatm_usb_ids[i].idVendor = vendor[i]; xusbatm_usb_ids[i].idProduct = product[i]; + xusbatm_usb_ids[i].driver_info = i; xusbatm_drivers[i].driver_name = xusbatm_driver_name; xusbatm_drivers[i].bind = xusbatm_bind; From ef8154d8b52d60338c1fd8d793cd8e891c604c14 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 7 Jul 2026 13:26:46 +0100 Subject: [PATCH 052/163] usb: fix UAF when probe runs concurrent to dyn ID removal Dynamic IDs are only guaranteed to be valid when usb_dynids_lock is held, as remove_id_store can free the node. Thus, make a copy in usb_probe_interface. Clarify the documentation that the id parameter is only valid during the probe. USB serial has the same pattern, but it does not need fixing as the IDs cannot be removed via sysfs. Fixes: 0c7a2b72746a ("USB: add remove_id sysfs attr for usb drivers") Signed-off-by: Gary Guo Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/20260707-usb_dyn_id_uaf-v2-7-632dcf3adfba@garyguo.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 12 ++++++++---- include/linux/usb.h | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f63004417058..7f33fe5ba03b 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -228,14 +228,16 @@ static void usb_free_dynids(struct usb_driver *usb_drv) } static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf, - const struct usb_driver *drv) + const struct usb_driver *drv, + struct usb_device_id *id_copy) { struct usb_dynid *dynid; guard(mutex)(&usb_dynids_lock); list_for_each_entry(dynid, &drv->dynids.list, node) { if (usb_match_one_id(intf, &dynid->id)) { - return &dynid->id; + *id_copy = dynid->id; + return id_copy; } } return NULL; @@ -321,6 +323,7 @@ static int usb_probe_interface(struct device *dev) struct usb_interface *intf = to_usb_interface(dev); struct usb_device *udev = interface_to_usbdev(intf); const struct usb_device_id *id; + struct usb_device_id id_copy; int error = -ENODEV; int lpm_disable_error = -ENODEV; @@ -340,7 +343,7 @@ static int usb_probe_interface(struct device *dev) return error; } - id = usb_match_dynamic_id(intf, driver); + id = usb_match_dynamic_id(intf, driver, &id_copy); if (!id) id = usb_match_id(intf, driver->id_table); if (!id) @@ -892,6 +895,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv) struct usb_interface *intf; const struct usb_driver *usb_drv; const struct usb_device_id *id; + struct usb_device_id id_copy; /* device drivers never match interfaces */ if (is_usb_device_driver(drv)) @@ -904,7 +908,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv) if (id) return 1; - id = usb_match_dynamic_id(intf, usb_drv); + id = usb_match_dynamic_id(intf, usb_drv, &id_copy); if (id) return 1; } diff --git a/include/linux/usb.h b/include/linux/usb.h index 25a203ac7a7e..83ff8e2c391d 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1185,7 +1185,8 @@ extern ssize_t usb_show_dynids(struct usb_dynids *dynids, char *buf); * interface. It may also use usb_set_interface() to specify the * appropriate altsetting. If unwilling to manage the interface, * return -ENODEV, if genuine IO errors occurred, an appropriate - * negative errno value. + * negative errno value. The usb_device_id parameter is only valid during + * probe. * @disconnect: Called when the interface is no longer accessible, usually * because its device has been (or is being) disconnected or the * driver module is being unloaded. From 9f66c01d68157c508190b5004e60c81317ac8d1e Mon Sep 17 00:00:00 2001 From: Subasri S Date: Thu, 9 Jul 2026 08:13:33 +0530 Subject: [PATCH 053/163] usb: typec: ucsi: Use %pe to print error pointers Use the %pe format specifier instead of %ld with PTR_ERR() for printing error pointers in ucsi_register_plug(), ucsi_register_cable(), and ucsi_register_partner(). This prints symbolic error names (e.g. -ENOMEM) instead of errno numbers (e.g. -12), making debug logs more readable. This patch fixes coccinelle reported warnings: ./typec/ucsi/ucsi.c:1026:3-10: WARNING: Consider using %pe to print PTR_ERR() ./typec/ucsi/ucsi.c:1156:3-10: WARNING: Consider using %pe to print PTR_ERR() ./typec/ucsi/ucsi.c:967:3-10: WARNING: Consider using %pe to print PTR_ERR() Signed-off-by: Subasri S Link: https://patch.msgid.link/20260709-subasri-usb-ucsi-v1-v1-1-74bed201e489@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 607d662b6cb4..fc3d027ea2bc 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -963,8 +963,8 @@ static int ucsi_register_plug(struct ucsi_connector *con) plug = typec_register_plug(con->cable, &desc); if (IS_ERR(plug)) { dev_err(con->ucsi->dev, - "con%d: failed to register plug (%ld)\n", con->num, - PTR_ERR(plug)); + "con%d: failed to register plug (%pe)\n", con->num, + plug); return PTR_ERR(plug); } @@ -1022,8 +1022,8 @@ static int ucsi_register_cable(struct ucsi_connector *con) cable = typec_register_cable(con->port, &desc); if (IS_ERR(cable)) { dev_err(con->ucsi->dev, - "con%d: failed to register cable (%ld)\n", con->num, - PTR_ERR(cable)); + "con%d: failed to register cable (%pe)\n", con->num, + cable); return PTR_ERR(cable); } @@ -1152,8 +1152,8 @@ static int ucsi_register_partner(struct ucsi_connector *con) partner = typec_register_partner(con->port, &desc); if (IS_ERR(partner)) { dev_err(con->ucsi->dev, - "con%d: failed to register partner (%ld)\n", con->num, - PTR_ERR(partner)); + "con%d: failed to register partner (%pe)\n", con->num, + partner); return PTR_ERR(partner); } From 3a1c90aeb96a2ecd6c60fcb70437ea1e851a5692 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 9 Jul 2026 13:46:41 +0800 Subject: [PATCH 054/163] usb: core: config: fix SS companion log for interrupt endpoints usb_parse_ss_endpoint_companion() clears bmAttributes when it is non-zero for control or interrupt endpoints. The diagnostic message reports Control for control endpoints and Bulk for the other branch. However, the other branch can only be an interrupt endpoint because bulk endpoints are handled by the following else-if branch. Report the endpoint type as Interrupt instead of Bulk to avoid misleading descriptor diagnostics. Acked-by: Mathias Nyman Signed-off-by: Xu Rao Link: https://patch.msgid.link/70C90942668659F9+20260709054641.346797-1-raoxu@uniontech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 45e20c6d76c0..cd3231d21090 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, usb_endpoint_xfer_int(&ep->desc)) && desc->bmAttributes != 0) { dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", - usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", + usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", desc->bmAttributes, cfgno, inum, asnum, ep->desc.bEndpointAddress); ep->ss_ep_comp.bmAttributes = 0; From 5eb5c72c72fef76cb765ef1669b62b6a3ba1bfc8 Mon Sep 17 00:00:00 2001 From: Gabriel Prostitis Date: Mon, 1 Jun 2026 08:44:10 +0200 Subject: [PATCH 055/163] USB: gadget: ffs: fix mm lifetime handling io_data stores a pointer to the submitting task's mm_struct, but does not currently hold a reference to it while async requests are pending. This can result in a use-after-free if the task exits before completion handling finishes. Take a reference with mmgrab() when queuing the read request and release it with mmdrop() on request completion. Reported-by: Gabriel Prostitis Signed-off-by: Gabriel Prostitis Link: https://patch.msgid.link/20260601-mm-uaf-fix-v2-1-3c942a707bce@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index d7ae4d75d5f5..e23cd18016ba 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -869,9 +869,15 @@ static void ffs_user_copy_worker(struct work_struct *work) bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; if (io_data->read && ret > 0) { - kthread_use_mm(io_data->mm); - ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); - kthread_unuse_mm(io_data->mm); + if (mmget_not_zero(io_data->mm)) { + kthread_use_mm(io_data->mm); + ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); + kthread_unuse_mm(io_data->mm); + mmput(io_data->mm); + } else { + ret = -EFAULT; + } + mmdrop(io_data->mm); } io_data->kiocb->ki_complete(io_data->kiocb, ret); @@ -1274,16 +1280,20 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) kiocb->private = p; - if (p->aio) + if (p->aio) { + mmgrab(p->mm); kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } res = ffs_epfile_io(kiocb->ki_filp, p); if (res == -EIOCBQUEUED) return res; - if (p->aio) + if (p->aio) { + mmdrop(p->mm); kfree(p); - else + } else { *from = p->data; + } return res; } @@ -1318,14 +1328,17 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) kiocb->private = p; - if (p->aio) + if (p->aio) { + mmgrab(p->mm); kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } res = ffs_epfile_io(kiocb->ki_filp, p); if (res == -EIOCBQUEUED) return res; if (p->aio) { + mmdrop(p->mm); kfree(p->to_free); kfree(p); } else { From a1c33c80e11ecd5c3310a38c4be324095dd85ac5 Mon Sep 17 00:00:00 2001 From: Gabriel Prostitis Date: Mon, 1 Jun 2026 08:44:11 +0200 Subject: [PATCH 056/163] USB: gadget: inode: fix mm lifetime handling priv stores a pointer to the submitting task's mm_struct, but does not currently hold a reference to it while async requests are pending. This can result in a use-after-free if the task exits before completion handling finishes. Take a reference with mmgrab() when queuing the read request and release it with mmdrop() on request completion. Reported-by: Gabriel Prostitis Signed-off-by: Gabriel Prostitis Acked-by: Alan Stern Link: https://patch.msgid.link/20260601-mm-uaf-fix-v2-2-3c942a707bce@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index d87a8ab51510..db961aaa3740 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) struct kiocb *iocb = priv->iocb; size_t ret; - kthread_use_mm(mm); - ret = copy_to_iter(priv->buf, priv->actual, &priv->to); - kthread_unuse_mm(mm); - if (!ret) + if (mmget_not_zero(mm)) { + kthread_use_mm(mm); + ret = copy_to_iter(priv->buf, priv->actual, &priv->to); + kthread_unuse_mm(mm); + mmput(mm); + if (!ret) + ret = -EFAULT; + } else { ret = -EFAULT; + } + mmdrop(mm); /* completing the iocb can drop the ctx and mm, don't touch mm after */ iocb->ki_complete(iocb, ret); @@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) * complete the aio request immediately. */ if (priv->to_free == NULL || unlikely(req->actual == 0)) { + mmdrop(priv->mm); kfree(req->buf); kfree(priv->to_free); kfree(priv); @@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb, priv->epdata = epdata; priv->actual = 0; priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ + mmgrab(priv->mm); /* each kiocb is coupled to one usb_request, but we can't * allocate or submit those if the host disconnected. @@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, fail: spin_unlock_irq(&epdata->dev->lock); + mmdrop(priv->mm); kfree(priv->to_free); kfree(priv); put_ep(epdata); From 99420b253d0b6b42a901c3d5e00f9b50c65d7aa1 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 3 Jun 2026 16:15:45 -0700 Subject: [PATCH 057/163] usb: gadget: pch_udc: remove excess kernel-doc member for registered This is no longer present. Fixes: 5a8a375714d0 ("usb: gadget: pch_udc: let udc-core manage gadget->dev") Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260603231545.7065-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pch_udc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 0a6886428739..99b3ce28210f 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -320,7 +320,6 @@ struct pch_vbus_gpio_data { * @lock: protects all state * @stall: stall requested * @prot_stall: protcol stall requested - * @registered: driver registered with system * @suspended: driver in suspended state * @connected: gadget driver associated * @vbus_session: required vbus_session state From e71b845c1d9e5054172f32da6c8fba2b7cb8e4ca Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 3 Jun 2026 17:05:13 -0700 Subject: [PATCH 058/163] usb: dwc2: add missing @remotewakeup kernel-doc parameter Add the @remotewakeup kernel-doc parameter description to the dwc2_wakeup_from_lpm_l1() function. Fixes: 5d69a3b54e5a ("usb: dwc2: gadget: LPM flow fix") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260604000513.15753-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/core_intr.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 7d3e641806f8..9565e6a52725 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) /** * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state - * * @hsotg: Programming view of DWC_otg controller - * + * @remotewakeup: Whether this is a remote wakeup */ void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) { From ff14cf71c89cbf4cbde9add29dcc72b0fc8cd88f Mon Sep 17 00:00:00 2001 From: Sardaruddin Syed Date: Sat, 30 May 2026 23:34:07 +0000 Subject: [PATCH 059/163] Documentation: ABI: remove outdated USB power/level removal notice The sysfs power/level interface is still implemented and documented despite the removal notice stating it would be removed after 2010. Remove the outdated removal timeline while keeping the deprecation notice and recommendation to use power/control instead. Signed-off-by: Sardaruddin Syed Link: https://patch.msgid.link/20260530233410.1718-1-ssardaruddin2002@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/obsolete/sysfs-bus-usb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/obsolete/sysfs-bus-usb b/Documentation/ABI/obsolete/sysfs-bus-usb index bd096d33fbc7..067016e62e11 100644 --- a/Documentation/ABI/obsolete/sysfs-bus-usb +++ b/Documentation/ABI/obsolete/sysfs-bus-usb @@ -26,6 +26,6 @@ Description: initializes all non-hub devices in the "on" level. Some drivers may change this setting when they are bound. - This file is deprecated and will be removed after 2010. + This file is deprecated. Use the power/control file instead; it does exactly the same thing. From e69027c25361b6044c7928715667586cc5469063 Mon Sep 17 00:00:00 2001 From: Fei Shao Date: Fri, 26 Jun 2026 16:21:51 +0800 Subject: [PATCH 060/163] usb: mtu3: allow system suspend during active gadget connection When operating in gadget mode connected to a USB host, system suspend fails with -EBUSY because active peripheral connections block suspend entry. Fix this by restricting the -EBUSY check to runtime autosuspend (PMSG_IS_AUTO). For system suspend (!PMSG_IS_AUTO), perform soft disconnect to disconnect from the bus and allow MAC sleep. Fixes: 427c66422e14 ("usb: mtu3: support suspend/resume for device mode") Signed-off-by: Fei Shao Link: https://patch.msgid.link/20260626082218.2750459-2-fshao@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_core.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index 66dbfe1705d5..a40bf5bad2d5 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -1037,9 +1037,14 @@ int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) if (!mtu->gadget_driver) return 0; - if (mtu->connected) + /* Prevent runtime suspend when active connection exists */ + if (mtu->connected && PMSG_IS_AUTO(msg)) return -EBUSY; + /* Perform soft disconnect for system suspend */ + if (mtu->softconnect && !PMSG_IS_AUTO(msg)) + mtu3_dev_on_off(mtu, 0); + mtu3_dev_suspend(mtu); synchronize_irq(mtu->irq); @@ -1055,5 +1060,9 @@ int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) mtu3_dev_resume(mtu); + /* Restore soft connect for system resume */ + if (mtu->softconnect && !PMSG_IS_AUTO(msg)) + mtu3_dev_on_off(mtu, 1); + return 0; } From 2fe9a76eb3c8ec5a6d2d281740bad580eb53a903 Mon Sep 17 00:00:00 2001 From: Fei Shao Date: Fri, 26 Jun 2026 16:21:52 +0800 Subject: [PATCH 061/163] usb: mtu3: condition PHY wakeup for host mode and runtime suspend Host bus activity during gadget mode system suspend can trigger unexpected system wakeups via interrupts because PHY wakeup is enabled unconditionally. Improve the suspend flow by conditioning PHY wakeup setup on device_may_wakeup() and restricting it to host mode or runtime suspend to ensure proper wakeup handling in gadget mode. Signed-off-by: Fei Shao Link: https://patch.msgid.link/20260626082218.2750459-3-fshao@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_plat.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index cc8a864dbd63..b4f4c776adb9 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) ssusb_phy_power_off(ssusb); clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); - ssusb_wakeup_set(ssusb, true); + if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) + ssusb_wakeup_set(ssusb, true); return 0; sleep_err: From 589b9e6f96be6bd8dd0d45fda8e948c31dc2fe94 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 2 Jul 2026 08:38:29 +0100 Subject: [PATCH 062/163] usb: renesas_usbhs: Fix power-off ordering on unbind Move the usbhsc_power_ctrl() call to before hardware_exit() and reset_control_assert() in usbhs_remove(), so the PHY is powered off while priv->phy is still valid, rather than after hardware_exit() has already cleared it. Fixes: eb9ac779830b ("usb: renesas_usbhs: Fix synchronous external abort on unbind") Signed-off-by: Biju Das Link: https://patch.msgid.link/20260702073832.175047-1-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 8c93bde4b816..51d3035f82be 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -813,9 +813,6 @@ static void usbhs_remove(struct platform_device *pdev) flush_delayed_work(&priv->notify_hotplug_work); - usbhs_platform_call(priv, hardware_exit, pdev); - reset_control_assert(priv->rsts); - /* * Explicitly free the IRQ to ensure the interrupt handler is * disabled and synchronized before freeing resources. @@ -832,6 +829,9 @@ static void usbhs_remove(struct platform_device *pdev) if (!usbhs_get_dparam(priv, runtime_pwctrl)) usbhsc_power_ctrl(priv, 0); + usbhs_platform_call(priv, hardware_exit, pdev); + reset_control_assert(priv->rsts); + usbhsc_clk_put(priv); pm_runtime_disable(&pdev->dev); } From a71f5aef1be98fb10f00e66a0ac4ef23ba9c877f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:16:51 +0200 Subject: [PATCH 063/163] USB: gadget: fsl-udc: enable compile testing Nothing seems to prevent this driver from being compile tested so enable that for wider build coverage. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141651.91003-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 26460340fbc9..7ddbc2606686 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -90,7 +90,7 @@ config USB_BCM63XX_UDC config USB_FSL_USB2 tristate "Freescale Highspeed USB DR Peripheral Controller" - depends on FSL_SOC + depends on FSL_SOC || COMPILE_TEST help Some of Freescale PowerPC and i.MX processors have a High Speed Dual-Role(DR) USB controller, which supports device mode. From e2467aabbfe14a4d6d4d40b58153ca3354606d9f Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 8 Jul 2026 07:49:24 +0200 Subject: [PATCH 064/163] docs: admin-guide: thunderbolt: fix sentence structure Replace ')' with ',' and add 'in' to sentence. Fixes: 3fb10ea4ce86 ("thunderbolt: Add support for retimer NVM upgrade when there is no link") Signed-off-by: Manuel Ebner Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260708054923.293003-3-manuelebner@mailbox.org Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/thunderbolt.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 91a6cb109988..ff25fe853706 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -294,8 +294,8 @@ for the retimers:: This enumerates and adds the on-board retimers. Now retimer NVM can be upgraded in the same way than with cable connected (see previous -section). However, the retimer is not disconnected as we are offline -mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for +section). However, the retimer is not disconnected as we are in offline +mode, so after writing ``1`` to ``nvm_authenticate`` one should wait for 5 or more seconds before running rescan again:: # echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan From 2c5659a7064e7c4c0c51eb9356bcf6726a85773b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 9 Jul 2026 20:32:39 +0800 Subject: [PATCH 065/163] usb: typec: ucsi: gaokun: unwind notifier on UCSI register failure gaokun_ucsi_register_worker() registers the EC notifier before calling ucsi_register(). If ucsi_register() fails, the worker currently only logs the error and leaves the notifier registered. Later EC events can then call into an unpublished UCSI instance. The remove path also unconditionally unregisters both the EC notifier and the UCSI device even if the delayed worker failed before both publication steps completed. Unregister the notifier immediately when ucsi_register() fails, and track only the fully published state. The remove path then tears down the pair only if both publication steps completed. Fixes: 00327d7f2c8c ("usb: typec: ucsi: add Huawei Matebook E Go ucsi driver") Reviewed-by: Heikki Krogerus Reviewed-by: Pengyu Luo Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260709123239.62930-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index ad669d2f8b9c..24e859ab2174 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -103,6 +103,7 @@ struct gaokun_ucsi { struct notifier_block nb; u16 version; u8 num_ports; + bool registered; }; /* -------------------------------------------------------------------------- */ @@ -482,8 +483,13 @@ static void gaokun_ucsi_register_worker(struct work_struct *work) } ret = ucsi_register(ucsi); - if (ret) + if (ret) { dev_err_probe(ucsi->dev, ret, "ucsi register failed\n"); + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + return; + } + + uec->registered = true; } static int gaokun_ucsi_probe(struct auxiliary_device *adev, @@ -528,8 +534,11 @@ static void gaokun_ucsi_remove(struct auxiliary_device *adev) int i; disable_delayed_work_sync(&uec->work); - gaokun_ec_unregister_notify(uec->ec, &uec->nb); - ucsi_unregister(uec->ucsi); + if (uec->registered) { + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + ucsi_unregister(uec->ucsi); + } + for (i = 0; i < uec->num_ports; ++i) typec_mux_put(uec->ports[i].typec_mux); From 1db5c6b0b9834aee2f14e39764becfcc29d09ccf Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Fri, 10 Jul 2026 15:08:34 +0800 Subject: [PATCH 066/163] usb: chipidea: imx: fix missing ret assignment for dev_err_probe Assign the return value of dev_err_probe() to ret so that the correct error code is propagated when goto err_clk is taken. Fixes: 2e9762f45efb ("usb: chipidea: ci_hdrc_imx: use "wakeup" suffix for wakeup interrupt name") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202607031656.FR3Xrved-lkp@intel.com/ Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260710070834.2744357-1-xu.yang_2@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 56d2ba824a0b..282314eea7fc 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -528,7 +528,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) if (data->wakeup_irq > 0) { irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name); if (!irq_name) { - dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); + ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); goto err_clk; } From 2ed12514074d06e19fca473eb7e24e9245393db2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 29 Jun 2026 14:37:33 +0200 Subject: [PATCH 067/163] USB: serial: digi_acceleport: do not log stopping of urbs as errors Stopping an urb is not an error and should not be logged as such. Demote the dev_err() in the read bulk completion handler to dev_dbg() when an urb is being unlinked on disconnect. Note that this will become more of an issue when the urbs are stopped every time a port is closed. This issue was flagged by Sashiko when reviewing the upcoming change. Link: https://sashiko.dev/#/patchset/20260623150826.314727-1-johan%40kernel.org?part=2 Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index dea039163661..fbb3609ac014 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1355,7 +1355,17 @@ static void digi_read_bulk_callback(struct urb *urb) } /* do not resubmit urb if it has any status error */ - if (status) { + switch (status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; + default: dev_err(&port->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); From 54ad7212195812e76bf33f561009a8e025bbfbd1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:16 +0200 Subject: [PATCH 068/163] USB: serial: digi_acceleport: fix port registration order The driver submits the read urbs for all ports when the first port is opened, which could happen before the other ports have been probed and their private data set up. If such an urb completes before the port has been probed, the completion handler will not resubmit it, thus preventing any further reads. Fix the ordering issue by not submitting the port read urbs until the port is opened. This also avoids wasting resources (e.g. power) when ports are not in use. Note that the port write urbs are already stopped on close (unless unbinding, but they are also stopped by core on disconnect). Fixes: fb44ff854e14 ("USB: digi_acceleport: fix port-data memory leak") Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 48 +++++++++++----------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index fbb3609ac014..f340c111f171 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1076,7 +1076,6 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); struct ktermios not_termios; - int throttled; /* be sure the device is started up */ if (digi_startup_device(port->serial) != 0) @@ -1106,17 +1105,14 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) } spin_lock_irq(&priv->dp_port_lock); - throttled = priv->dp_throttle_restart; priv->dp_throttled = 0; priv->dp_throttle_restart = 0; spin_unlock_irq(&priv->dp_port_lock); - if (throttled) { - ret = usb_submit_urb(port->read_urb, GFP_KERNEL); - if (ret) { - dev_err(&port->dev, "failed to submit read urb: %d\n", ret); - return ret; - } + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&port->dev, "failed to submit read urb: %d\n", ret); + return ret; } return 0; @@ -1130,6 +1126,8 @@ static void digi_close(struct usb_serial_port *port) unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); + usb_kill_urb(port->read_urb); + mutex_lock(&port->serial->disc_mutex); /* if disconnected, just clear flags */ if (port->serial->disconnected) @@ -1192,15 +1190,15 @@ static void digi_close(struct usb_serial_port *port) /* * Digi Startup Device * - * Starts reads on all ports. Must be called AFTER startup, with + * Starts read on the OOB port. Must be called AFTER startup, with * urbs initialized. Returns 0 if successful, non-zero error otherwise. */ static int digi_startup_device(struct usb_serial *serial) { - int i, ret = 0; struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *port; + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + int ret; /* be sure this happens exactly once */ spin_lock(&serial_priv->ds_serial_lock); @@ -1211,19 +1209,13 @@ static int digi_startup_device(struct usb_serial *serial) serial_priv->ds_device_started = 1; spin_unlock(&serial_priv->ds_serial_lock); - /* start reading from each bulk in endpoint for the device */ - /* set USB_DISABLE_SPD flag for write bulk urbs */ - for (i = 0; i < serial->type->num_ports + 1; i++) { - port = serial->port[i]; - ret = usb_submit_urb(port->read_urb, GFP_KERNEL); - if (ret != 0) { - dev_err(&port->dev, - "%s: usb_submit_urb failed, ret=%d, port=%d\n", - __func__, ret, i); - break; - } + ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ret); + return ret; } - return ret; + + return 0; } static int digi_port_init(struct usb_serial_port *port, unsigned port_num) @@ -1294,13 +1286,11 @@ static int digi_startup(struct usb_serial *serial) static void digi_disconnect(struct usb_serial *serial) { - int i; + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; - /* stop reads and writes on all ports */ - for (i = 0; i < serial->type->num_ports + 1; i++) { - usb_kill_urb(serial->port[i]->read_urb); - usb_kill_urb(serial->port[i]->write_urb); - } + usb_kill_urb(oob_port->read_urb); + usb_kill_urb(oob_port->write_urb); } From 9da927878069208d2f581aa703c45ab013eca685 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:17 +0200 Subject: [PATCH 069/163] USB: serial: digi_acceleport: drop unused wait queue Drop the close wait queue which has not been used since commit 335f8514f200 ("tty: Bring the usb tty port structure into more use"). Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index f340c111f171..b14a8c33d11d 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -194,7 +194,6 @@ struct digi_port { int dp_throttled; int dp_throttle_restart; wait_queue_head_t dp_flush_wait; - wait_queue_head_t dp_close_wait; /* wait queue for close */ wait_queue_head_t write_wait; struct usb_serial_port *dp_port; }; @@ -1181,7 +1180,6 @@ static void digi_close(struct usb_serial_port *port) exit: spin_lock_irq(&priv->dp_port_lock); priv->dp_write_urb_in_use = 0; - wake_up_interruptible(&priv->dp_close_wait); spin_unlock_irq(&priv->dp_port_lock); mutex_unlock(&port->serial->disc_mutex); } @@ -1230,7 +1228,6 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) priv->dp_port_num = port_num; init_waitqueue_head(&priv->dp_transmit_idle_wait); init_waitqueue_head(&priv->dp_flush_wait); - init_waitqueue_head(&priv->dp_close_wait); init_waitqueue_head(&priv->write_wait); priv->dp_port = port; From 5d17fbd6296db34d84aef771c87387c86eae7d2b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:18 +0200 Subject: [PATCH 070/163] USB: serial: digi_acceleport: always stop write urb on close Explicitly stop the write urb on close() also if the device is being unbound instead of relying on core to do it after returning. Note that the dp_write_urb_in_use flag is cleared by the completion handler. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index b14a8c33d11d..1b858b0cc84b 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1128,7 +1128,6 @@ static void digi_close(struct usb_serial_port *port) usb_kill_urb(port->read_urb); mutex_lock(&port->serial->disc_mutex); - /* if disconnected, just clear flags */ if (port->serial->disconnected) goto exit; @@ -1174,14 +1173,11 @@ static void digi_close(struct usb_serial_port *port) TASK_INTERRUPTIBLE); schedule_timeout(DIGI_CLOSE_TIMEOUT); finish_wait(&priv->dp_flush_wait, &wait); +exit: + mutex_unlock(&port->serial->disc_mutex); /* shutdown any outstanding bulk writes */ usb_kill_urb(port->write_urb); -exit: - spin_lock_irq(&priv->dp_port_lock); - priv->dp_write_urb_in_use = 0; - spin_unlock_irq(&priv->dp_port_lock); - mutex_unlock(&port->serial->disc_mutex); } From 1c44f3b971ffa979191f83c98823e1f83f169208 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:19 +0200 Subject: [PATCH 071/163] USB: serial: digi_acceleport: add oob port helper Add a helper function for retrieving the OOB port to replace two convoluted expressions. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 1b858b0cc84b..487816470ceb 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -350,6 +350,13 @@ __releases(lock) return timeout; } +static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + + return serial_priv->ds_oob_port; +} + /* * Digi Write OOB Command * @@ -366,7 +373,7 @@ static int digi_write_oob_command(struct usb_serial_port *port, { int ret = 0; int len; - struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned long flags; @@ -511,7 +518,7 @@ static int digi_set_modem_signals(struct usb_serial_port *port, int ret; struct digi_port *port_priv = usb_get_serial_port_data(port); - struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned char *data = oob_port->write_urb->transfer_buffer; unsigned long flags; From dc2723aab475ebdfa51608670617ef51f71b94e1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:20 +0200 Subject: [PATCH 072/163] USB: serial: digi_acceleport: clean up declarations and whitespace Clean up the driver by moving some declarations to approximate reverse xmas style and removing some stray newlines (and adding a few for readability). While at it, also replace two spaces before tabs in the driver structs. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 88 +++++++++------------------- 1 file changed, 29 insertions(+), 59 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 487816470ceb..21f15e19fae3 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -271,7 +271,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { .dtr_rts = digi_dtr_rts, .write = digi_write, .write_room = digi_write_room, - .write_bulk_callback = digi_write_bulk_callback, + .write_bulk_callback = digi_write_bulk_callback, .read_bulk_callback = digi_read_bulk_callback, .chars_in_buffer = digi_chars_in_buffer, .throttle = digi_rx_throttle, @@ -300,7 +300,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { .close = digi_close, .write = digi_write, .write_room = digi_write_room, - .write_bulk_callback = digi_write_bulk_callback, + .write_bulk_callback = digi_write_bulk_callback, .read_bulk_callback = digi_read_bulk_callback, .chars_in_buffer = digi_chars_in_buffer, .throttle = digi_rx_throttle, @@ -334,7 +334,6 @@ static struct usb_serial_driver * const serial_drivers[] = { * interruptible_sleep_on_timeout is deprecated and has been replaced * with the equivalent code. */ - static long cond_wait_interruptible_timeout_irqrestore( wait_queue_head_t *q, long timeout, spinlock_t *lock, unsigned long flags) @@ -367,15 +366,14 @@ static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) * the interruptible flag is true, or a negative error * returned by usb_submit_urb. */ - static int digi_write_oob_command(struct usb_serial_port *port, unsigned char *buf, int count, int interruptible) { - int ret = 0; - int len; struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned long flags; + int ret = 0; + int len; dev_dbg(&port->dev, "digi_write_oob_command: TOP: port=%d, count=%d\n", @@ -412,10 +410,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", __func__, ret); return ret; - } - /* * Digi Write In Band Command * @@ -427,16 +423,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, * timeout ticks. Returns 0 if successful, or a negative * error returned by digi_write. */ - static int digi_write_inb_command(struct usb_serial_port *port, unsigned char *buf, int count, unsigned long timeout) { - int ret = 0; - int len; struct digi_port *priv = usb_get_serial_port_data(port); unsigned char *data = port->write_urb->transfer_buffer; unsigned long expire; unsigned long flags; + int ret = 0; + int len; dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", priv->dp_port_num, count); @@ -490,7 +485,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, count -= len; buf += len; } - } spin_unlock_irqrestore(&priv->dp_port_lock, flags); @@ -501,7 +495,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, return ret; } - /* * Digi Set Modem Signals * @@ -511,17 +504,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, * -EINTR if interrupted while sleeping, or a non-zero error * returned by usb_submit_urb. */ - static int digi_set_modem_signals(struct usb_serial_port *port, unsigned int modem_signals, int interruptible) { - - int ret; struct digi_port *port_priv = usb_get_serial_port_data(port); struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned char *data = oob_port->write_urb->transfer_buffer; unsigned long flags; + int ret; dev_dbg(&port->dev, "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", @@ -579,14 +570,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, * is only called from close, and only one process can be in close on a * port at a time, so its ok. */ - static int digi_transmit_idle(struct usb_serial_port *port, unsigned long timeout) { - int ret; - unsigned char buf[2]; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[2]; unsigned long flags; + int ret; spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_transmit_idle = 0; @@ -613,16 +603,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, } priv->dp_transmit_idle = 0; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return 0; - } - static void digi_rx_throttle(struct tty_struct *tty) { - unsigned long flags; struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; /* stop receiving characters by not resubmitting the read urb */ spin_lock_irqsave(&priv->dp_port_lock, flags); @@ -631,13 +620,12 @@ static void digi_rx_throttle(struct tty_struct *tty) spin_unlock_irqrestore(&priv->dp_port_lock, flags); } - static void digi_rx_unthrottle(struct tty_struct *tty) { - int ret = 0; - unsigned long flags; struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; + int ret = 0; spin_lock_irqsave(&priv->dp_port_lock, flags); @@ -657,7 +645,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) __func__, ret, priv->dp_port_num); } - static void digi_set_termios(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios) @@ -768,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, /* set stop bits */ if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - if ((cflag & CSTOPB)) arg = DIGI_STOP_BITS_2; else @@ -778,7 +764,6 @@ static void digi_set_termios(struct tty_struct *tty, buf[i++] = priv->dp_port_num; buf[i++] = arg; buf[i++] = 0; - } /* set input flow control */ @@ -847,7 +832,6 @@ static void digi_set_termios(struct tty_struct *tty, tty_encode_baud_rate(tty, baud, baud); } - static int digi_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; @@ -861,43 +845,41 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) return digi_write_inb_command(port, buf, 4, 0); } - static int digi_tiocmget(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - unsigned int val; unsigned long flags; + unsigned int val; spin_lock_irqsave(&priv->dp_port_lock, flags); val = priv->dp_modem_signals; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return val; } - static int digi_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - unsigned int val; unsigned long flags; + unsigned int val; spin_lock_irqsave(&priv->dp_port_lock, flags); val = (priv->dp_modem_signals & ~clear) | set; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return digi_set_modem_signals(port, val, 1); } - static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count) { - - int ret, data_len, new_len; struct digi_port *priv = usb_get_serial_port_data(port); unsigned char *data = port->write_urb->transfer_buffer; + int ret, data_len, new_len; unsigned long flags; dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", @@ -960,21 +942,20 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, "%s: usb_submit_urb failed, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); dev_dbg(&port->dev, "digi_write: returning %d\n", ret); - return ret; + return ret; } static void digi_write_bulk_callback(struct urb *urb) { - struct usb_serial_port *port = urb->context; struct usb_serial *serial; struct digi_port *priv; struct digi_serial *serial_priv; - unsigned long flags; - int ret = 0; int status = urb->status; + unsigned long flags; bool wakeup; + int ret = 0; /* port and serial sanity check */ if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { @@ -1047,8 +1028,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) spin_unlock_irqrestore(&priv->dp_port_lock, flags); dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); - return room; + return room; } static unsigned int digi_chars_in_buffer(struct tty_struct *tty) @@ -1078,10 +1059,10 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) { - int ret; - unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); struct ktermios not_termios; + unsigned char buf[32]; + int ret; /* be sure the device is started up */ if (digi_startup_device(port->serial) != 0) @@ -1124,13 +1105,12 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) return 0; } - static void digi_close(struct usb_serial_port *port) { + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[32]; DEFINE_WAIT(wait); int ret; - unsigned char buf[32]; - struct digi_port *priv = usb_get_serial_port_data(port); usb_kill_urb(port->read_urb); @@ -1187,14 +1167,12 @@ static void digi_close(struct usb_serial_port *port) usb_kill_urb(port->write_urb); } - /* * Digi Startup Device * * Starts read on the OOB port. Must be called AFTER startup, with * urbs initialized. Returns 0 if successful, non-zero error otherwise. */ - static int digi_startup_device(struct usb_serial *serial) { struct digi_serial *serial_priv = usb_get_serial_data(serial); @@ -1283,7 +1261,6 @@ static int digi_startup(struct usb_serial *serial) return 0; } - static void digi_disconnect(struct usb_serial *serial) { struct digi_serial *serial_priv = usb_get_serial_data(serial); @@ -1293,7 +1270,6 @@ static void digi_disconnect(struct usb_serial *serial) usb_kill_urb(oob_port->write_urb); } - static void digi_release(struct usb_serial *serial) { struct digi_serial *serial_priv; @@ -1325,8 +1301,8 @@ static void digi_read_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct digi_port *priv; struct digi_serial *serial_priv; - int ret; int status = urb->status; + int ret; /* port sanity check, do not resubmit if port is not valid */ if (port == NULL) @@ -1378,7 +1354,6 @@ static void digi_read_bulk_callback(struct urb *urb) "%s: failed resubmitting urb, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); } - } /* @@ -1390,7 +1365,6 @@ static void digi_read_bulk_callback(struct urb *urb) * It returns 0 if successful, 1 if successful but the port is * throttled, and -1 if the sanity checks failed. */ - static int digi_read_inb_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; @@ -1468,10 +1442,8 @@ static int digi_read_inb_callback(struct urb *urb) dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); return throttled ? 1 : 0; - } - /* * Digi Read OOB Callback * @@ -1480,10 +1452,8 @@ static int digi_read_inb_callback(struct urb *urb) * the port->serial is valid. It returns 0 if successful, and * -1 if the sanity checks failed. */ - static int digi_read_oob_callback(struct urb *urb) { - struct usb_serial_port *port = urb->context; struct usb_serial *serial = port->serial; struct tty_struct *tty; @@ -1491,8 +1461,8 @@ static int digi_read_oob_callback(struct urb *urb) unsigned char *buf = urb->transfer_buffer; int opcode, line, status, val; unsigned long flags; - int i; unsigned int rts; + int i; if (urb->actual_length < 4) return -1; @@ -1562,8 +1532,8 @@ static int digi_read_oob_callback(struct urb *urb) } tty_kref_put(tty); } - return 0; + return 0; } module_usb_serial_driver(serial_drivers, id_table_combined); From 58ef164543a4b7b0d4b704989fc580d1260fc058 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:21 +0200 Subject: [PATCH 073/163] USB: serial: digi_acceleport: drop redundant driver data sanity checks The urb context pointer does not change while an urb is in flight so there is never a need to check for NULL on completion. The port driver data is not freed until the port is unbound at which point all I/O for that port has been stopped (and I/O is no longer started for a port that has not yet been probed). The device driver data is not freed until after the driver has been unbound and at which point all I/O has also ceased. Drop the redundant, overly defensive (and still incomplete) sanity checks from the completion callbacks. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 40 +++------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 21f15e19fae3..5139e1a35669 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -949,28 +949,12 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, static void digi_write_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct usb_serial *serial; - struct digi_port *priv; - struct digi_serial *serial_priv; - int status = urb->status; + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv = usb_get_serial_port_data(port); unsigned long flags; bool wakeup; int ret = 0; - /* port and serial sanity check */ - if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { - pr_err("%s: port or port->private is NULL, status=%d\n", - __func__, status); - return; - } - serial = port->serial; - if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { - dev_err(&port->dev, - "%s: serial or serial->private is NULL, status=%d\n", - __func__, status); - return; - } - /* handle oob callback */ if (priv->dp_port_num == serial_priv->ds_oob_port_num) { dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); @@ -1299,27 +1283,11 @@ static void digi_port_remove(struct usb_serial_port *port) static void digi_read_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct digi_port *priv; - struct digi_serial *serial_priv; + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv = usb_get_serial_port_data(port); int status = urb->status; int ret; - /* port sanity check, do not resubmit if port is not valid */ - if (port == NULL) - return; - priv = usb_get_serial_port_data(port); - if (priv == NULL) { - dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", - __func__, status); - return; - } - if (port->serial == NULL || - (serial_priv = usb_get_serial_data(port->serial)) == NULL) { - dev_err(&port->dev, "%s: serial is bad or serial->private " - "is NULL, status=%d\n", __func__, status); - return; - } - /* do not resubmit urb if it has any status error */ switch (status) { case 0: From 747e057e55308f07198c1cafb01c34185b5f2941 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:22 +0200 Subject: [PATCH 074/163] USB: serial: digi_acceleport: stop OOB I/O when not in use The driver submits the OOB read urb on first open of a port and does not stop it until the device is disconnected. Add an open counter and submit the urb on first open and stop it on last close to avoid wasting resources (e.g. power) when the device is not in use. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 100 ++++++++++++++------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 5139e1a35669..afaa62e257d0 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -176,10 +176,10 @@ /* Structures */ struct digi_serial { - spinlock_t ds_serial_lock; + struct mutex open_mutex; struct usb_serial_port *ds_oob_port; /* out-of-band port */ int ds_oob_port_num; /* index of out-of-band port */ - int ds_device_started; + int open_count; }; struct digi_port { @@ -226,9 +226,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); static void digi_close(struct usb_serial_port *port); static void digi_dtr_rts(struct usb_serial_port *port, int on); -static int digi_startup_device(struct usb_serial *serial); static int digi_startup(struct usb_serial *serial); -static void digi_disconnect(struct usb_serial *serial); static void digi_release(struct usb_serial *serial); static int digi_port_probe(struct usb_serial_port *port); static void digi_port_remove(struct usb_serial_port *port); @@ -281,7 +279,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .disconnect = digi_disconnect, .release = digi_release, .port_probe = digi_port_probe, .port_remove = digi_port_remove, @@ -310,7 +307,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .disconnect = digi_disconnect, .release = digi_release, .port_probe = digi_port_probe, .port_remove = digi_port_remove, @@ -1041,6 +1037,43 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); } +static int digi_open_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + int ret = 0; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count++ == 0) { + ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", + ret); + serial_priv->open_count--; + } + } + + mutex_unlock(&serial_priv->open_mutex); + + return ret; +} + +static void digi_close_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count-- == 1) { + usb_kill_urb(oob_port->read_urb); + usb_kill_urb(oob_port->write_urb); + } + + mutex_unlock(&serial_priv->open_mutex); +} + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) { struct digi_port *priv = usb_get_serial_port_data(port); @@ -1048,9 +1081,9 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) unsigned char buf[32]; int ret; - /* be sure the device is started up */ - if (digi_startup_device(port->serial) != 0) - return -ENXIO; + ret = digi_open_oob_port(port->serial); + if (ret) + return ret; /* read modem signals automatically whenever they change */ buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; @@ -1083,10 +1116,15 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) ret = usb_submit_urb(port->read_urb, GFP_KERNEL); if (ret) { dev_err(&port->dev, "failed to submit read urb: %d\n", ret); - return ret; + goto err_close_oob; } return 0; + +err_close_oob: + digi_close_oob_port(port->serial); + + return ret; } static void digi_close(struct usb_serial_port *port) @@ -1149,36 +1187,8 @@ static void digi_close(struct usb_serial_port *port) /* shutdown any outstanding bulk writes */ usb_kill_urb(port->write_urb); -} -/* - * Digi Startup Device - * - * Starts read on the OOB port. Must be called AFTER startup, with - * urbs initialized. Returns 0 if successful, non-zero error otherwise. - */ -static int digi_startup_device(struct usb_serial *serial) -{ - struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *oob_port = serial_priv->ds_oob_port; - int ret; - - /* be sure this happens exactly once */ - spin_lock(&serial_priv->ds_serial_lock); - if (serial_priv->ds_device_started) { - spin_unlock(&serial_priv->ds_serial_lock); - return 0; - } - serial_priv->ds_device_started = 1; - spin_unlock(&serial_priv->ds_serial_lock); - - ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); - if (ret) { - dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ret); - return ret; - } - - return 0; + digi_close_oob_port(port->serial); } static int digi_port_init(struct usb_serial_port *port, unsigned port_num) @@ -1229,7 +1239,8 @@ static int digi_startup(struct usb_serial *serial) if (!serial_priv) return -ENOMEM; - spin_lock_init(&serial_priv->ds_serial_lock); + mutex_init(&serial_priv->open_mutex); + serial_priv->ds_oob_port_num = oob_port_num; serial_priv->ds_oob_port = serial->port[oob_port_num]; @@ -1245,15 +1256,6 @@ static int digi_startup(struct usb_serial *serial) return 0; } -static void digi_disconnect(struct usb_serial *serial) -{ - struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *oob_port = serial_priv->ds_oob_port; - - usb_kill_urb(oob_port->read_urb); - usb_kill_urb(oob_port->write_urb); -} - static void digi_release(struct usb_serial *serial) { struct digi_serial *serial_priv; From 6f04e550a6247acf57fc736e346d4479c92bb70d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:23 +0200 Subject: [PATCH 075/163] USB: serial: digi_acceleport: drop unused in-buf define Drop the in-buf size define which has not been used since the port buffers were removed by commit 5fea2a4dabdf ("USB: digi_acceleport further buffer clean up"). Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index afaa62e257d0..8d320cce86fa 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -32,10 +32,6 @@ /* so we can be sure to send the full buffer in one urb */ #define DIGI_OUT_BUF_SIZE 8 -/* port input buffer length -- must be >= transfer buffer length - 3 */ -/* so we can be sure to hold at least one full buffer from one urb */ -#define DIGI_IN_BUF_SIZE 64 - /* retry timeout while sleeping */ #define DIGI_RETRY_TIMEOUT (HZ/10) From 6016799d33f27648ed41c82c3a0e1ac8e025b18b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:24 +0200 Subject: [PATCH 076/163] USB: serial: digi_acceleport: clean up xfer buf length expression Add the missing space around operators in transfer-buffer length expressions to make the code more readable. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 8d320cce86fa..0f6127c05998 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -452,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, /* len must be a multiple of 4 and small enough to */ /* guarantee the write will send buffered data first, */ /* so commands are in order with data and not split */ - len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); if (len > 4) len &= ~3; @@ -878,7 +878,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, priv->dp_port_num, count); /* copy user data (which can sleep) before getting spin lock */ - count = min(count, port->bulk_out_size-2); + count = min(count, port->bulk_out_size - 2); count = min(64, count); /* be sure only one write proceeds at a time */ @@ -900,7 +900,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, /* allow space for any buffered data and for new data, up to */ /* transfer buffer size - 2 (for command and length bytes) */ - new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); data_len = new_len + priv->dp_out_buf_len; if (data_len == 0) { @@ -908,7 +908,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, return 0; } - port->write_urb->transfer_buffer_length = data_len+2; + port->write_urb->transfer_buffer_length = data_len + 2; *data++ = DIGI_CMD_SEND_DATA; *data++ = data_len; From cc94b7b0cd203497bc89c520bd9a42724a335849 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:25 +0200 Subject: [PATCH 077/163] USB: serial: digi_acceleport: clean up write completion Clean up the write completion handler by adding a temporary variable for the transfer buffer and using the pre-existing urb pointer while dropping some redundant casts. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 0f6127c05998..f67bca769484 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -943,6 +943,7 @@ static void digi_write_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct digi_serial *serial_priv = usb_get_serial_data(port->serial); struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; unsigned long flags; bool wakeup; int ret = 0; @@ -962,15 +963,13 @@ static void digi_write_bulk_callback(struct urb *urb) spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; if (priv->dp_out_buf_len > 0) { - *((unsigned char *)(port->write_urb->transfer_buffer)) - = (unsigned char)DIGI_CMD_SEND_DATA; - *((unsigned char *)(port->write_urb->transfer_buffer) + 1) - = (unsigned char)priv->dp_out_buf_len; - port->write_urb->transfer_buffer_length = - priv->dp_out_buf_len + 2; - memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, - priv->dp_out_buf_len); - ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; + memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + + urb->transfer_buffer_length = priv->dp_out_buf_len + 2; + + ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret == 0) { priv->dp_write_urb_in_use = 1; priv->dp_out_buf_len = 0; From fdb85e08aa7a935eae6a60c5d721b34106d8a73a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:26 +0200 Subject: [PATCH 078/163] USB: serial: digi_acceleport: clean up inb command submission Clean up the inb command handling a bit by removing an unnecessary line break and moving the assignment operator before breaking another long expression. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index f67bca769484..efaafaf728f8 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -460,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, if (priv->dp_out_buf_len > 0) { data[0] = DIGI_CMD_SEND_DATA; data[1] = priv->dp_out_buf_len; - memcpy(data + 2, priv->dp_out_buf, - priv->dp_out_buf_len); + memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); memcpy(data + 2 + priv->dp_out_buf_len, buf, len); - port->write_urb->transfer_buffer_length - = priv->dp_out_buf_len + 2 + len; + port->write_urb->transfer_buffer_length = + priv->dp_out_buf_len + 2 + len; } else { memcpy(data, buf, len); port->write_urb->transfer_buffer_length = len; From c3c1852355c8ee171dcc71f71a328d672a604179 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 29 Jun 2026 14:40:37 +0200 Subject: [PATCH 079/163] USB: serial: digi_acceleport: fix oob port dev_printk() The OOB port is not registered with driver core and does not have a name. Use the USB interface with dev_printk() that may involve the OOB port to avoid log entries with no driver and a "null" device name. Fixes: f9dfbebb8b39 ("USB: serial: digi_acceleport.c: remove dbg() usage") Fixes: 194343d9364e ("USB: remove use of err() in drivers/usb/serial") Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index efaafaf728f8..e6d14f7d106d 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -949,7 +949,6 @@ static void digi_write_bulk_callback(struct urb *urb) /* handle oob callback */ if (priv->dp_port_num == serial_priv->ds_oob_port_num) { - dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; wake_up_interruptible(&priv->write_wait); @@ -1279,7 +1278,8 @@ static void digi_port_remove(struct usb_serial_port *port) static void digi_read_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct usb_serial *serial = port->serial; + struct digi_serial *serial_priv = usb_get_serial_data(serial); struct digi_port *priv = usb_get_serial_port_data(port); int status = urb->status; int ret; @@ -1291,12 +1291,12 @@ static void digi_read_bulk_callback(struct urb *urb) case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: - dev_dbg(&port->dev, + dev_err(&serial->interface->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); return; default: - dev_err(&port->dev, + dev_err(&serial->interface->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); return; @@ -1314,7 +1314,7 @@ static void digi_read_bulk_callback(struct urb *urb) /* continue read */ ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret != 0 && ret != -EPERM) { - dev_err(&port->dev, + dev_err(&serial->interface->dev, "%s: failed resubmitting urb, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); } @@ -1438,7 +1438,8 @@ static int digi_read_oob_callback(struct urb *urb) status = buf[i + 2]; val = buf[i + 3]; - dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + dev_dbg(&serial->interface->dev, + "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", opcode, line, status, val); if (status != 0 || line >= serial->type->num_ports) From c41d491929bbb2e3fcd9f0233e563b1f6e1120a7 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:21:46 +0200 Subject: [PATCH 080/163] USB: serial: metro-usb: replace unnecessary atomic allocation The unthrottle callback is allowed to sleep so pass the correct GFP flag to usb_submit_urb() to avoid unnecessary atomic allocations. Signed-off-by: Johan Hovold --- drivers/usb/serial/metro-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index 35473544f1c8..f42ad5dec35e 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -329,7 +329,7 @@ static void metrousb_unthrottle(struct tty_struct *tty) spin_unlock_irqrestore(&metro_priv->lock, flags); /* Submit the urb to read from the port. */ - result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (result) dev_err(&port->dev, "failed submitting interrupt in urb error code=%d\n", From 563cd5aacd759376e7a6af63ca5fbe7237a3b9de Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:21:47 +0200 Subject: [PATCH 081/163] USB: serial: metro-usb: fix unthrottle race If the completion handler races with unthrottle() both functions may try to resubmit the same interrupt-in urb, but at most one will succeed. Fix the unthrottle logic using a throttle-requested flag so that only one attempt to resubmit the urb is made to avoid logging an error. Fixes: 43d186fe992d ("USB: serial: add metro-usb driver to the tree") Signed-off-by: Johan Hovold --- drivers/usb/serial/metro-usb.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index f42ad5dec35e..8458713277f4 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -36,6 +36,7 @@ struct metrousb_private { spinlock_t lock; int throttled; + int throttle_req; unsigned long control_state; }; @@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) /* Set any port variables. */ spin_lock_irqsave(&metro_priv->lock, flags); - throttled = metro_priv->throttled; + if (metro_priv->throttle_req) { + metro_priv->throttled = 1; + throttled = 1; + } spin_unlock_irqrestore(&metro_priv->lock, flags); if (throttled) @@ -175,6 +179,7 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) spin_lock_irqsave(&metro_priv->lock, flags); metro_priv->control_state = 0; metro_priv->throttled = 0; + metro_priv->throttle_req = 0; spin_unlock_irqrestore(&metro_priv->lock, flags); /* Clear the urb pipe. */ @@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) /* Set the private information for the port to stop reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); - metro_priv->throttled = 1; + metro_priv->throttle_req = 1; spin_unlock_irqrestore(&metro_priv->lock, flags); } @@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) struct usb_serial_port *port = tty->driver_data; struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; + int throttled; int result = 0; /* Set the private information for the port to resume reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); + throttled = metro_priv->throttled; metro_priv->throttled = 0; + metro_priv->throttle_req = 0; spin_unlock_irqrestore(&metro_priv->lock, flags); - /* Submit the urb to read from the port. */ - result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); - if (result) - dev_err(&port->dev, - "failed submitting interrupt in urb error code=%d\n", - result); + if (throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", + result); + } + } } static struct usb_serial_driver metrousb_device = { From 79c6baf62ee4aa0a18ef8a61597158125745f171 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:21:48 +0200 Subject: [PATCH 082/163] USB: serial: metro-usb: drop redundant initialisations Three functions are initialising their return value variables at declaration only to later assign them unconditionally. Signed-off-by: Johan Hovold --- drivers/usb/serial/metro-usb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index 8458713277f4..22c5f071f116 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -109,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) unsigned char *data = urb->transfer_buffer; unsigned long flags; int throttled = 0; - int result = 0; + int result; dev_dbg(&port->dev, "%s\n", __func__); @@ -173,7 +173,7 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) struct usb_serial *serial = port->serial; struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; - int result = 0; + int result; /* Set the private data information for the port. */ spin_lock_irqsave(&metro_priv->lock, flags); @@ -327,7 +327,7 @@ static void metrousb_unthrottle(struct tty_struct *tty) struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; int throttled; - int result = 0; + int result; /* Set the private information for the port to resume reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); From 091738d09786ae4da789b5297cb4dae3024d1c13 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 29 Jun 2026 14:46:28 +0200 Subject: [PATCH 083/163] USB: serial: keyspan_pda: drop unused driver data usb-serial pointer The driver data usb-serial pointer is unused since commit 66c32e483355 ("USB: serial: keyspan_pda: drop redundant usb-serial pointer"), which apparently failed to remove the pointer as intended. Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index f05bcce60600..e8755d126244 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -31,7 +31,6 @@ struct keyspan_pda_private { int tx_room; struct work_struct unthrottle_work; - struct usb_serial *serial; struct usb_serial_port *port; }; From 0673b919e0933541ae56bb3f0253853f696d3773 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 10 Jul 2026 11:55:18 +0200 Subject: [PATCH 084/163] usb: misc: cypress_cy7c63: check result of IO If the device returns a bogus short read, treat it as EIO. Actually check for IO errors. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260710095523.1646308-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/cypress_cy7c63.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c index 4a7f955ba85b..ecc81c93c6ce 100644 --- a/drivers/usb/misc/cypress_cy7c63.c +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request, address, data, iobuf, CYPRESS_MAX_REQSIZE, USB_CTRL_GET_TIMEOUT); /* we must not process garbage */ - if (retval < 2) + if (retval < 2) { + if (retval >= 0) + retval = -EIO; goto err_buf; + } /* store returned data (more READs to be added) */ switch (request) { @@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr, dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); - dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); + if (result < 0) + return result; return sprintf(buf, "%d", cyp->port[port_num]); } From 41cf62c2cf6d12396b1fcb3244021ec7a9fbbda0 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Sun, 12 Jul 2026 23:18:35 +0530 Subject: [PATCH 085/163] dt-binding: qcom,snps-dwc3: Add Maili compatible to supported device list Add Maili compatible to supported device list. Maili has one SuperSpeed USB controller. Signed-off-by: Krishna Kurapati Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260712-maili-usb-dwc3-binding-v1-1-fd6697fa1e21@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 932d7aea43c5..9eec07dd33de 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -37,6 +37,7 @@ properties: - qcom,ipq8074-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 + - qcom,maili-dwc3 - qcom,milos-dwc3 - qcom,msm8953-dwc3 - qcom,msm8994-dwc3 @@ -208,6 +209,7 @@ allOf: - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 + - qcom,maili-dwc3 - qcom,msm8953-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 @@ -546,6 +548,7 @@ allOf: - qcom,ipq4019-dwc3 - qcom,ipq8064-dwc3 - qcom,kaanapali-dwc3 + - qcom,maili-dwc3 - qcom,qcs615-dwc3 - qcom,qcs8300-dwc3 - qcom,qdu1000-dwc3 From 958ea8c2137946e8c35b890bcfbdc6db974d8b82 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 9 Jul 2026 16:53:30 -0400 Subject: [PATCH 086/163] dt-bindings: usb: cdns3: allow iommus property Some SoC such as i.MX8QM have iommu support. Add optional property iommus. Signed-off-by: Frank Li Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260709205331.636449-1-Frank.Li@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/cdns,usb3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index e8082c5c05a2..c14a88aad31e 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -50,6 +50,9 @@ properties: - const: otg - const: wakeup + iommus: + maxItems: 1 + port: $ref: /schemas/graph.yaml#/properties/port description: From 0ac067cac7e24a27d483ca25a5061ff78b3b81db Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Fri, 10 Jul 2026 10:51:35 +0800 Subject: [PATCH 087/163] dt-bindings: usb: Add Aspeed AST2700 DWC3 controller The Aspeed AST2700 SoC integrates the Synopsys DesignWare USB3 core with no vendor glue logic: it is functionally compatible with snps,dwc3, uses the standard DWC3 clocks, and the only SoC-specific part is a USB3 PHY that is handled by a separate driver. Add a dedicated binding document rather than adding the compatible and a conditional to snps,dwc3.yaml. This follows the established per-vendor DWC3 convention (apple,dwc3.yaml, socionext,uniphier-dwc3.yaml, ...) and keeps the AST2700-specific constraints - notably the mandatory USB3 PHY - out of the generic schema. Signed-off-by: Ryan Chen Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260710-xhci-v2-1-f292c4f7339a@aspeedtech.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/aspeed,dwc3.yaml | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml b/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml new file mode 100644 index 000000000000..fff5a200f8c7 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/aspeed,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Aspeed SuperSpeed DWC3 USB SoC controller + +maintainers: + - Ryan Chen + +description: + The common content of the node is defined in snps,dwc3.yaml. + +select: + properties: + compatible: + contains: + const: aspeed,ast2700-xhci + required: + - compatible + +properties: + compatible: + items: + - const: aspeed,ast2700-xhci + - const: snps,dwc3 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Controller bus early clock + - description: PHY reference clock + - description: Controller suspend clock + + clock-names: + items: + - const: bus_early + - const: ref + - const: suspend + + resets: + maxItems: 1 + + phys: + maxItems: 1 + + phy-names: + const: usb3-phy + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - phys + - phy-names + +allOf: + - $ref: snps,dwc3.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + bus { + #address-cells = <2>; + #size-cells = <2>; + + usb@12030000 { + compatible = "aspeed,ast2700-xhci", "snps,dwc3"; + reg = <0x0 0x12030000 0x0 0x10000>; + interrupts = ; + clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>, + <&syscon0 SCU0_CLK_U2PHY_REFCLK>, + <&syscon0 SCU0_CLK_U2PHY_CLK12M>; + clock-names = "bus_early", "ref", "suspend"; + resets = <&syscon0 SCU0_RESET_PORTA_XHCI>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb3axh_default &pinctrl_usb2axh_default>; + phys = <&uphy3a>; + phy-names = "usb3-phy"; + dr_mode = "host"; + }; + }; From 4011ecdc5eea3dd3af395b85c6046cc041eaea91 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Sat, 11 Jul 2026 23:33:01 +0530 Subject: [PATCH 088/163] dt-bindings: usb: qcom,snps-dwc3: Add Shikra compatible Introduce the compatible definition for Shikra QCOM SNPS DWC3. Shikra SoC has two usb controllers and the secondary controller is high-speed only capable. Signed-off-by: Krishna Kurapati Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260711-usb-shikra-v4-v4-1-9d59b9d9aff7@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 9eec07dd33de..d410aeaf92fb 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -62,6 +62,7 @@ properties: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 + - qcom,shikra-dwc3 - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 @@ -222,6 +223,7 @@ allOf: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 + - qcom,shikra-dwc3 - qcom,sm6350-dwc3 - qcom,sm8750-dwc3 then: @@ -562,6 +564,7 @@ allOf: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 + - qcom,shikra-dwc3 - qcom,sm6350-dwc3 - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 From c9a934ddee1d572cd7bba3002aacee8d10896e75 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Mon, 13 Jul 2026 13:28:22 +0100 Subject: [PATCH 089/163] usb: gadget: f_fs: fix __le16 of wMaxPacketSize The wMaxPacketSize is __le16 type, fix the sparse warnings by changing the type. Fixes the following sparse warnings: drivers/usb/gadget/function/f_fs.c:3346:32: warning: incorrect type in assignment (different base types) drivers/usb/gadget/function/f_fs.c:3346:32: expected unsigned short [usertype] wMaxPacketSize drivers/usb/gadget/function/f_fs.c:3346:32: got restricted __le16 [usertype] wMaxPacketSize drivers/usb/gadget/function/f_fs.c:3371:36: warning: incorrect type in assignment (different base types) drivers/usb/gadget/function/f_fs.c:3371:36: expected restricted __le16 [usertype] wMaxPacketSize drivers/usb/gadget/function/f_fs.c:3371:36: got unsigned short [usertype] wMaxPacketSize Signed-off-by: Ben Dooks Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi Link: https://patch.msgid.link/20260713122822.1331334-1-ben.dooks@codethink.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 0fa7672f95da..073c4cbd90fb 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -3428,7 +3428,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, struct usb_request *req; struct usb_ep *ep; u8 bEndpointAddress; - u16 wMaxPacketSize; + __le16 wMaxPacketSize; /* * We back up bEndpointAddress because autoconfig overwrites From e263e18a9e7b1ff3e7301f0801c6ff87c31adfb6 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Mon, 13 Jul 2026 17:43:53 +0200 Subject: [PATCH 090/163] usb: core: Add lock to usb_wakeup_notification() Add a spin lock to usb_wakeup notification to prevent a race condition with dereferencing freed memory. This could be hit by the xHCI driver as it calls this function from an IRQ and could race with the hub_disconnect() function, which properly grabs this lock to protect the state of the device. Assisted-by: gkh_clanker_t1000 Signed-off-by: Griffin Kroah-Hartman Link: https://patch.msgid.link/20260713-usb_core_patches_1-v1-3-7721c2b33f53@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5262e11c12cd..5798efdd91a9 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -753,10 +753,12 @@ void usb_wakeup_notification(struct usb_device *hdev, { struct usb_hub *hub; struct usb_port *port_dev; + unsigned long flags; if (!hdev) return; + spin_lock_irqsave(&device_state_lock, flags); hub = usb_hub_to_struct_hub(hdev); if (hub) { port_dev = hub->ports[portnum - 1]; @@ -766,6 +768,7 @@ void usb_wakeup_notification(struct usb_device *hdev, set_bit(portnum, hub->wakeup_bits); kick_hub_wq(hub); } + spin_unlock_irqrestore(&device_state_lock, flags); } EXPORT_SYMBOL_GPL(usb_wakeup_notification); From 90b2a18d230b157525b368c44a2d234b0f2d02b3 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Tue, 14 Jul 2026 11:48:17 +0530 Subject: [PATCH 091/163] dt-bindings: usb: ti,tps6598x: add TPS66993 compatible Add a ti,tps66993 compatible to explicitly identify TPS66993 devices. The TPS66993 is not host-interface compatible with TPS6598x, so a distinct compatible is required. On the AMD/Xilinx VEK385 Evaluation Board, the Texas Instruments TPS66993 acts as the USB Type-C/USB PD DRP controller for the MMI USB interface, handling CC signaling, connection detection, PD negotiation and power/data role swapping. Acked-by: Krzysztof Kozlowski Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260714061820.537792-2-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ti,tps6598x.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml index 1745e28b3110..2c589e9e712e 100644 --- a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml +++ b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml @@ -4,13 +4,14 @@ $id: http://devicetree.org/schemas/usb/ti,tps6598x.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Texas Instruments 6598x Type-C Port Switch and Power Delivery controller +title: Texas Instruments Type-C port switch and USB Power Delivery controllers maintainers: - Bryan O'Donoghue description: | - Texas Instruments 6598x Type-C Port Switch and Power Delivery controller + Texas Instruments 6598x and 66993 Type-C Port Switch and Power Delivery + controller. A variant of this controller known as Apple CD321x or Apple ACE is also present on hardware with Apple SoCs such as the M1. @@ -19,6 +20,7 @@ properties: compatible: enum: - ti,tps6598x + - ti,tps66993 - apple,cd321x - ti,tps25750 From 1b21957eec2e8dc3bccc39abe8a4493a2cb53e44 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Tue, 14 Jul 2026 11:48:18 +0530 Subject: [PATCH 092/163] usb: typec: tipd: add read_power_status callback to tipd_data Convert direct tps6598x_read_power_status() calls to use an indirect read_power_status callback through tipd_data. This allows variants (e.g. TPS66993) to provide their own power status reading logic while keeping existing behavior unchanged for TPS6598x, CD321x, and TPS25750. Reviewed-by: Heikki Krogerus Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260714061820.537792-3-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index b6335b36d384..9097b0d40a71 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -159,6 +159,7 @@ struct tipd_data { int (*init)(struct tps6598x *tps); int (*switch_power_state)(struct tps6598x *tps, u8 target_state); bool (*read_data_status)(struct tps6598x *tps); + bool (*read_power_status)(struct tps6598x *tps); int (*reset)(struct tps6598x *tps); int (*connect)(struct tps6598x *tps, u32 status); }; @@ -897,7 +898,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) goto err_unlock; if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_unlock; if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { if (tps6598x_read_partner_identity(tps)) { @@ -952,7 +953,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) goto err_clear_ints; if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_clear_ints; if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) @@ -1026,7 +1027,7 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) @@ -1839,7 +1840,7 @@ static int tps6598x_probe(struct i2c_client *client) if (status & TPS_STATUS_PLUG_PRESENT) { ret = -EINVAL; - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_unregister_port; if (!tps->data->read_data_status(tps)) goto err_unregister_port; @@ -1981,6 +1982,7 @@ static const struct tipd_data cd321x_data = { .trace_status = trace_tps6598x_status, .init = cd321x_init, .read_data_status = cd321x_read_data_status, + .read_power_status = tps6598x_read_power_status, .reset = cd321x_reset, .switch_power_state = cd321x_switch_power_state, .connect = cd321x_connect, @@ -2000,6 +2002,7 @@ static const struct tipd_data tps6598x_data = { .apply_patch = tps6598x_apply_patch, .init = tps6598x_init, .read_data_status = tps6598x_read_data_status, + .read_power_status = tps6598x_read_power_status, .reset = tps6598x_reset, .connect = tps6598x_connect, }; @@ -2018,6 +2021,7 @@ static const struct tipd_data tps25750_data = { .apply_patch = tps25750_apply_patch, .init = tps25750_init, .read_data_status = tps6598x_read_data_status, + .read_power_status = tps6598x_read_power_status, .reset = tps25750_reset, .connect = tps6598x_connect, }; From c7ca309d57bd42174127e102031fcd9f39008450 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Tue, 14 Jul 2026 11:48:19 +0530 Subject: [PATCH 093/163] usb: typec: tipd: add TPS66993 support Derive power status from the STATUS register (0x1A) now that TPS66993 deprecates the Power_Status register (0x3F). Add support for the "APP1" mode string. TPS66993 controller is configured in polling mode and only type-c flip orientation feature is supported on AMD Versal AI Edge Gen 2 VEK385 Evaluation Kit. Signed-off-by: Radhey Shyam Pandey Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260714061820.537792-4-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 64 +++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 9097b0d40a71..522f56742aa9 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -130,6 +130,7 @@ enum { TPS_MODE_BIST, TPS_MODE_DISC, TPS_MODE_PTCH, + TPS_MODE_APP1, }; static const char *const modes[] = { @@ -138,6 +139,7 @@ static const char *const modes[] = { [TPS_MODE_BIST] = "BIST", [TPS_MODE_DISC] = "DISC", [TPS_MODE_PTCH] = "PTCH", + [TPS_MODE_APP1] = "APP1", }; /* Unrecognized commands will be replaced with "!CMD" */ @@ -631,6 +633,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) return true; } +/* + * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported + * and the remaining bits are redundant with STATUS register (0x1A). + * Synthesize pwr_status from the already-read STATUS register. + */ +static bool tps66993_read_power_status(struct tps6598x *tps) +{ + u16 pwr_status = 0; + + /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ + if (tps->status & TPS_STATUS_PLUG_PRESENT) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); + + /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ + if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); + + if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, + TPS_POWER_STATUS_TYPEC_CURRENT_PD); + + tps->pwr_status = pwr_status; + + tps->data->trace_power_status(pwr_status); + + return true; +} + static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) { int ret; @@ -1026,6 +1056,8 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) if (!tps6598x_read_status(tps, &status)) goto err_unlock; + tps->status = status; + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) if (!tps->data->read_power_status(tps)) goto err_unlock; @@ -1034,9 +1066,15 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) if (!tps->data->read_data_status(tps)) goto err_unlock; - /* Handle plug insert or removal */ - if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) + /* + * Refresh power status before connect - needed for TPS66993 which + * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. + */ + if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { + if (!tps->data->read_power_status(tps)) + goto err_unlock; tps6598x_handle_plug_event(tps, status); + } err_unlock: mutex_unlock(&tps->lock); @@ -1072,6 +1110,7 @@ static int tps6598x_check_mode(struct tps6598x *tps) switch (ret) { case TPS_MODE_APP: + case TPS_MODE_APP1: case TPS_MODE_PTCH: return ret; case TPS_MODE_BOOT: @@ -1813,6 +1852,8 @@ static int tps6598x_probe(struct i2c_client *client) goto err_clear_mask; } + tps->status = status; + /* * This fwnode has a "compatible" property, but is never populated as a * struct device. Instead we simply parse it to read the properties. @@ -2007,6 +2048,24 @@ static const struct tipd_data tps6598x_data = { .connect = tps6598x_connect, }; +static const struct tipd_data tps66993_data = { + .irq_handler = tps6598x_interrupt, + .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), + .register_port = tps6598x_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, + .read_power_status = tps66993_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, +}; + static const struct tipd_data tps25750_data = { .irq_handler = tps25750_interrupt, .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | @@ -2028,6 +2087,7 @@ static const struct tipd_data tps25750_data = { static const struct of_device_id tps6598x_of_match[] = { { .compatible = "ti,tps6598x", &tps6598x_data}, + { .compatible = "ti,tps66993", &tps66993_data}, { .compatible = "apple,cd321x", &cd321x_data}, { .compatible = "ti,tps25750", &tps25750_data}, {} From 3a3a48487344a653f46a4aadc9ba296386eb3541 Mon Sep 17 00:00:00 2001 From: Lucas Martins Alves Date: Tue, 14 Jul 2026 13:49:49 +0000 Subject: [PATCH 094/163] usb: musb: host: clear stale RX interrupt on three-strikes error Unplugging a busy USB Ethernet adapter can leave a stale RX interrupt pending while the host handles MUSB_RXCSR_H_ERROR. On AM335x/DSPS platforms this can retrigger the same three-strikes error before the disconnect path completes, flooding the log and potentially leaving the host port stuck until reboot. Clear the pending endpoint RX interrupt when aborting the transfer so the error storm is broken, and rate limit the error message to avoid spamming the log while the fault path completes. Signed-off-by: Lucas Martins Alves Link: https://patch.msgid.link/20260714134941.18508-1-lucas.alves@lumal21.com.br Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_host.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 8efd2fa472f9..31b061248991 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) status = -EPIPE; } else if (rx_csr & MUSB_RXCSR_H_ERROR) { - dev_err(musb->controller, "ep%d RX three-strikes error", epnum); + dev_err_ratelimited(musb->controller, + "ep%d RX three-strikes error\n", epnum); /* * The three-strikes error could only happen when the USB @@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum) rx_csr &= ~MUSB_RXCSR_H_ERROR; musb_writew(epio, MUSB_RXCSR, rx_csr); + /* + * Unplugging a USB-Ethernet adapter while it is busy can make + * the controller keep re-asserting the three-strikes error for + * this endpoint before the disconnect is processed. That floods + * the log and can wedge the host port until reboot. Drop the + * stale pending RX interrupt on platforms that support it (e.g. + * AM335x/DSPS) to break the storm; the transfer is still + * aborted below via the fault path. + */ + musb_platform_clear_ep_rxintr(musb, epnum); + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { if (USB_ENDPOINT_XFER_ISOC != qh->type) { From 1e6be4bd53f0f06377c3ce3c26ea4a6cadd4ab46 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 14 Jul 2026 12:20:47 +0100 Subject: [PATCH 095/163] usb: musb: remove dead select of USB_MUSB_DUAL_ROLE 'select' does not work on config options in a 'choice', so currently it is possible to enable USB_MUSB_POLARFIRE_SOC without USB_MUSB_DUAL_ROLE. Remove the dead select, as it is unnecessary, from this conversation: https://lore.kernel.org/all/20260712-parole-stoning-d7e66a0961a8@spud/ This dead select was found by kconfirm, a static analysis tool for Kconfig. Suggested-by: Conor Dooley Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260714112047.2304856-2-julianbraha@gmail.com Acked-by: Conor Dooley Acked-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index f56929267eaa..1b0d27d8e02f 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC tristate "Microchip PolarFire SoC platforms" depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST depends on NOP_USB_XCEIV - select USB_MUSB_DUAL_ROLE help Say Y here to enable support for USB on Microchip's PolarFire SoC. From 3b17c8d8d658f24e319b1aacd937dc9be38c3fe4 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Tue, 14 Jul 2026 21:10:52 +0000 Subject: [PATCH 096/163] power: supply: Add helpers to get and put arrays of power supply handles Add power_supply_get_system_batteries() to allow drivers to obtain a list of registered battery type power supply references in the system. Also add power_supply_put_system_batteries() to perform cleanup after the former function is called. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260714-batt-status-v5-1-9de4aa900b69@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/power/supply/power_supply_core.c | 153 +++++++++++++++++++++++ include/linux/power_supply.h | 15 +++ 2 files changed, 168 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 2532e221b2e1..70f35f64cc72 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +static bool power_supply_is_system_battery(struct power_supply *psy) +{ + union power_supply_propval val; + + if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) + return false; + + if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, + &val)) + if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) + return false; + + return true; +} + +static int __power_supply_get_num_system_batteries(struct power_supply *epsy, + void *data) +{ + int *count = data; + + if (power_supply_is_system_battery(epsy)) + (*count)++; + + return 0; +} + +static int power_supply_get_num_system_batteries(struct device *dev) +{ + int ret, count = 0; + + ret = power_supply_for_each_psy(&count, + __power_supply_get_num_system_batteries); + + dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); + + if (ret) + return ret; + + return count; +} + +struct psy_get_supplies_data { + int cnt; + int size; + struct power_supply **psys; +}; + +static int +__power_supply_populate_system_batteries_array(struct power_supply *epsy, + void *_data) +{ + struct psy_get_supplies_data *data = _data; + + if (power_supply_is_system_battery(epsy)) { + if (data->size <= data->cnt) + return -EOVERFLOW; + + get_device(&epsy->dev); + data->psys[data->cnt] = epsy; + atomic_inc(&epsy->use_cnt); + data->cnt++; + } + + return 0; +} + +static int +power_supply_populate_system_batteries_array(struct device *dev, int size, + struct power_supply **batteries) +{ + int ret; + + struct psy_get_supplies_data data = { + .cnt = 0, + .size = size, + .psys = batteries, + }; + + ret = power_supply_for_each_psy(&data, + __power_supply_populate_system_batteries_array); + + dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", + __func__, data.cnt, data.size, ret); + + if (ret < 0 || !data.cnt) { + power_supply_put_system_batteries(batteries, data.cnt); + return ret; + } + + return data.cnt; +} + +/** + * power_supply_get_system_batteries() - Fetches references to battery type + * power supplies in the system. + * @dev: Pointer to device requesting the power supply refs. + * @psys: Pointer to an array of power supply refs. + * + * Helper function to get handles to battery type power supplies in the system. + * If acquiring a ref to a power supply fails, then the search for battery type + * power supplies will abort and the acquired power supply references will be + * released. + * + * Return: Indicates the number of battery type power supplies returned on + * success or a negative error code on failure. + * + * Call power_supply_put_system_batteries() after use to cleanup resources. + */ +int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys) +{ + int ret; + + if (!psys) + return -EINVAL; + + ret = power_supply_get_num_system_batteries(dev); + if (ret <= 0) { + *psys = NULL; + return ret; + } + + *psys = kzalloc_objs(**psys, ret); + if (!*psys) + return -ENOMEM; + + ret = power_supply_populate_system_batteries_array(dev, ret, *psys); + if (ret <= 0) + *psys = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); + +/** + * power_supply_put_system_batteries() - Cleanup resources allocated by + * power_supply_get_system_batteries() + * @psys: Array of power supply references to release and free. + * @count: Number of elements in the array. + */ +void power_supply_put_system_batteries(struct power_supply **psys, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (psys[i]) + power_supply_put(psys[i]); + } + + kfree(psys); +} +EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); + /** * power_supply_put() - Drop reference obtained with power_supply_get_by_name * @psy: Reference to put diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7a5e4c3242a0..9167a33d074b 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -806,11 +806,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); extern void power_supply_unreg_notifier(struct notifier_block *nb); #if IS_ENABLED(CONFIG_POWER_SUPPLY) extern struct power_supply *power_supply_get_by_name(const char *name); +extern int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys); +extern void power_supply_put_system_batteries(struct power_supply **psys, int count); extern void power_supply_put(struct power_supply *psy); #else static inline void power_supply_put(struct power_supply *psy) {} static inline struct power_supply *power_supply_get_by_name(const char *name) { return NULL; } +static inline int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys) +{ + if (psys) + *psys = NULL; + return 0; +} + +static inline void power_supply_put_system_batteries(struct power_supply **psys, + int count) +{ +} #endif extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, const char *property); From 6ffa37493898b0d70f72027790487451e00cd82b Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Tue, 14 Jul 2026 21:10:53 +0000 Subject: [PATCH 097/163] usb: typec: tcpm: Add support for Battery Status response message Add support for responding to a Get_Battery_Status request with a Battery_Status message. The port partner shall request the status of a port's battery by providing an index in the Get_Battery_Status AMS. In case of failure to identify the battery, the port shall reply with an appropriate message indicating so. Support for Battery_Status message is required for sinks that contain battery as specified in USB PD Rev3.1 v1.8 ("Applicability of Data Messages" section). Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260714-batt-status-v5-2-9de4aa900b69@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 138 +++++++++++++++++++++++++++++++++- include/linux/usb/pd.h | 29 +++++++ 2 files changed, 163 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 5e652f3d449b..4b815e933d31 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -232,7 +233,8 @@ enum pd_msg_request { PD_MSG_DATA_SINK_CAP, PD_MSG_DATA_SOURCE_CAP, PD_MSG_DATA_REV, - PD_MSG_EXT_SINK_CAP_EXT + PD_MSG_EXT_SINK_CAP_EXT, + PD_MSG_DATA_BATT_STATUS }; enum adev_actions { @@ -387,7 +389,15 @@ struct pd_timings { }; /* Convert microwatt to watt */ -#define UW_TO_W(pow) ((pow) / 1000000) +#define UW_TO_W(pow) (div_u64((pow), 1000000)) + +/* + * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries + * that a port can be queried is restricted to 4. + */ +#define MAX_NUM_FIXED_BATT 4 + +#define BATTERY_PROPERTY_UNKNOWN 0xffff /* * struct pd_identifier - Contains info about PD identifiers @@ -683,6 +693,9 @@ struct tcpm_port { struct pd_identifier pd_ident; struct sink_caps_ext_data sink_caps_ext; + struct power_supply **fixed_batt; + u32 fixed_batt_cnt; + u32 batt_request_id; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -1470,6 +1483,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } +static void tcpm_get_fixed_batt(struct tcpm_port *port) +{ + int ret; + + if (!port->self_powered || port->fixed_batt_cnt > 0) + return; + + ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); + if (ret < 0) + tcpm_log(port, "Failed to get battery array, ret=%d", ret); + else + port->fixed_batt_cnt = ret; +} + static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) { u16 operating_snk_watt = port->operating_snk_mw / 1000; @@ -1482,6 +1509,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) if (!port->self_powered) data->spr_op_pdp = operating_snk_watt; + tcpm_get_fixed_batt(port); + /* * SPR Sink Minimum PDP indicates the minimum power required to operate * a sink device in its lowest level of functionality without requiring @@ -1507,6 +1536,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) skedb.load_step = data->load_step; skedb.load_char = cpu_to_le16(data->load_char); skedb.compliance = data->compliance; + skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); skedb.modes = data->modes; skedb.spr_min_pdp = data->spr_min_pdp; skedb.spr_op_pdp = data->spr_op_pdp; @@ -1525,6 +1555,88 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) port->message_id, data_obj_cnt, 1 /* Denotes if ext header */)); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_batt_status(struct tcpm_port *port) +{ + u16 present_charge = BATTERY_PROPERTY_UNKNOWN; + bool batt_present = false, invalid_ref = true; + u32 batt_id = port->batt_request_id; + union power_supply_propval val; + struct power_supply *batt; + u8 charging_status = 0; + struct pd_message msg; + int ret, charge_now; + u64 energy_now; + u32 bsdo; + + tcpm_get_fixed_batt(port); + memset(&msg, 0, sizeof(msg)); + + if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) + goto send_status; + + invalid_ref = false; + batt = port->fixed_batt[batt_id]; + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); + if (ret) + tcpm_log(port, + "Failed to fetch power_supply_prop_present ret %d", + ret); + else + batt_present = val.intval > 0; + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, + &val); + if (!ret) { + charge_now = val.intval; + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + &val); + if (!ret) { + energy_now = div_u64((u64)charge_now * val.intval, + 1000000); + + /* + * Battery Present Charge is reported in + * increments of 0.1WH. + */ + present_charge = (u16)UW_TO_W(energy_now * 10); + } + } + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); + if (!ret) { + switch (val.intval) { + case POWER_SUPPLY_STATUS_CHARGING: + charging_status = BSDO_BATTERY_INFO_CHARGING; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + charging_status = BSDO_BATTERY_INFO_DISCHARGING; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + case POWER_SUPPLY_STATUS_FULL: + charging_status = BSDO_BATTERY_INFO_IDLE; + break; + default: + charging_status = BSDO_BATTERY_INFO_RSVD; + break; + } + } + +send_status: + + bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); + msg.payload[0] = cpu_to_le32(bsdo); + msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 1); + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } @@ -3892,6 +4004,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, { enum pd_ext_msg_type type = pd_header_type_le(msg->header); unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); + const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; /* stopping VDM state machine if interrupted by other Messages */ if (tcpm_vdm_ams(port)) { @@ -3900,7 +4013,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, mod_vdm_delayed_work(port, 0); } - if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { + if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); tcpm_log(port, "Unchunked extended messages unsupported"); return; @@ -3925,9 +4038,17 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, NONE_AMS, 0); } break; + case PD_EXT_GET_BATT_STATUS: + if (data_size >= 1) { + port->batt_request_id = ext_msg->data[0]; + tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, + GETTING_BATTERY_STATUS); + } else { + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } + break; case PD_EXT_SOURCE_CAP_EXT: case PD_EXT_GET_BATT_CAP: - case PD_EXT_GET_BATT_STATUS: case PD_EXT_BATT_CAP: case PD_EXT_GET_MANUFACTURER_INFO: case PD_EXT_MANUFACTURER_INFO: @@ -4138,6 +4259,14 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) ret); tcpm_ams_finish(port); break; + case PD_MSG_DATA_BATT_STATUS: + ret = tcpm_pd_send_batt_status(port); + if (ret) + tcpm_log(port, + "Failed to send battery status ret=%d", + ret); + tcpm_ams_finish(port); + break; default: break; } @@ -8639,6 +8768,7 @@ void tcpm_unregister_port(struct tcpm_port *port) hrtimer_cancel(&port->vdm_state_machine_timer); hrtimer_cancel(&port->state_machine_timer); + power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); tcpm_reset_port(port); tcpm_port_unregister_pd(port); diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index ee360dedeaa6..9b94fb218bb6 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -724,4 +724,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi #endif /* CONFIG_TYPEC */ +/* Battery Status Data Object */ +#define BSDO_PRESENT_CAPACITY GENMASK(31, 16) +#define BSDO_CHG_STATUS GENMASK(11, 10) +#define BSDO_BATTERY_PRESENT BIT(9) +#define BSDO_INVALID_BATTERY_REFERENCE BIT(8) + +/* + * Battery Charge Status: Battery Charging Status Values as defined in + * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)". + */ +#define BSDO_BATTERY_INFO_CHARGING 0x0 +#define BSDO_BATTERY_INFO_DISCHARGING 0x1 +#define BSDO_BATTERY_INFO_IDLE 0x2 +#define BSDO_BATTERY_INFO_RSVD 0x3 + +/** + * BSDO() - Pack data into Battery Status Data Object format. + * @batt_charge: Battery's present state of charge in 0.1WH increment. + * @chg_status: Battery charge status. + * @batt_present: Indicates that battery is present/attached when set else absent when unset. + * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status + * request. + */ +#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \ + ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \ + (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \ + ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \ + ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0)) + #endif /* __LINUX_USB_PD_H */ From 9277b3b2d5a990591e36ec391fe14682e460ab94 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Tue, 14 Jul 2026 23:06:47 +0000 Subject: [PATCH 098/163] usb: typec: tcpm: Add support for Battery Cap response message Add support for responding to Get_Battery_Cap (extended) request with a a Battery_Capabilities (extended) msg. The requester will request Battery Cap for a specific battery using an index in Get_Battery_Cap. In case of failure to identify battery, TCPM shall reply with an appropriate message indicating so. As the Battery Cap Data Block size is 9 Bytes (lesser than MaxExtendedMsgChunkLen of 26B), only a single chunk is required to complete the AMS. Support for Battery_Capabilities message is required for sinks that contain battery as specified in USB PD Rev3.1 v1.8 ("Applicability of Data Messages" section). Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260714-batt-caps-upstream-v1-1-c86f0a7fbbda@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 108 ++++++++++++++++++++++++++++++---- include/linux/usb/pd.h | 22 +++++++ 2 files changed, 117 insertions(+), 13 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 4b815e933d31..1b56941fa2fa 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -234,7 +234,8 @@ enum pd_msg_request { PD_MSG_DATA_SOURCE_CAP, PD_MSG_DATA_REV, PD_MSG_EXT_SINK_CAP_EXT, - PD_MSG_DATA_BATT_STATUS + PD_MSG_DATA_BATT_STATUS, + PD_MSG_EXT_BATT_CAP, }; enum adev_actions { @@ -1559,6 +1560,14 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } +static u16 tcpm_charge_to_energy(int charge, int voltage) +{ + u64 energy = div_u64((u64)charge * voltage, 1000000); + + /* Battery telemetry is reported in increments of 0.1Wh */ + return (u16)UW_TO_W(energy * 10); +} + static int tcpm_pd_send_batt_status(struct tcpm_port *port) { u16 present_charge = BATTERY_PROPERTY_UNKNOWN; @@ -1569,7 +1578,6 @@ static int tcpm_pd_send_batt_status(struct tcpm_port *port) u8 charging_status = 0; struct pd_message msg; int ret, charge_now; - u64 energy_now; u32 bsdo; tcpm_get_fixed_batt(port); @@ -1595,16 +1603,9 @@ static int tcpm_pd_send_batt_status(struct tcpm_port *port) ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, &val); - if (!ret) { - energy_now = div_u64((u64)charge_now * val.intval, - 1000000); - - /* - * Battery Present Charge is reported in - * increments of 0.1WH. - */ - present_charge = (u16)UW_TO_W(energy_now * 10); - } + if (!ret) + present_charge = tcpm_charge_to_energy(charge_now, + val.intval); } ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); @@ -1640,6 +1641,71 @@ static int tcpm_pd_send_batt_status(struct tcpm_port *port) return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } +static int tcpm_pd_send_batt_cap(struct tcpm_port *port) +{ + u16 design_cap = BATTERY_PROPERTY_UNKNOWN; + u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; + u32 batt_id = port->batt_request_id; + union power_supply_propval val; + struct batt_cap_ext_msg bcdb; + struct power_supply *batt; + bool invalid_ref = true; + struct pd_message msg; + u8 data_obj_cnt; + int ret, vol; + + tcpm_get_fixed_batt(port); + memset(&msg, 0, sizeof(msg)); + + if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) + goto send_cap; + + invalid_ref = false; + batt = port->fixed_batt[batt_id]; + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, + &val); + if (!ret) { + vol = val.intval; + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + &val); + if (!ret) + design_cap = tcpm_charge_to_energy(val.intval, vol); + + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_CHARGE_FULL, + &val); + if (!ret) + charge_cap = tcpm_charge_to_energy(val.intval, vol); + } + +send_cap: + + /* + * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the + * USB-IF) does not exist or an invalid battery reference is made by the + * requestor, then set the VID field to 0xffff. If the VID field is + * 0xffff, set the PID field to 0. + */ + bcdb.vid = BATTERY_PROPERTY_UNKNOWN; + bcdb.pid = 0; + bcdb.batt_design_cap = cpu_to_le16(design_cap); + bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); + bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; + memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); + msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), + 0, /* Denotes if request chunk */ + 0, /* Chunk number */ + 1 /* Chunked */); + + data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); + msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, + port->data_role, port->negotiated_rev, + port->message_id, data_obj_cnt); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + static void mod_tcpm_delayed_work(struct tcpm_port *port, unsigned int delay_ms) { if (delay_ms) { @@ -4047,8 +4113,16 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, tcpm_set_state(port, SOFT_RESET_SEND, 0); } break; - case PD_EXT_SOURCE_CAP_EXT: case PD_EXT_GET_BATT_CAP: + if (data_size >= 1) { + port->batt_request_id = ext_msg->data[0]; + tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, + GETTING_BATTERY_CAPABILITIES); + } else { + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } + break; + case PD_EXT_SOURCE_CAP_EXT: case PD_EXT_BATT_CAP: case PD_EXT_GET_MANUFACTURER_INFO: case PD_EXT_MANUFACTURER_INFO: @@ -4267,6 +4341,14 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) ret); tcpm_ams_finish(port); break; + case PD_MSG_EXT_BATT_CAP: + ret = tcpm_pd_send_batt_cap(port); + if (ret) + tcpm_log(port, + "Failed to send battery cap ret=%d", + ret); + tcpm_ams_finish(port); + break; default: break; } diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 9b94fb218bb6..914e78f8acfd 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -106,6 +106,9 @@ enum pd_ext_msg_type { #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) +#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1))) + static inline unsigned int pd_header_cnt(u16 header) { return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; @@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size) return ((size / 4) + (size % 4 ? 1 : 0)); } +/** + * batt_cap_ext_msg - Battery capability extended PD message + * @vid: Battery Vendor ID (assigned by USB-IF) + * @pid: Battery Product ID (assigned by battery or device vendor) + * @batt_design_cap: Battery design capacity in 0.1Wh + * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh + * @batt_type: Battery Type. bit0 when set indicates invalid battery reference. + * Rest of the bits are reserved. + */ +struct batt_cap_ext_msg { + __le16 vid; + __le16 pid; + __le16 batt_design_cap; + __le16 batt_last_chg_cap; + u8 batt_type; +} __packed; + +#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0) + /* Sink Caps Extended Data Block Version */ #define SKEDB_VER_1_0 1 From 20f456d95275018cfe069c2691b06b75ed981493 Mon Sep 17 00:00:00 2001 From: Chaoyi Chen Date: Thu, 9 Jul 2026 14:21:47 +0800 Subject: [PATCH 099/163] usb: typec: altmodes: select DRM_AUX_HPD_BRIDGE for TYPEC_DP_ALTMODE When TYPEC_DP_ALTMODE is enabled, DRM_AUX_HPD_BRIDGE is most likely also needed for embedded platforms. Select it when available. Suggested-by: Xu Yang Signed-off-by: Chaoyi Chen Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260709062147.240-1-kernel@airkyi.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig index 7867fa7c405d..b054c0b6a8d4 100644 --- a/drivers/usb/typec/altmodes/Kconfig +++ b/drivers/usb/typec/altmodes/Kconfig @@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers" config TYPEC_DP_ALTMODE tristate "DisplayPort Alternate Mode driver" depends on DRM + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF help DisplayPort USB Type-C Alternate Mode allows DisplayPort displays and adapters to be attached to the USB Type-C From 590d74ec8f488e06b9f1c0f8f0941f45531f3a55 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Mon, 13 Jul 2026 14:08:45 +0800 Subject: [PATCH 100/163] usb: gadget: f_uac1_legacy: remove broken string configfs attributes The UAC1_STR_ATTRIBUTE macro defines configfs show/store handlers for the fn_play, fn_cap, and fn_cntl string options. The store function contains an inverted null check on the kstrndup() return value. This means every write attempt returns -ENOMEM on success and dereferences a NULL pointer on allocation failure. The attributes have been broken and unused for many years. Remove the UAC1_STR_ATTRIBUTE macro and the three attributes it generated. The internal defaults (FILE_PCM_PLAYBACK, FILE_PCM_CAPTURE, FILE_CONTROL) set in f_audio_alloc_inst() are unaffected. Fixes: 0854611a19ae ("usb: gadget: f_uac1: add configfs support") Link: https://lore.kernel.org/linux-usb/20260625113154.1954813-1-xu.yang_2@oss.nxp.com/ Suggested-by: Greg Kroah-Hartman Assisted-by: Claude:claude-sonnet-4.6 Signed-off-by: Xu Yang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260713060845.3759673-1-xu.yang_2@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- .../testing/configfs-usb-gadget-uac1_legacy | 3 - Documentation/usb/gadget-testing.rst | 3 - drivers/usb/gadget/function/f_uac1_legacy.c | 56 ------------------- drivers/usb/gadget/function/u_uac1_legacy.h | 3 - 4 files changed, 65 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy index b2eaefd9bc49..6a681d219f43 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy @@ -5,8 +5,5 @@ Description: The attributes: audio_buf_size - audio buffer size - fn_cap - capture pcm device file name - fn_cntl - control device file name - fn_play - playback pcm device file name req_buf_size - ISO OUT endpoint request buffer size req_count - ISO OUT endpoint request count diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index a6e8292f320a..4921e5307d49 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -714,9 +714,6 @@ The uac1 function provides these attributes in its function directory: =============== ==================================== audio_buf_size audio buffer size - fn_cap capture pcm device file name - fn_cntl control device file name - fn_play playback pcm device file name req_buf_size ISO OUT endpoint request buffer size req_count ISO OUT endpoint request count =============== ==================================== diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 5d201a2e30e7..3f52099a4fdd 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -888,60 +888,10 @@ UAC1_INT_ATTRIBUTE(req_buf_size); UAC1_INT_ATTRIBUTE(req_count); UAC1_INT_ATTRIBUTE(audio_buf_size); -#define UAC1_STR_ATTRIBUTE(name) \ -static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ - char *page) \ -{ \ - struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ - int result; \ - \ - mutex_lock(&opts->lock); \ - result = sprintf(page, "%s\n", opts->name); \ - mutex_unlock(&opts->lock); \ - \ - return result; \ -} \ - \ -static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ - const char *page, size_t len) \ -{ \ - struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ - int ret = -EBUSY; \ - char *tmp; \ - \ - mutex_lock(&opts->lock); \ - if (opts->refcnt) \ - goto end; \ - \ - tmp = kstrndup(page, len, GFP_KERNEL); \ - if (tmp) { \ - ret = -ENOMEM; \ - goto end; \ - } \ - if (opts->name##_alloc) \ - kfree(opts->name); \ - opts->name##_alloc = true; \ - opts->name = tmp; \ - ret = len; \ - \ -end: \ - mutex_unlock(&opts->lock); \ - return ret; \ -} \ - \ -CONFIGFS_ATTR(f_uac1_opts_, name) - -UAC1_STR_ATTRIBUTE(fn_play); -UAC1_STR_ATTRIBUTE(fn_cap); -UAC1_STR_ATTRIBUTE(fn_cntl); - static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_req_buf_size, &f_uac1_opts_attr_req_count, &f_uac1_opts_attr_audio_buf_size, - &f_uac1_opts_attr_fn_play, - &f_uac1_opts_attr_fn_cap, - &f_uac1_opts_attr_fn_cntl, NULL, }; @@ -956,12 +906,6 @@ static void f_audio_free_inst(struct usb_function_instance *f) struct f_uac1_legacy_opts *opts; opts = container_of(f, struct f_uac1_legacy_opts, func_inst); - if (opts->fn_play_alloc) - kfree(opts->fn_play); - if (opts->fn_cap_alloc) - kfree(opts->fn_cap); - if (opts->fn_cntl_alloc) - kfree(opts->fn_cntl); kfree(opts); } diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h index b5df9bcbbeba..b9ddae550ff3 100644 --- a/drivers/usb/gadget/function/u_uac1_legacy.h +++ b/drivers/usb/gadget/function/u_uac1_legacy.h @@ -62,9 +62,6 @@ struct f_uac1_legacy_opts { char *fn_cap; char *fn_cntl; unsigned bound:1; - unsigned fn_play_alloc:1; - unsigned fn_cap_alloc:1; - unsigned fn_cntl_alloc:1; struct mutex lock; int refcnt; }; From 15902712b9e2a46649cba2f22002d05faec2a990 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Fri, 17 Jul 2026 01:24:13 +0300 Subject: [PATCH 101/163] USB: misc: uss720: Fix leak in get_1284_register if wait_for_completion_timeout() times out in get_1284_register() then we do not destroy the reference acquired via kref_get() in submit_async_request. Add the missing kref_put in the timeout path. link: https://syzkaller.appspot.com/bug?extid=56962eb32ba0136cd330 Reported-by: syzbot+56962eb32ba0136cd330@syzkaller.appspotmail.com Signed-off-by: Michail Tatas Link: https://patch.msgid.link/allaDTUua9n5DQ0a@michalis-linux Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/uss720.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index 1ce48f5832d7..bd0ad7c3261d 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha } printk(KERN_WARNING "get_1284_register timeout\n"); kill_all_async_requests_priv(priv); + kref_put(&rq->ref_count, destroy_async); return -EIO; } From caad28c0816fe212564362cdc176aea5c506f839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Tue, 16 Jun 2026 19:36:06 -0300 Subject: [PATCH 102/163] usb: rust: mark Device and Interface methods as inline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When building the kernel using llvm-19.1.7-rust-1.85.1-x86_64, the following symbols are generated: $ nm vmlinux | grep ' _R'.*usb.*Device | rustfilt ... ffffffff823f2490 T ::dec_ref ffffffff823f2470 T ::inc_ref ... $ nm vmlinux | grep ' _R'.*usb.*Interface | rustfilt ffffffff823f2450 T ::dec_ref ffffffff823f2430 T ::inc_ref ... However, these Rust symbols are trivial wrappers around the `usb_get_dev`, `usb_put_dev`, `usb_get_intf` and `usb_put_intf` functions. It doesn't make sense to go through a trivial wrapper for these functions. Link: https://github.com/Rust-for-Linux/linux/issues/1145 Suggested-by: Alice Ryhl Signed-off-by: Nicolás Antinori Reviewed-by: Daniel Almeida Reviewed-by: Alice Ryhl Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260616223614.16444-1-nico.antinori.7@gmail.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/usb.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index bbf85366d2c9..6dfdd64dd617 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -382,6 +382,7 @@ fn as_ref(&self) -> &Device { // SAFETY: Instances of `Interface` are always reference-counted. unsafe impl AlwaysRefCounted for Interface { + #[inline] fn inc_ref(&self) { // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` // returns a valid `struct usb_interface` pointer, for which we will @@ -389,6 +390,7 @@ fn inc_ref(&self) { unsafe { bindings::usb_get_intf(self.as_raw()) }; } + #[inline] unsafe fn dec_ref(obj: NonNull) { // SAFETY: The safety requirements guarantee that the refcount is non-zero. unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } @@ -433,6 +435,7 @@ fn as_raw(&self) -> *mut bindings::usb_device { // SAFETY: Instances of `Device` are always reference-counted. unsafe impl AlwaysRefCounted for Device { + #[inline] fn inc_ref(&self) { // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` // returns a valid `struct usb_device` pointer, for which we will @@ -440,6 +443,7 @@ fn inc_ref(&self) { unsafe { bindings::usb_get_dev(self.as_raw()) }; } + #[inline] unsafe fn dec_ref(obj: NonNull) { // SAFETY: The safety requirements guarantee that the refcount is non-zero. unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) } From 80574c40598aedbc1751c528e414d7e224bc6313 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Jul 2026 17:49:57 +0200 Subject: [PATCH 103/163] USB: phy: fsl-usb: fix missing static keywords A recent change enabling compile testing of a Freescale dual-role controller indirectly enabled a USB PHY driver to be built. That driver in turn is missing a bunch of static keywords which results in warnings like: drivers/usb/phy/phy-fsl-usb.c:105:5: error: no previous prototype for 'write_ulpi' [-Werror=missing-prototypes] 105 | int write_ulpi(u8 addr, u8 data) | ^~~~~~~~~~ which consequently breaks -Werror builds. Add the missing static keywords. Fixes: 0807c500a1a6 ("USB: add Freescale USB OTG Transceiver driver") Cc: stable@vger.kernel.org # 3.0 Reported-by: Mark Brown Link: https://lore.kernel.org/r/4f9f5ff9-8eaa-4bd5-9331-37119f78e13f@sirena.org.uk Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260717154957.1853976-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-fsl-usb.c | 52 +++++++++++++++++------------------ drivers/usb/phy/phy-fsl-usb.h | 6 ++-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index 35d79f11b03d..1986f7e9d9be 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -46,7 +46,7 @@ static const char driver_name[] = "fsl-usb2-otg"; -const pm_message_t otg_suspend_state = { +static const pm_message_t otg_suspend_state = { .event = 1, }; @@ -57,11 +57,11 @@ static struct fsl_otg *fsl_otg_dev; static int srp_wait_done; /* FSM timers */ -struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, +static struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr; /* Driver specific timers */ -struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, +static struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, *b_srp_wait_tmr, *a_wait_enum_tmr; static struct list_head active_timers; @@ -102,7 +102,7 @@ static void (*_fsl_writel)(u32 v, unsigned __iomem *p); #define fsl_writel(val, addr) writel(val, addr) #endif /* CONFIG_PPC32 */ -int write_ulpi(u8 addr, u8 data) +static int write_ulpi(u8 addr, u8 data) { u32 temp; @@ -115,7 +115,7 @@ int write_ulpi(u8 addr, u8 data) /* Operations that will be called from OTG Finite State Machine */ /* Charge vbus for vbus pulsing in SRP */ -void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on) +static void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on) { u32 tmp; @@ -133,7 +133,7 @@ void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on) } /* Discharge vbus through a resistor to ground */ -void fsl_otg_dischrg_vbus(int on) +static void fsl_otg_dischrg_vbus(int on) { u32 tmp; @@ -151,7 +151,7 @@ void fsl_otg_dischrg_vbus(int on) } /* A-device driver vbus, controlled through PP bit in PORTSC */ -void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on) +static void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on) { u32 tmp; @@ -169,7 +169,7 @@ void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on) * Pull-up D+, signalling connect by periperal. Also used in * data-line pulsing in SRP */ -void fsl_otg_loc_conn(struct otg_fsm *fsm, int on) +static void fsl_otg_loc_conn(struct otg_fsm *fsm, int on) { u32 tmp; @@ -188,7 +188,7 @@ void fsl_otg_loc_conn(struct otg_fsm *fsm, int on) * port. In host mode, controller will automatically send SOF. * Suspend will block the data on the port. */ -void fsl_otg_loc_sof(struct otg_fsm *fsm, int on) +static void fsl_otg_loc_sof(struct otg_fsm *fsm, int on) { u32 tmp; @@ -203,7 +203,7 @@ void fsl_otg_loc_sof(struct otg_fsm *fsm, int on) } /* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ -void fsl_otg_start_pulse(struct otg_fsm *fsm) +static void fsl_otg_start_pulse(struct otg_fsm *fsm) { u32 tmp; @@ -219,7 +219,7 @@ void fsl_otg_start_pulse(struct otg_fsm *fsm) fsl_otg_add_timer(fsm, b_data_pulse_tmr); } -void b_data_pulse_end(unsigned long foo) +static void b_data_pulse_end(unsigned long foo) { #ifdef HA_DATA_PULSE #else @@ -230,7 +230,7 @@ void b_data_pulse_end(unsigned long foo) fsl_otg_pulse_vbus(); } -void fsl_otg_pulse_vbus(void) +static void fsl_otg_pulse_vbus(void) { srp_wait_done = 0; fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 1); @@ -238,7 +238,7 @@ void fsl_otg_pulse_vbus(void) fsl_otg_add_timer(&fsl_otg_dev->fsm, b_vbus_pulse_tmr); } -void b_vbus_pulse_end(unsigned long foo) +static void b_vbus_pulse_end(unsigned long foo) { fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 0); @@ -251,7 +251,7 @@ void b_vbus_pulse_end(unsigned long foo) fsl_otg_add_timer(&fsl_otg_dev->fsm, b_srp_wait_tmr); } -void b_srp_end(unsigned long foo) +static void b_srp_end(unsigned long foo) { fsl_otg_dischrg_vbus(0); srp_wait_done = 1; @@ -266,7 +266,7 @@ void b_srp_end(unsigned long foo) * a_host will start by SRP. It needs to set b_hnp_enable before * actually suspending to start HNP */ -void a_wait_enum(unsigned long foo) +static void a_wait_enum(unsigned long foo) { VDBG("a_wait_enum timeout\n"); if (!fsl_otg_dev->phy.otg->host->b_hnp_enable) @@ -276,13 +276,13 @@ void a_wait_enum(unsigned long foo) } /* The timeout callback function to set time out bit */ -void set_tmout(unsigned long indicator) +static void set_tmout(unsigned long indicator) { *(int *)indicator = 1; } /* Initialize timers */ -int fsl_otg_init_timers(struct otg_fsm *fsm) +static int fsl_otg_init_timers(struct otg_fsm *fsm) { /* FSM used timers */ a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, @@ -339,7 +339,7 @@ int fsl_otg_init_timers(struct otg_fsm *fsm) } /* Uninitialize timers */ -void fsl_otg_uninit_timers(void) +static void fsl_otg_uninit_timers(void) { /* FSM used timers */ kfree(a_wait_vrise_tmr); @@ -391,7 +391,7 @@ static struct fsl_otg_timer *fsl_otg_get_timer(enum otg_fsm_timer t) } /* Add timer to timer list */ -void fsl_otg_add_timer(struct otg_fsm *fsm, void *gtimer) +static void fsl_otg_add_timer(struct otg_fsm *fsm, void *gtimer) { struct fsl_otg_timer *timer = gtimer; struct fsl_otg_timer *tmp_timer; @@ -421,7 +421,7 @@ static void fsl_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) } /* Remove timer from the timer list; clear timeout status */ -void fsl_otg_del_timer(struct otg_fsm *fsm, void *gtimer) +static void fsl_otg_del_timer(struct otg_fsm *fsm, void *gtimer) { struct fsl_otg_timer *timer = gtimer; struct fsl_otg_timer *tmp_timer, *del_tmp; @@ -443,7 +443,7 @@ static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) } /* Reset controller, not reset the bus */ -void otg_reset_controller(void) +static void otg_reset_controller(void) { u32 command; @@ -455,7 +455,7 @@ void otg_reset_controller(void) } /* Call suspend/resume routines in host driver */ -int fsl_otg_start_host(struct otg_fsm *fsm, int on) +static int fsl_otg_start_host(struct otg_fsm *fsm, int on) { struct usb_otg *otg = fsm->otg; struct device *dev; @@ -522,7 +522,7 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on) * Call suspend and resume function in udc driver * to stop and start udc driver. */ -int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +static int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) { struct usb_otg *otg = fsm->otg; struct device *dev; @@ -704,7 +704,7 @@ static int fsl_otg_start_hnp(struct usb_otg *otg) * intact. It needs to have knowledge of some USB interrupts * such as port change. */ -irqreturn_t fsl_otg_isr(int irq, void *dev_id) +static irqreturn_t fsl_otg_isr(int irq, void *dev_id) { struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; struct usb_otg *otg = ((struct fsl_otg *)dev_id)->phy.otg; @@ -830,7 +830,7 @@ static int fsl_otg_conf(struct platform_device *pdev) } /* OTG Initialization */ -int usb_otg_start(struct platform_device *pdev) +static int usb_otg_start(struct platform_device *pdev) { struct fsl_otg *p_otg; struct usb_phy *otg_trans = usb_get_phy(USB_PHY_TYPE_USB2); @@ -1002,7 +1002,7 @@ static void fsl_otg_remove(struct platform_device *pdev) pdata->exit(pdev); } -struct platform_driver fsl_otg_driver = { +static struct platform_driver fsl_otg_driver = { .probe = fsl_otg_probe, .remove = fsl_otg_remove, .driver = { diff --git a/drivers/usb/phy/phy-fsl-usb.h b/drivers/usb/phy/phy-fsl-usb.h index 95bfe7f1b83a..b754077875c3 100644 --- a/drivers/usb/phy/phy-fsl-usb.h +++ b/drivers/usb/phy/phy-fsl-usb.h @@ -373,6 +373,6 @@ struct fsl_otg_config { #define FSL_OTG_NAME "fsl-usb2-otg" -void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer); -void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer); -void fsl_otg_pulse_vbus(void); +static void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer); +static void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer); +static void fsl_otg_pulse_vbus(void); From 205dc9cb39f52150861ed5adaad90274389d19cf Mon Sep 17 00:00:00 2001 From: RD Babiera Date: Fri, 17 Jul 2026 23:26:12 +0000 Subject: [PATCH 104/163] usb: typec: tcpm: implement retry mechanism for Discover Identity VDMs The current mechanism for sending Discover Identity in the ready state presents a flaw where tcpm_queue_vdm can collide with non interruptible AMSes such as GET_SINK_CAP or VCONN_SWAP. vdm_run_state_machine will hit the VDM_STATE_BUSY state, and Discover SVIDs or Discover Modes will not retry. This patch introduces a state machine under the enum vdm_discovery_states. The tcpm_port field vdm_discovery_state tracks which step of the Discover Identity process has been completed. The current TCPM implementation utilizes the send_discover and send_discover_prime booleans to queue Discover Identity in the aforementioned collision case. These booleans are removed in place of vdm_discovery_state and send_discover_work is replaced by vdm_discovery_work, which runs unconditionally in the ready state. When the Discovery process is complete, the port will move to the VDM_DISCOVERY_COMPLETE state and vdm_discovery_work becomes a no-op. When there are still Discovery VDMs to be sent, vdm_discovery_work will continue based on the last received response from the port partner or cable. Signed-off-by: RD Babiera Reviewed-by: Heikki Krogerus Reviewed-by: Badhri Jagan Sridharan Link: https://patch.msgid.link/20260717232612.3671978-2-rdbabiera@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 403 ++++++++++++++++++++++------------ 1 file changed, 260 insertions(+), 143 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 1b56941fa2fa..a8cd1959c426 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -213,6 +213,24 @@ static const char * const tcpm_ams_str[] = { FOREACH_AMS(GENERATE_STRING) }; +#define FOREACH_VDM_DISCOVERY(S) \ + S(VDM_DISCOVERY_UNKNOWN), \ + S(VDM_DISCOVERY_PARTNER_IDENT), \ + S(VDM_DISCOVERY_CABLE_IDENT), \ + S(VDM_DISCOVERY_PARTNER_SVIDS), \ + S(VDM_DISCOVERY_PARTNER_MODES), \ + S(VDM_DISCOVERY_CABLE_SVIDS), \ + S(VDM_DISCOVERY_CABLE_MODES), \ + S(VDM_DISCOVERY_COMPLETE) + +enum vdm_discovery_states { + FOREACH_VDM_DISCOVERY(GENERATE_ENUM) +}; + +static const char * const vdm_discovery_state_strings[] = { + FOREACH_VDM_DISCOVERY(GENERATE_STRING) +}; + enum vdm_states { VDM_STATE_ERR_BUSY = -3, VDM_STATE_ERR_SEND = -2, @@ -277,7 +295,7 @@ enum frs_typec_current { #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) #define GET_SINK_CAP_RETRY_MS 100 -#define SEND_DISCOVER_RETRY_MS 100 +#define SEND_DISCOVERY_VDM_RETRY_MS 100 struct pd_mode_data { int svid_index; /* current SVID index */ @@ -494,8 +512,6 @@ struct tcpm_port { bool vbus_source; bool vbus_charge; - /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */ - bool send_discover; bool op_vsafe5v; int try_role; @@ -521,8 +537,8 @@ struct tcpm_port { struct kthread_work vdm_state_machine; struct hrtimer enable_frs_timer; struct kthread_work enable_frs; - struct hrtimer send_discover_timer; - struct kthread_work send_discover_work; + struct hrtimer vdm_discovery_timer; + struct kthread_work vdm_discovery_work; bool state_machine_running; /* Set to true when VDM State Machine has following actions. */ bool vdm_sm_running; @@ -589,6 +605,9 @@ struct tcpm_port { u32 bist_request; + /* VDM Discovery State to determine message sent */ + enum vdm_discovery_states vdm_discovery_state; + /* PD state for Vendor Defined Messages */ enum vdm_states vdm_state; u32 vdm_retries; @@ -652,12 +671,6 @@ struct tcpm_port { bool potential_contaminant; /* SOP* Related Fields */ - /* - * Flag to determine if SOP' Discover Identity is available. The flag - * is set if Discover Identity on SOP' does not immediately follow - * Discover Identity on SOP. - */ - bool send_discover_prime; /* * tx_sop_type determines which SOP* a message is being sent on. * For messages that are queued and not sent immediately such as in @@ -785,6 +798,9 @@ static const char * const pd_rev[] = { #define tcpm_wait_for_discharge(port) \ (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) +#define tcpm_can_send_vdm(state) \ + ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) { if (port->port_type == TYPEC_PORT_DRP) { @@ -1737,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del } } -static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) +{ + hrtimer_cancel(&port->vdm_discovery_timer); + kthread_cancel_work_sync(&port->vdm_discovery_work); +} + +static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) { if (delay_ms) { - hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); } else { - hrtimer_cancel(&port->send_discover_timer); - kthread_queue_work(port->wq, &port->send_discover_work); + hrtimer_cancel(&port->vdm_discovery_timer); + kthread_queue_work(port->wq, &port->vdm_discovery_work); } } @@ -1951,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, WARN_ON(!mutex_is_locked(&port->lock)); /* If is sending discover_identity, handle received message first */ - if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { - if (tx_sop_type == TCPC_TX_SOP_PRIME) - port->send_discover_prime = true; - else - port->send_discover = true; - mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); - } else { + if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) + mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + else /* Make sure we are not still processing a previous VDM packet */ WARN_ON(port->vdm_state > VDM_STATE_DONE); - } port->vdo_count = cnt + 1; port->vdo_data[0] = header; @@ -1983,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) struct tcpm_port *port = event->port; mutex_lock(&port->lock); - if (port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) { + if (!tcpm_can_send_vdm(port->state)) { tcpm_log_force(port, "dropping altmode_vdm_event"); goto port_unlock; } @@ -2333,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port) tcpm_can_communicate_sop_prime(port); } +static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, + enum vdm_discovery_states new_state) +{ + enum vdm_discovery_states old_state = port->vdm_discovery_state; + + if (old_state != new_state) + tcpm_log_force(port, "vdm discovery state changed: %s -> %s", + vdm_discovery_state_strings[old_state], + vdm_discovery_state_strings[new_state]); + + port->vdm_discovery_state = new_state; +} + static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, enum tcpm_transmit_type rx_sop_type, enum tcpm_transmit_type *response_tx_sop_type) @@ -2350,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, response[0] = VDO(svid, 1, typec_get_negotiated_svdm_version(typec), CMD_DISCOVER_MODES); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); return 1; } @@ -2358,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, response[0] = VDO(USB_SID_PD, 1, typec_get_cable_svdm_version(typec), CMD_DISCOVER_SVID); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); return 1; } tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { modep = &port->mode_data_prime; modep->svid_index++; @@ -2372,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, response[0] = VDO(svid, 1, typec_get_cable_svdm_version(typec), CMD_DISCOVER_MODES); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); return 1; } tcpm_register_plug_altmodes(port); tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } return 0; @@ -2555,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, typec_cable_set_svdm_version(port->cable, svdm_version); } + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); + /* 6.4.4.3.1 */ svdm_consume_identity(port, p, cnt); /* Attempt Vconn swap, delay SOP' discovery if necessary */ if (tcpm_attempt_vconn_swap_discovery(port)) { - port->send_discover_prime = true; port->upcoming_state = VCONN_SWAP_SEND; ret = tcpm_ams_start(port, VCONN_SWAP); if (!ret) return 0; /* Cannot perform Vconn swap */ port->upcoming_state = INVALID_STATE; - port->send_discover_prime = false; } /* @@ -2577,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, if (IS_ERR_OR_NULL(port->cable) && tcpm_can_communicate_sop_prime(port)) { *response_tx_sop_type = TCPC_TX_SOP_PRIME; - port->send_discover_prime = true; response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec), CMD_DISCOVER_IDENT); @@ -2605,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); return 0; } + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); *response_tx_sop_type = TCPC_TX_SOP; response[0] = VDO(USB_SID_PD, 1, @@ -2624,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, rlen = 1; } else { if (rx_sop_type == TCPC_TX_SOP) { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_PARTNER_SVIDS); if (modep->nsvids && supports_modal(port)) { response[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); rlen = 1; + } else { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_COMPLETE); } } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_CABLE_SVIDS); if (modep_prime->nsvids) { response[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); rlen = 1; + } else { + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_COMPLETE); } } } @@ -2684,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, case CMDT_RSP_NAK: tcpm_ams_finish(port); switch (cmd) { + /* + * The cable is not allowed to respond with NAK so this must've happened over SOP + */ case CMD_DISCOVER_IDENT: case CMD_DISCOVER_SVID: + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): break; case CMD_DISCOVER_MODES: @@ -2865,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, port->vdm_sm_running = false; } -static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, - const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) -{ - int svdm_version; - u32 header; - - switch (tx_sop_type) { - case TCPC_TX_SOP_PRIME: - /* - * If the port partner is discovered, then the port partner's - * SVDM Version will be returned - */ - svdm_version = typec_get_cable_svdm_version(port->typec_port); - if (svdm_version < 0) - svdm_version = SVDM_VER_MAX; - break; - case TCPC_TX_SOP: - svdm_version = typec_get_negotiated_svdm_version(port->typec_port); - if (svdm_version < 0) - return; - break; - default: - svdm_version = typec_get_negotiated_svdm_version(port->typec_port); - if (svdm_version < 0) - return; - break; - } - - if (WARN_ON(count > VDO_MAX_SIZE - 1)) - count = VDO_MAX_SIZE - 1; - - /* set VDM header with VID & CMD */ - header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? - 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), - svdm_version, cmd); - tcpm_queue_vdm(port, header, data, count, tx_sop_type); -} - static unsigned int vdm_ready_timeout(u32 vdm_hdr) { unsigned int timeout; @@ -2948,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) * if there's traffic or we're not in PDO ready state don't send * a VDM. */ - if (port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) { + if (!tcpm_can_send_vdm(port->state)) { port->vdm_sm_running = false; break; } @@ -2959,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) switch (PD_VDO_CMD(vdo_hdr)) { case CMD_DISCOVER_IDENT: res = tcpm_ams_start(port, DISCOVER_IDENTITY); - if (res == 0) { - switch (port->tx_sop_type) { - case TCPC_TX_SOP_PRIME: - port->send_discover_prime = false; - break; - case TCPC_TX_SOP: - port->send_discover = false; - break; - default: - port->send_discover = false; - break; - } - } else if (res == -EAGAIN) { + if (res == -EAGAIN) { port->vdo_data[0] = 0; - mod_send_discover_delayed_work(port, - SEND_DISCOVER_RETRY_MS); + mod_vdm_discovery_delayed_work(port, + SEND_DISCOVERY_VDM_RETRY_MS); } break; case CMD_DISCOVER_SVID: @@ -3032,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) */ if (port->state == SRC_VDM_IDENTITY_REQUEST) { tcpm_ams_finish(port); + port->vdo_data[0] = 0; port->vdm_state = VDM_STATE_DONE; tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); /* @@ -3048,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) tcpm_ams_finish(port); } else { tcpm_ams_finish(port); + port->vdo_data[0] = 0; if (port->tx_sop_type == TCPC_TX_SOP) break; /* Handle SOP' Transmission Errors */ @@ -3057,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) * discovery process on SOP only. */ case CMD_DISCOVER_IDENT: - port->vdo_data[0] = 0; response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version( port->typec_port), CMD_DISCOVER_SVID); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); tcpm_queue_vdm(port, response[0], &response[1], 0, TCPC_TX_SOP); break; @@ -3071,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) */ case CMD_DISCOVER_SVID: tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); break; case CMD_DISCOVER_MODES: tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); break; default: break; @@ -3986,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); } else { - if (port->send_discover && port->negotiated_rev < PD_REV30) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -4002,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); } else { - if (port->send_discover && port->negotiated_rev < PD_REV30) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -4011,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, } break; case PD_CTRL_VCONN_SWAP: - if (port->send_discover && port->negotiated_rev < PD_REV30) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -5065,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port) port->partner = NULL; port->attached = true; - port->send_discover = true; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); return 0; @@ -5143,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port) port->in_ams = false; port->ams = NONE_AMS; port->vdm_sm_running = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + mod_vdm_discovery_cancel_delayed_work(port); tcpm_unregister_altmodes(port); tcpm_typec_disconnect(port); port->attached = false; @@ -5226,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) port->partner = NULL; port->attached = true; - port->send_discover = true; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); return 0; } @@ -5634,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port) * as well. */ if (port->explicit_contract) { - if (port->send_discover_prime) { - port->tx_sop_type = TCPC_TX_SOP_PRIME; - } else { - port->tx_sop_type = TCPC_TX_SOP; + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) tcpm_set_initial_svdm_version(port); - } - mod_send_discover_delayed_work(port, 0); + mod_vdm_discovery_delayed_work(port, 0); } else { - port->send_discover = false; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } /* @@ -6026,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port) * as well. */ if (port->explicit_contract) { - if (port->send_discover_prime) { - port->tx_sop_type = TCPC_TX_SOP_PRIME; - } else { - port->tx_sop_type = TCPC_TX_SOP; + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) tcpm_set_initial_svdm_version(port); - } - mod_send_discover_delayed_work(port, 0); + mod_vdm_discovery_delayed_work(port, 0); } else { - port->send_discover = false; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } power_supply_changed(port->psy); @@ -6081,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port) port->tcpc->set_pd_rx(port->tcpc, false); tcpm_unregister_altmodes(port); port->nr_sink_caps = 0; - port->send_discover = true; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + mod_vdm_discovery_cancel_delayed_work(port); if (port->pwr_role == TYPEC_SOURCE) tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, PD_T_PS_HARD_RESET); @@ -6229,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port) /* DR_Swap states */ case DR_SWAP_SEND: tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { - port->send_discover = true; - port->send_discover_prime = false; - } tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; case DR_SWAP_ACCEPT: tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { - port->send_discover = true; - port->send_discover_prime = false; - } tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); break; case DR_SWAP_SEND_TIMEOUT: tcpm_swap_complete(port, -ETIMEDOUT); - port->send_discover = false; - port->send_discover_prime = false; tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -6259,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port) else tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, TYPEC_HOST); + if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -6542,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port) /* Cable states */ case SRC_VDM_IDENTITY_REQUEST: - port->send_discover_prime = true; - port->tx_sop_type = TCPC_TX_SOP_PRIME; - mod_send_discover_delayed_work(port, 0); + mod_vdm_discovery_delayed_work(port, 0); port->upcoming_state = SRC_SEND_CAPABILITIES; break; @@ -7248,8 +7234,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) goto unlock; /* Send when the state machine is idle */ - if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || - port->send_discover_prime) + if (port->state != SNK_READY || port->vdm_sm_running || + port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) goto resched; port->upcoming_state = GET_SINK_CAP; @@ -7266,29 +7252,160 @@ static void tcpm_enable_frs_work(struct kthread_work *work) mutex_unlock(&port->lock); } -static void tcpm_send_discover_work(struct kthread_work *work) +static void tcpm_vdm_discovery_work(struct kthread_work *work) { - struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work); + struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); + enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; + struct typec_port *typec = port->typec_port; + struct pd_mode_data *modep, *modep_prime; + u32 msg[2] = { }; + int svdm_version; mutex_lock(&port->lock); - /* No need to send DISCOVER_IDENTITY anymore */ - if (!port->send_discover && !port->send_discover_prime) - goto unlock; - if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { - port->send_discover = false; - port->send_discover_prime = false; + tcpm_log_force(port, "%s state [%s]", __func__, + vdm_discovery_state_strings[port->vdm_discovery_state]); + + /* No need to perform work if Discovery process is complete */ + if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) goto unlock; - } /* Retry if the port is not idle */ - if ((port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { - mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); + if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { + mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); goto unlock; } - tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); + modep = &port->mode_data; + modep_prime = &port->mode_data_prime; + + svdm_version = typec_get_negotiated_svdm_version(typec); + + switch (port->vdm_discovery_state) { + /* + * The port has not received a Discover Identity response from the port partner. + * + * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and + * SNK_READY states if there is an explicit contract + * 2. The port will send Discover Identity to the cable over SOP' in the + * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. + */ + case VDM_DISCOVERY_UNKNOWN: + /* Can't send Discover Identity, VDM discovery is complete */ + if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = SVDM_VER_MAX; + } + + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); + break; + /* + * The port has received a Discover Identity ACK from the port partner. + * + * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and + * SNK_READY states if it did not previously discover the cable but is capable of doing + * so. + * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and + * SNK_READY states otherwise. + */ + case VDM_DISCOVERY_PARTNER_IDENT: + if (tcpm_can_communicate_sop_prime(port) && !port->cable) { + tx_sop_type = TCPC_TX_SOP_PRIME; + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); + } else { + if (tcpm_can_communicate_sop_prime(port)) + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + } + break; + /* + * The port has received a Discover Identity ACK from the cable. + * + * 1. The port will send Discover SVIDs to the partner over SOP. + */ + case VDM_DISCOVERY_CABLE_IDENT: + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + break; + /* + * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported + * by the partner. + * + * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports + * modal operation and valid SVIDs were registered. + * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. + */ + case VDM_DISCOVERY_PARTNER_SVIDS: + if (modep->nsvids && supports_modal(port)) { + msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + break; + /* + * The port has received a Discover Modes ACK from the partner for any mode. + * + * 1. The port will send Discover Modes for the next SVID that has not been discovered to + * the port partner over SOP. + * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' + * and the cable supports VDMs. + * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. + */ + case VDM_DISCOVERY_PARTNER_MODES: + /* Not all modes have been discovered yet */ + if (modep->svid_index < modep->nsvids) { + msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, + CMD_DISCOVER_MODES); + } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + break; + /* + * The port has received a Discover SVIDs ACK from the cable over SOP'. + * + * 1. The port will send Discover Modes for the first SVID over SOP'. + */ + case VDM_DISCOVERY_CABLE_SVIDS: + if (modep_prime->nsvids) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); + } else { + goto unlock; + } + break; + /* + * The port has received a Discover Modes ACK from the cable for any mode. + * + * 1. The port will send Discover Modes for the next SVID that has not been discovered to + * the cable over SOP'. + */ + case VDM_DISCOVERY_CABLE_MODES: + if (modep_prime->svid_index < modep_prime->nsvids) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, + CMD_DISCOVER_MODES); + } else { + goto unlock; + } + break; + default: + goto unlock; + } + + if (svdm_version >= 0) + tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); unlock: mutex_unlock(&port->lock); @@ -8704,12 +8821,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) return HRTIMER_NORESTART; } -static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer) +static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) { - struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); + struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); if (port->registered) - kthread_queue_work(port->wq, &port->send_discover_work); + kthread_queue_work(port->wq, &port->vdm_discovery_work); return HRTIMER_NORESTART; } @@ -8743,14 +8860,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); kthread_init_work(&port->event_work, tcpm_pd_event_handler); kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); - kthread_init_work(&port->send_discover_work, tcpm_send_discover_work); + kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC, + hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); spin_lock_init(&port->pd_event_lock); @@ -8845,7 +8962,7 @@ void tcpm_unregister_port(struct tcpm_port *port) port->registered = false; kthread_destroy_worker(port->wq); - hrtimer_cancel(&port->send_discover_timer); + hrtimer_cancel(&port->vdm_discovery_timer); hrtimer_cancel(&port->enable_frs_timer); hrtimer_cancel(&port->vdm_state_machine_timer); hrtimer_cancel(&port->state_machine_timer); From 913910ed36ba14354ea6e1d4978e2e4c14c3df28 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 20 Jul 2026 23:09:57 -0300 Subject: [PATCH 105/163] usb: gadget: f_mass_storage: Remove obsolete version log The mass-storage function prints the following message whenever a function instance is allocated: Mass Storage Function, version: 2009/09/11 The hard-coded date does not identify the running kernel or provide useful diagnostic information. Remove the message and the unused version definition. Signed-off-by: Fabio Estevam Link: https://patch.msgid.link/20260721020957.81956-1-festevam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index b7b06cb79ff5..a50743caf083 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -203,7 +203,6 @@ /*------------------------------------------------------------------------*/ #define FSG_DRIVER_DESC "Mass Storage Function" -#define FSG_DRIVER_VERSION "2009/09/11" static const char fsg_string_interface[] = "Mass Storage"; @@ -3513,8 +3512,6 @@ static struct usb_function_instance *fsg_alloc_inst(void) if (rc) goto release_common; - pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); - memset(&config, 0, sizeof(config)); config.removable = true; rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", From a29496745aa335d97f617385809583241e118610 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Wed, 22 Jul 2026 10:17:39 +0200 Subject: [PATCH 106/163] usb: core: Strengthen error handling in hub_hub_status() Add additional error handling after the call to get_hub_status() in hub_hub_status(). get_hub_status() uses usb_control_msg() which does not verify that the message is the correct length, substituting it for usb_control_msg_recv() would also solve this issue but increase memory allocations. Instead, error handling is copied from the method used in hub_ext_port_status(), which shares the same flow of logic as hub_hub_status(). Assisted-by: gkh_clanker_t1000 Signed-off-by: Griffin Kroah-Hartman Link: https://patch.msgid.link/20260722-usb_core_patches_2-v3-1-87622252bfdd@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5798efdd91a9..74a365ca08bd 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -994,10 +994,12 @@ static int hub_hub_status(struct usb_hub *hub, mutex_lock(&hub->status_mutex); ret = get_hub_status(hub->hdev, &hub->status->hub); - if (ret < 0) { + if (ret < (int)sizeof(hub->status->hub)) { if (ret != -ENODEV) dev_err(hub->intfdev, "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; } else { *status = le16_to_cpu(hub->status->hub.wHubStatus); *change = le16_to_cpu(hub->status->hub.wHubChange); From cae6572efdd0948a7b676b3c5a7cebf9483bc781 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Wed, 22 Jul 2026 10:17:40 +0200 Subject: [PATCH 107/163] usb: core: reformat error handling and messages Rearrange the error handling changes in the previous patch, in both hub_ext_port_status() and hub_hub_status(), in respect to maintainer feedback. Additionally, change the two usages of dev_err() in these functions to dev_dbg(), and reformat the error messages to be more accurate. Suggested-by: Alan Stern Signed-off-by: Griffin Kroah-Hartman Link: https://patch.msgid.link/20260722-usb_core_patches_2-v3-2-87622252bfdd@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 74a365ca08bd..437ff2a0f301 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, mutex_lock(&hub->status_mutex); ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); if (ret < len) { - if (ret != -ENODEV) - dev_err(hub->intfdev, - "%s failed (err = %d)\n", __func__, ret); if (ret >= 0) ret = -EIO; + if (ret != -ENODEV) + dev_dbg(hub->intfdev, + "get_port_status failed: err = %d\n", ret); } else { *status = le16_to_cpu(hub->status->port.wPortStatus); *change = le16_to_cpu(hub->status->port.wPortChange); @@ -995,11 +995,11 @@ static int hub_hub_status(struct usb_hub *hub, mutex_lock(&hub->status_mutex); ret = get_hub_status(hub->hdev, &hub->status->hub); if (ret < (int)sizeof(hub->status->hub)) { - if (ret != -ENODEV) - dev_err(hub->intfdev, - "%s failed (err = %d)\n", __func__, ret); if (ret >= 0) ret = -EIO; + if (ret != -ENODEV) + dev_dbg(hub->intfdev, + "get_hub_status failed: err = %d\n", ret); } else { *status = le16_to_cpu(hub->status->hub.wHubStatus); *change = le16_to_cpu(hub->status->hub.wHubChange); From 5f40cba7d4fa343cf074d1a764683c1392f3134e Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Thu, 11 Jun 2026 11:11:57 +0530 Subject: [PATCH 108/163] thunderbolt: Assert downstream port reset on shutdown On shutdown the connection manager tears down the router tree without signalling connected devices. A Thunderbolt 3 device directly connected to a USB4 host never receives a disconnect indication and during shutdown this can cause polling the dead link for up to 60 seconds. On some platforms this behavior leads to a warm reset instead of a shutdown due to this timeout. Fix this by asserting PORT_CS_19.DPR on each connected downstream port before tearing down the router tree. This drives SBTX low (USB4 spec section 6.9), causing the device to detect SBRX low and transition to Uninitialized Unplugged state immediately. Always do this on system shutdown/reboot by forcing host_reset in the PCI ->shutdown callback. On plain driver unload only do it when the host router was actually reset on load (host_reset=1), since in that case the tunnels are not preserved across reload anyway; with host_reset=0 the tunnels are kept alive across unload/reload so the links are left intact. Restrict the reset to Thunderbolt 3 devices. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Sanath S Signed-off-by: Sanath S Signed-off-by: Basavaraj Natikar Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 2 ++ drivers/thunderbolt/pci.c | 28 +++++++++++++++++++++++----- drivers/thunderbolt/switch.c | 11 ++++++++++- drivers/thunderbolt/tb.c | 21 +++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + include/linux/thunderbolt.h | 6 ++++++ 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 0f795ea58756..698fb124d529 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1235,6 +1235,8 @@ int nhi_probe(struct tb_nhi *nhi) init_completion(&nhi->domain_released); + nhi->host_reset = host_reset; + res = tb_domain_add(tb, host_reset); if (res) { /* diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index bbd186c29ef7..dbb6badda867 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -230,7 +230,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring) ring->irq = 0; } -static void nhi_pci_shutdown(struct tb_nhi *nhi) +static void nhi_pci_release_irq(struct tb_nhi *nhi) { struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); struct pci_dev *pdev = to_pci_dev(nhi->dev); @@ -256,7 +256,7 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { .post_nvm_auth = nhi_pci_complete_dma_port, .request_ring_irq = nhi_pci_ring_request_msix, .release_ring_irq = nhi_pci_ring_release_msix, - .shutdown = nhi_pci_shutdown, + .shutdown = nhi_pci_release_irq, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, }; @@ -424,7 +424,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi) static void icl_nhi_shutdown(struct tb_nhi *nhi) { - nhi_pci_shutdown(nhi); + nhi_pci_release_irq(nhi); icl_nhi_force_power(nhi, false); } @@ -479,11 +479,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) return nhi_probe(&nhi_pci->nhi); } -static void nhi_pci_remove(struct pci_dev *pdev) +static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset) { struct tb *tb = pci_get_drvdata(pdev); struct tb_nhi *nhi = tb->nhi; + /* + * On system shutdown/reboot force a host router reset so the + * connection manager asserts DPR on connected Thunderbolt 3 devices + * before the router tree is removed (see tb_stop()). + */ + if (reset) + nhi->host_reset = true; + pm_runtime_get_sync(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_forbid(&pdev->dev); @@ -493,6 +501,16 @@ static void nhi_pci_remove(struct pci_dev *pdev) nhi_shutdown(nhi); } +static void nhi_pci_remove(struct pci_dev *pdev) +{ + nhi_pci_do_remove(pdev, false); +} + +static void nhi_pci_shutdown(struct pci_dev *pdev) +{ + nhi_pci_do_remove(pdev, true); +} + static struct pci_device_id nhi_ids[] = { /* * We have to specify class, the TB bridges use the same device and @@ -593,7 +611,7 @@ static struct pci_driver nhi_driver = { .id_table = nhi_ids, .probe = nhi_pci_probe, .remove = nhi_pci_remove, - .shutdown = nhi_pci_remove, + .shutdown = nhi_pci_shutdown, .driver.pm = &nhi_pm_ops, }; diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index a830c82bb905..404c0693df50 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port) return __tb_port_enable(port, false); } -static int tb_port_reset(struct tb_port *port) +/** + * tb_port_reset() - Reset the port + * @port: Port to reset + * + * Resets @port. For USB4 ports this issues a USB4 port reset and for + * legacy ports the link controller port is reset. + * + * Return: %0 on success, negative errno otherwise. + */ +int tb_port_reset(struct tb_port *port) { if (tb_switch_is_usb4(port->sw)) return port->cap_usb4 ? usb4_port_reset(port) : 0; diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 76323255439a..b7cc6894a598 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, static void tb_stop(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); + struct tb_nhi *nhi = tb->nhi; struct tb_tunnel *tunnel; + struct tb_port *port; struct tb_tunnel *n; cancel_delayed_work(&tcm->remove_work); @@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb) tb_tunnel_deactivate(tunnel); tb_tunnel_put(tunnel); } + /* + * Signal disconnect to connected devices before the router tree is + * removed below. A Thunderbolt 3 device directly connected to a USB4 + * host otherwise never receives a disconnect indication, leaving + * firmware to poll the dead link for up to ~60 s which on some + * platforms turns the shutdown into a warm reset. Asserting + * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device + * detects SBRX low and goes to Uninitialized Unplugged immediately. + */ + if (nhi->host_reset) { + tb_switch_for_each_port(tb->root_switch, port) { + if (!tb_port_is_null(port) || !tb_port_has_remote(port)) + continue; + if (tb_switch_is_usb4(port->remote->sw)) + continue; + if (tb_port_reset(port)) + tb_port_dbg(port, "downstream port reset failed, continuing\n"); + } + } tb_switch_remove(tb->root_switch); tb->root_switch = NULL; tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index ec9192b61bc0..4373336d9425 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter); int tb_port_unlock(struct tb_port *port); int tb_port_enable(struct tb_port *port); int tb_port_disable(struct tb_port *port); +int tb_port_reset(struct tb_port *port); int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); void tb_port_release_in_hopid(struct tb_port *port, int hopid); int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index feb1af175cfd..cb1621c6b703 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc); * @hop_count: Number of rings (end point hops) supported by NHI. * @quirks: NHI specific quirks if any * @domain_released: Completed when domain has been fully released + * @host_reset: Host router was reset on driver load, or forced on system + * shutdown/reboot. When set, tb_stop() asserts DPR on connected + * downstream ports to signal disconnect before tearing down the + * router tree. Only Thunderbolt 3 devices are reset; USB4 + * routers are skipped. */ struct tb_nhi { spinlock_t lock; @@ -528,6 +533,7 @@ struct tb_nhi { u32 hop_count; unsigned long quirks; struct completion domain_released; + bool host_reset; }; /** From 45f104755af82afe24e104adeab69fe390fbeeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 18 Jun 2026 12:14:50 +0200 Subject: [PATCH 109/163] thunderbolt: Stop passing matched device ID to .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No driver makes use of that parameter, so drop it and don't spend the effort to determine the matching entry. Signed-off-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Mika Westerberg --- drivers/net/thunderbolt/main.c | 2 +- drivers/thunderbolt/dma_test.c | 2 +- drivers/thunderbolt/domain.c | 4 +--- drivers/thunderbolt/stream.c | 2 +- include/linux/thunderbolt.h | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 02a91650561a..4b8af0b41ec5 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -1339,7 +1339,7 @@ static void tbnet_generate_mac(struct net_device *dev) dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; } -static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id) +static int tbnet_probe(struct tb_service *svc) { struct tb_xdomain *xd = tb_service_parent(svc); struct net_device *dev; diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index 7877319b1b03..63e6bbf00e12 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc) debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops); } -static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) +static int dma_test_probe(struct tb_service *svc) { struct tb_xdomain *xd = tb_service_parent(svc); struct dma_test *dt; diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 479fa4d265c2..24611f05b3cd 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev) { struct tb_service *svc = tb_to_service(dev); struct tb_service_driver *driver; - const struct tb_service_id *id; driver = container_of(dev->driver, struct tb_service_driver, driver); - id = __tb_service_match(dev, &driver->driver); - return driver->probe(svc, id); + return driver->probe(svc); } static void tb_service_remove(struct device *dev) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index c1f5c55583d0..b28e4e95b422 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -1540,7 +1540,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream) config_group_put(&sg->group); } -static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) +static int tbstream_probe(struct tb_service *svc) { struct tbstream *stream; diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index cb1621c6b703..0a9ac4bfea67 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) */ struct tb_service_driver { struct device_driver driver; - int (*probe)(struct tb_service *svc, const struct tb_service_id *id); + int (*probe)(struct tb_service *svc); void (*remove)(struct tb_service *svc); void (*shutdown)(struct tb_service *svc); const struct tb_service_id *id_table; From 36eea3468c2c87bba132def3bf6ebf05c3c4b9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 18 Jun 2026 12:14:51 +0200 Subject: [PATCH 110/163] thunderbolt: Assert that a service driver has a probe callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tb_service_probe() calls the driver's probe function unconditionally. Check at driver register time that this callback is valid to prevent a NULL pointer exception. Signed-off-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 86b2f7474670..05442df0e99c 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, */ int tb_register_service_driver(struct tb_service_driver *drv) { + if (!drv->probe) + return -EINVAL; + drv->driver.bus = &tb_bus_type; return driver_register(&drv->driver); } From 9990c493ef109377eabe3cc1d337861b7bd1fd9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 18 Jun 2026 12:14:52 +0200 Subject: [PATCH 111/163] thunderbolt: Drop comma after device id array terminator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usual style for other device id arrays doesn't have a comma after the initializer. Signed-off-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Mika Westerberg --- drivers/net/thunderbolt/main.c | 2 +- drivers/thunderbolt/dma_test.c | 2 +- drivers/thunderbolt/stream.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 4b8af0b41ec5..eab443ffe0fd 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -1459,7 +1459,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume); static const struct tb_service_id tbnet_ids[] = { { TB_SERVICE("network", 1) }, - { }, + { } }; MODULE_DEVICE_TABLE(tbsvc, tbnet_ids); diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index 63e6bbf00e12..519c67678b08 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = { static const struct tb_service_id dma_test_ids[] = { { TB_SERVICE("dma_test", 1) }, - { }, + { } }; MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index b28e4e95b422..68d81958262e 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -1630,7 +1630,7 @@ static const struct dev_pm_ops tbstream_pm_ops = { static const struct tb_service_id tbstream_ids[] = { { TB_SERVICE("stream", 1) }, - { }, + { } }; MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); From e027dba038f0008df9bc9575f5c3e803e90636c6 Mon Sep 17 00:00:00 2001 From: Milo Chen Date: Wed, 24 Jun 2026 14:09:09 +0800 Subject: [PATCH 112/163] thunderbolt: xdomain: Notify peers after enumeration Service drivers may register local XDomain properties while discovery is still in progress. This can cause the properties changed notification to be sent before the peer is ready to act on it. If the peer has already read the local property block before the service was registered, it may keep using the old property generation and miss the newly registered service. With ThunderboltIP this can leave the network service half-discovered after a warm reboot and the login request eventually times out. Queue another properties changed notification after the XDomain reaches ENUMERATED so the peer can re-read the final local properties. Signed-off-by: Milo Chen Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 05442df0e99c..c179bd751fe4 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -1814,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work) tb_xdomain_failed(xd); } else { xd->state = XDOMAIN_STATE_ENUMERATED; + tb_xdomain_queue_properties_changed(xd); } break; From 21ed236e70bf3d48611c2ce1a438699234780cc7 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 8 Jul 2026 07:49:24 +0200 Subject: [PATCH 113/163] docs: admin-guide: thunderbolt: Fix sentence structure Replace ')' with ',' and add 'in' to sentence. Signed-off-by: Manuel Ebner Acked-by: Randy Dunlap Signed-off-by: Mika Westerberg --- Documentation/admin-guide/thunderbolt.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 91a6cb109988..ff25fe853706 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -294,8 +294,8 @@ for the retimers:: This enumerates and adds the on-board retimers. Now retimer NVM can be upgraded in the same way than with cable connected (see previous -section). However, the retimer is not disconnected as we are offline -mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for +section). However, the retimer is not disconnected as we are in offline +mode, so after writing ``1`` to ``nvm_authenticate`` one should wait for 5 or more seconds before running rescan again:: # echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan From 41365e558e447cc012ad7a6984f55054323fc63c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Tue, 28 Jul 2026 16:05:48 +0800 Subject: [PATCH 114/163] thunderbolt: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() calls. Signed-off-by: Pan Chuang Signed-off-by: Mika Westerberg --- drivers/thunderbolt/pci.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index dbb6badda867..8462ccb59b7e 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -112,7 +112,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) { struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); struct pci_dev *pdev = to_pci_dev(nhi->dev); - struct device *dev = &pdev->dev; int res, irq, nvec; ida_init(&nhi_pci->msix_ida); @@ -139,7 +138,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) res = devm_request_irq(&pdev->dev, irq, nhi_msi, IRQF_NO_SUSPEND, "thunderbolt", nhi); if (res) - return dev_err_probe(dev, res, "request_irq failed, aborting\n"); + return res; } return 0; From 885d802f544ca7bfa8f3984d94233cce715bb6b3 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Sun, 26 Jul 2026 00:27:51 +0800 Subject: [PATCH 115/163] USB: serial: option: fix slab OOB read in interrupt URB callback The interrupt URB buffer is allocated in setup_port_interrupt_in() based on the endpoint's wMaxPacketSize: buffer_size = usb_endpoint_maxp(epd); port->interrupt_in_buffer = kmalloc(buffer_size, GFP_KERNEL); When a USB device declares wMaxPacketSize = 8 on its interrupt IN endpoint, the buffer is allocated from kmalloc-8 cache (exactly 8 bytes). If the device sends a short packet (actual_length < wMaxPacketSize), the URB completes with status == 0 and the callback proceeds to read: data[sizeof(struct usb_ctrlrequest)] which evaluates to data[8], accessing 1 byte beyond the allocated 8-byte buffer. This results in a slab out-of-bounds read. Fix this by adding the missing bounds check: first verify that the actual length is large enough to contain the struct usb_ctrlrequest header before accessing req_pkt->bRequestType and req_pkt->bRequest, and then verify that there is an additional byte for the modem signal state before reading data[sizeof(struct usb_ctrlrequest)] inside the conditional. Use sizeof(*req_pkt) instead of sizeof(struct usb_ctrlrequest) for consistency. Assisted-by: Claude:deepseek-v4-pro Signed-off-by: Jiale Yao Fixes: 58cfe9113e48 ("[PATCH] USB: add Option Card driver") Cc: stable@vger.kernel.org # v2.6.12 [ johan: use dev_err(); split signals declaration and initialisation ] Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 7275f4e7f569..fd8f96294199 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2688,12 +2688,26 @@ static void option_instat_callback(struct urb *urb) dev_dbg(dev, "%s: NULL req_pkt\n", __func__); return; } + + if (urb->actual_length < sizeof(*req_pkt)) { + dev_err(dev, "%s: short packet: %u bytes\n", __func__, + urb->actual_length); + return; + } + if ((req_pkt->bRequestType == 0xA1) && (req_pkt->bRequest == 0x20)) { + unsigned char signals; int old_dcd_state; - unsigned char signals = *((unsigned char *) - urb->transfer_buffer + - sizeof(struct usb_ctrlrequest)); + + if (urb->actual_length < sizeof(*req_pkt) + 1) { + dev_err(dev, "%s: short interrupt transfer: %u bytes\n", + __func__, urb->actual_length); + return; + } + + signals = *((unsigned char *)urb->transfer_buffer + + sizeof(*req_pkt)); dev_dbg(dev, "%s: signal x%x\n", __func__, signals); From 177b48f840c79dbee24c59e311e1f463cf462737 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sat, 1 Aug 2026 14:58:27 +0200 Subject: [PATCH 116/163] USB: serial: digi_acceleport: add port lock nesting annotation The driver takes the driver port lock of both the OOB port and the port itself when setting the modem control signals, which confuses lockdep. Mark the OOB port lock as belonging to a separate subclass to suppress false positive lockdep deadlock warnings. Reported-by: syzbot+2051460e19471eeb42c3@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a6cd832.1aa927e4.17d4bf.0007.GAE@google.com/ Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index dea039163661..911d74f20f0e 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1228,6 +1228,7 @@ static int digi_startup_device(struct usb_serial *serial) static int digi_port_init(struct usb_serial_port *port, unsigned port_num) { + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); struct digi_port *priv; priv = kzalloc_obj(*priv); @@ -1235,6 +1236,10 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) return -ENOMEM; spin_lock_init(&priv->dp_port_lock); + + if (port == serial_priv->ds_oob_port) + lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); + priv->dp_port_num = port_num; init_waitqueue_head(&priv->dp_transmit_idle_wait); init_waitqueue_head(&priv->dp_flush_wait); @@ -1279,6 +1284,8 @@ static int digi_startup(struct usb_serial *serial) serial_priv->ds_oob_port_num = oob_port_num; serial_priv->ds_oob_port = serial->port[oob_port_num]; + usb_set_serial_data(serial, serial_priv); + ret = digi_port_init(serial_priv->ds_oob_port, serial_priv->ds_oob_port_num); if (ret) { @@ -1286,8 +1293,6 @@ static int digi_startup(struct usb_serial *serial) return ret; } - usb_set_serial_data(serial, serial_priv); - return 0; } From 15734de99517b0c81a1a5a3bccaff4593ef8d953 Mon Sep 17 00:00:00 2001 From: Charles Yeh Date: Tue, 21 Jul 2026 19:24:40 +0800 Subject: [PATCH 117/163] USB: serial: pl2303: add support for PL256X multi-port devices Prolific PL256X devices are multi-port USB-to-UART controllers, including the PL2533, PL2543 and PL2565 variants. These devices use vendor requests that differ from those used by the existing TYPE_HX and TYPE_HXN devices. They also require a separate UART reset request and use a port-specific register for configuring flow control. Add a new TYPE_MP device type and select the appropriate vendor requests, reset operation and flow-control register for PL256X devices. Store the USB interface number so that requests can be directed to the corresponding UART port. Detect the supported PL256X variants using bcdDevice before issuing any legacy vendor requests, as PL256X devices do not accept those requests. PL256X devices support baud rates up to 24 Mbps and do not use divisor encoding. Signed-off-by: Charles Yeh Link: https://lore.kernel.org/all/CAAZvQQ6O4p35Xs2hVYaoJxD4D7U0YonsdweuPh6W8RQVhvoUNw@mail.gmail.com/ Signed-off-by: Johan Hovold --- drivers/usb/serial/pl2303.c | 108 +++++++++++++++++++++++++++++++++--- drivers/usb/serial/pl2303.h | 2 +- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 0bcbdcea52af..bf42a545c20f 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), @@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); #define VENDOR_WRITE_REQUEST_TYPE 0x40 #define VENDOR_WRITE_REQUEST 0x01 #define VENDOR_WRITE_NREQUEST 0x80 +#define VENDOR_WRITE_MPREQUEST 0x80 #define VENDOR_READ_REQUEST_TYPE 0xc0 #define VENDOR_READ_REQUEST 0x01 #define VENDOR_READ_NREQUEST 0x81 +#define VENDOR_READ_MPREQUEST 0x80 + +#define PL256X_RESET_REQUEST_TYPE 0x40 +#define PL256X_RESET_REQUEST 0x96 #define UART_STATE_INDEX 8 #define UART_STATE_MSR_MASK 0x8b @@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c +#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 +#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 +#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 +#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 + +#define PL256X_FLOWCTRL_MASK 0x43 +#define PL256X_FLOWCTRL_XON_XOFF 0x40 +#define PL256X_FLOWCTRL_RTS_CTS 0x03 +#define PL256X_FLOWCTRL_NONE 0x00 + static int pl2303_set_break(struct usb_serial_port *port, bool enable); enum pl2303_type { @@ -181,6 +197,7 @@ enum pl2303_type { TYPE_TB, TYPE_HXD, TYPE_HXN, + TYPE_MP, TYPE_COUNT }; @@ -196,6 +213,8 @@ struct pl2303_type_data { struct pl2303_serial_private { const struct pl2303_type_data *type; unsigned long quirks; + u16 interface_num; + u16 flowctrl_reg; }; struct pl2303_private { @@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { .max_baud_rate = 12000000, .no_divisors = true, }, + [TYPE_MP] = { + .name = "MP", + .max_baud_rate = 24000000, + .no_divisors = true, + }, }; +static int pl256x_uart_reset(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int res; + + res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, + 0, spriv->interface_num, NULL, 0, 100); + if (res) { + dev_err(dev, "failed to reset device: %d\n", res); + return res; + } + + return 0; +} + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, unsigned char buf[1]) { @@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, if (spriv->type == &pl2303_type_data[TYPE_HXN]) request = VENDOR_READ_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_READ_MPREQUEST; else request = VENDOR_READ_REQUEST; @@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) if (spriv->type == &pl2303_type_data[TYPE_HXN]) request = VENDOR_WRITE_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_WRITE_MPREQUEST; else request = VENDOR_WRITE_REQUEST; @@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) if (!buf) return -ENOMEM; - if (spriv->type == &pl2303_type_data[TYPE_HXN]) + if (spriv->type == &pl2303_type_data[TYPE_HXN] || + spriv->type == &pl2303_type_data[TYPE_MP]) { ret = pl2303_vendor_read(serial, reg, buf); - else + } else { ret = pl2303_vendor_read(serial, reg | 0x80, buf); + } if (ret) goto out_free; @@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) case 0x905: /* GT-2AB */ case 0x1005: /* GC-Q20 */ return TYPE_HXN; + case 0x3302: /* PL2533 VC 2 Port */ + case 0x3304: /* PL2533 VC 4 Port */ + case 0x4302: /* PL2543 VC 2 Port */ + case 0x4304: /* PL2543 VC 4 Port */ + case 0x6502: /* PL2565 VC 2 Port */ + case 0x6504: /* PL2565 VC 4 Port */ + case 0x6506: /* PL2565 VC 4 Port QFN64 package */ + return TYPE_MP; } break; } @@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) struct pl2303_serial_private *spriv; enum pl2303_type type; unsigned char *buf; + unsigned int ifnum; int ret; ret = pl2303_detect_type(serial); @@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) type = ret; dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ifnum = serial->interface->altsetting->desc.bInterfaceNumber; + spriv = kzalloc_obj(*spriv); if (!spriv) return -ENOMEM; + if (type == TYPE_MP) { + switch (ifnum) { + case 0: + spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; + break; + case 1: + spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; + break; + case 2: + spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; + break; + case 3: + spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; + break; + default: + kfree(spriv); + return -ENODEV; + } + } + spriv->type = &pl2303_type_data[type]; spriv->quirks = (unsigned long)usb_get_serial_data(serial); spriv->quirks |= spriv->type->quirks; + spriv->interface_num = ifnum; if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; usb_set_serial_data(serial, spriv); - if (type != TYPE_HXN) { + if (type != TYPE_HXN && type != TYPE_MP) { buf = kmalloc(1, GFP_KERNEL); if (!buf) { kfree(spriv); @@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) { struct usb_device *dev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); int retval; dev_dbg(&port->dev, "%s - %02x\n", __func__, value); retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, - value, 0, NULL, 0, 100); + value, spriv->interface_num, NULL, 0, 100); if (retval) dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); @@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, - 0, 0, buf, 7, 100); + 0, spriv->interface_num, buf, 7, 100); if (ret != 7) { dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); @@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, unsigned char buf[7]) { struct usb_device *udev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); int ret; ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, - 0, 0, buf, 7, 100); + 0, spriv->interface_num, buf, 7, 100); if (ret < 0) { dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); return ret; @@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_RTS_CTS); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_RTS_CTS); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); } @@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_XON_XOFF); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_XON_XOFF); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); } @@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_NONE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_NONE); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); } @@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, PL2303_HXN_RESET_UPSTREAM_PIPE | PL2303_HXN_RESET_DOWNSTREAM_PIPE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl256x_uart_reset(serial); } else { pl2303_vendor_write(serial, 8, 0); pl2303_vendor_write(serial, 9, 0); @@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), BREAK_REQUEST, BREAK_REQUEST_TYPE, state, - 0, NULL, 0, 100); + spriv->interface_num, NULL, 0, 100); if (result) { dev_err(&port->dev, "error sending break = %d\n", result); return result; diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h index d60eda7f6eda..8eb1e7b5d3ec 100644 --- a/drivers/usb/serial/pl2303.h +++ b/drivers/usb/serial/pl2303.h @@ -26,7 +26,7 @@ #define PL2303_PRODUCT_ID_HCR331 0x331a #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 - +#define PL256X_PRODUCT_ID_4P 0x2533 #define ATEN_VENDOR_ID 0x0557 #define ATEN_VENDOR_ID2 0x0547 From 039701ea3d76ec004863fd7437fb56a9146cb094 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 28 Jul 2026 13:24:27 +0300 Subject: [PATCH 118/163] thunderbolt: stream: Restore consumer if copying from iter fails In tbstream_dev_alloc_tx() if copying data from iterator fails we leave the consumer pointer as is wasting one entry in the ring. Fix this by restoring the consumer back in case of failure. Fixes: 6db21d817b43 ("thunderbolt: Add support for USB4STREAM") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index 68d81958262e..01763f6184e3 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -512,8 +512,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, DMA_TO_DEVICE); if (pdf == TBSTREAM_DATA) { - if (copy_page_from_iter(sf->page, 0, size, from) != size) + if (copy_page_from_iter(sf->page, 0, size, from) != size) { + sdev->tx_ring.cons--; return ERR_PTR(-EFAULT); + } } else { memset(page_address(sf->page), 0, size); } From af73a21a8eb460744d733fa5e2627935ed125081 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 28 Jul 2026 13:27:28 +0300 Subject: [PATCH 119/163] thunderbolt: stream: Fix possible short reads/writes Since copy_page_{to|from}_iter() advances the iterator and makes iov_iter_count() reflect the remaining bytes, subtracting nbytes from it makes it count it twice resulting in possible short reads/writes on a read/write spanning multiple frames. Fix this by using iov_iter_count() directly. Fixes: 6db21d817b43 ("thunderbolt: Add support for USB4STREAM") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index 01763f6184e3..aa606721c51e 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -673,7 +673,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) } nbytes = 0; - while (nbytes < iov_iter_count(to)) { + while (iov_iter_count(to)) { struct tbstream_frame *sf; size_t size, sf_size; @@ -695,7 +695,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) } sf_size = tb_ring_frame_size(&sf->frame); - size = min(iov_iter_count(to) - nbytes, sf_size); + size = min(iov_iter_count(to), sf_size); if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { ret = -EFAULT; @@ -765,10 +765,10 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) } nbytes = 0; - while (nbytes < iov_iter_count(from)) { + while (iov_iter_count(from)) { size_t size; - size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); + size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE); ret = tbstream_dev_send_data(sdev, from, size); if (ret) { /* From 42bc6935339b913d424331a62f9e965bc72ca3df Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 17 Jun 2026 07:42:06 +0300 Subject: [PATCH 120/163] thunderbolt: stream: Support IOCB_NOWAIT in non-blocking I/O as well For read_iter/write_iter() it is also possible to pass IOCB_NOWAIT with the kiocb to indicate non-blocking read/write. For instance io_uring does this. So take this into account on read and write paths. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 40 +++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index aa606721c51e..b3fab395194e 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -633,10 +633,23 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) sdev->tx_ring.ring = NULL; } +static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) +{ + if (nowait) { + if (!mutex_trylock(&sdev->lock)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + } + return 0; +} + static ssize_t tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) { struct file *file = kiocb->ki_filp; + bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); size_t nbytes; int ret; @@ -645,14 +658,16 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (ret) return ret; - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; + ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; while (!tbstream_ring_available(&sdev->rx_ring)) { mutex_unlock(&sdev->lock); - if (file->f_flags & O_NONBLOCK) + if (nowait) return -EAGAIN; + ret = wait_event_interruptible(sdev->wait, tbstream_ring_available(&sdev->rx_ring) || tbstream_dev_valid(sdev) != 0 || @@ -668,8 +683,9 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) return 0; - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; + ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; } nbytes = 0; @@ -729,6 +745,7 @@ static ssize_t tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) { struct file *file = kiocb->ki_filp; + bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); size_t nbytes; int ret; @@ -737,14 +754,16 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) if (ret) return ret; - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; + ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; while (!tbstream_ring_available(&sdev->tx_ring)) { mutex_unlock(&sdev->lock); - if (file->f_flags & O_NONBLOCK) + if (nowait) return -EAGAIN; + ret = wait_event_interruptible(sdev->wait, tbstream_ring_available(&sdev->tx_ring) || tbstream_dev_valid(sdev) != 0 || @@ -760,8 +779,9 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) return -ENXIO; - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; + ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; } nbytes = 0; From f5cb175ed728488aacd8a631cb0a815bfb06664e Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 18 Jun 2026 07:22:49 +0300 Subject: [PATCH 121/163] thunderbolt: Make interrupt optional for rings For some use-cases it does make sense to poll the rings directly instead of relying on the interrupt. For this reason add a new flag RING_FLAG_NO_INTERRUPT that can be used to allocate ring in polled mode. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 27 +++++++++++++++++++++------ include/linux/thunderbolt.h | 2 ++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 698fb124d529..ad6e01ac4c41 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) { struct ring_frame *frame, *n; struct ring_desc *descriptor; + u32 flags; + + flags = RING_DESC_POSTED; + if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) + flags |= RING_DESC_INTERRUPT; + list_for_each_entry_safe(frame, n, &ring->queue, list) { if (ring_full(ring)) break; @@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) descriptor = &ring->descriptors[ring->head]; descriptor->phys = frame->buffer_phy; descriptor->time = 0; - descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; + descriptor->flags = flags; if (ring->is_tx) { descriptor->length = frame->size; descriptor->eof = frame->eof; @@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); * @ring: Ring to poll * * This function can be called when @start_poll callback of the @ring - * has been called. It will read one completed frame from the ring and - * return it to the caller. + * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. + * It will read one completed frame from the ring and return it to the + * caller. * * Return: Pointer to &struct ring_frame, %NULL if there is no more * completed frames. @@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", transmit ? "TX" : "RX", hop, size); + if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { + dev_WARN(nhi->dev, + "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); + return NULL; + } + ring = kzalloc_obj(*ring); if (!ring) return NULL; @@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, if (!ring->descriptors) goto err_free_ring; - if (nhi->ops->request_ring_irq) { + if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) goto err_free_descs; } @@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) ring_iowrite32options(ring, flags, 0); } - ring_interrupt_active(ring, true); + if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) + ring_interrupt_active(ring, true); ring->running = true; err: spin_unlock(&ring->lock); @@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) RING_TYPE(ring), ring->hop); goto err; } - ring_interrupt_active(ring, false); + if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) + ring_interrupt_active(ring, false); ring_iowrite32options(ring, 0, 0); ring_iowrite64desc(ring, 0, 0); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 0a9ac4bfea67..a4e3e8248ace 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -598,6 +598,8 @@ struct tb_ring { #define RING_FLAG_FRAME BIT(1) /* Enable end-to-end flow control */ #define RING_FLAG_E2E BIT(2) +/* Do not enable interrupt for the ring */ +#define RING_FLAG_NO_INTERRUPT BIT(3) struct ring_frame; typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); From e120d14d03b3db5f4ac049f32328c4951a532a90 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 18 Jun 2026 07:27:22 +0300 Subject: [PATCH 122/163] thunderbolt: stream: Add support for busy polling Using interrupts and scheduling workers increase latency so latency critical applications may want to avoid that. Make this possible in USB4STREAM by adding a new ConfigFS attribute: busy_poll that, when activated switches the rings to polling mode. The cost for lower latency is that this burns more CPU cycles and things like poll(2) cannot be used. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mika Westerberg --- .../ABI/testing/configfs-thunderbolt_stream | 15 ++ drivers/thunderbolt/stream.c | 212 ++++++++++++++---- 2 files changed, 187 insertions(+), 40 deletions(-) diff --git a/Documentation/ABI/testing/configfs-thunderbolt_stream b/Documentation/ABI/testing/configfs-thunderbolt_stream index 7abc6b73a1e4..cbecb3d8db50 100644 --- a/Documentation/ABI/testing/configfs-thunderbolt_stream +++ b/Documentation/ABI/testing/configfs-thunderbolt_stream @@ -27,6 +27,21 @@ Description: default values. If there is an advertised remote stream with the same name, uses its values as the default. +What: /sys/kernel/config/thunderbolt/stream/./$name/busy_poll +Date: Nov 2026 +KernelVersion: v7.3 +Contact: Mika Westerberg +Description: + Instead of using interrupts for completing the frames in + the TX/RX rings, busy poll them directly from the + read(2) and write(2) calls. This burns more CPU cycles + but provides lower latency for applications that need it. + + This also makes poll(2) return EPOLLERR because + interrupts do not provide wakeup anymore. Likewise a + blocking read(2) without available data busy-spins until + data arrives or a signal is received. + What: /sys/kernel/config/thunderbolt/stream/./$name/index Date: Sep 2026 KernelVersion: v7.2 diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index b3fab395194e..34192edfb7a3 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -9,10 +9,12 @@ #define pr_fmt(fmt) "tbstream: " fmt +#include #include #include #include #include +#include #include #include #include @@ -128,6 +130,7 @@ struct tbstream_ring { * @out_hopid: Out HopID * @ring_size: Size of the rings * @throttling: Interrupt throttling rate in ns + * @busy_poll: Instead of interrupts, busy poll the rings * @users: Number of times @cdev has been opened * @closed: CLOSE packet was received * @removed: Userspace removed the ConfigFS group underneath. @@ -147,6 +150,7 @@ struct tbstream_dev { int out_hopid; unsigned int ring_size; unsigned int throttling; + bool busy_poll; int users; bool closed; bool removed; @@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); } +static void +tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) +{ + struct ring_frame *frame; + + if (!sdev->busy_poll) + return; + + while ((frame = tb_ring_poll(ring->ring))) + frame->callback(ring->ring, frame, false); +} + static int tbstream_dev_send_close(struct tbstream_dev *sdev) { struct tbstream_frame *sf; + if (sdev->busy_poll) { + /* + * When busy polling it's the write(2) path that + * advances the completions so it is possible that the + * ring is full at this point. Advance the ring here so + * that there is room for the CLOSE packet to be sent. + */ + ktime_t timeout = ktime_add_ms(ktime_get(), 500); + + do { + if (tbstream_ring_available(&sdev->tx_ring)) + break; + tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + fsleep(15); + } while (ktime_before(ktime_get(), timeout)); + } + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); if (IS_ERR(sf)) return PTR_ERR(sf); @@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) static int tbstream_dev_start(struct tbstream_dev *sdev) { struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; u16 sof_mask, eof_mask; struct tb_ring *ring; int ret, e2e_tx_hop; - ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, - RING_FLAG_FRAME | RING_FLAG_E2E); + if (sdev->busy_poll) + flags |= RING_FLAG_NO_INTERRUPT; + + ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); if (!ring) return -ENOMEM; sdev->tx_ring.ring = ring; @@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) sof_mask = BIT(TBSTREAM_FRAME_START); eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); - ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, - RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, - sof_mask, eof_mask, NULL, NULL); + ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, + e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); if (!ring) { ret = -ENOMEM; goto err_free_tx_buffers; @@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) return ret; } +static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) +{ + const struct tbstream_ring *ring = &sdev->tx_ring; + + /* + * Everything is completed when number of free TX slots is back + * to the maximum. + */ + return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; +} + static void tbstream_dev_stop(struct tbstream_dev *sdev) { struct tb_xdomain *xd; - /* Wait for the ring to complete any outstanding frames */ - tb_ring_flush(sdev->tx_ring.ring, 500); - tb_ring_stop(sdev->tx_ring.ring); - tb_ring_flush(sdev->rx_ring.ring, 500); - tb_ring_stop(sdev->rx_ring.ring); + if (sdev->busy_poll) { + /* + * When busy polling we must advance the ring ourselves + * to push all outstanding frames on the wire. + */ + ktime_t timeout = ktime_add_ms(ktime_get(), 500); + + do { + if (tbstream_dev_tx_drained(sdev)) + break; + tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + fsleep(15); + } while (ktime_before(ktime_get(), timeout)); + + tb_ring_stop(sdev->tx_ring.ring); + tb_ring_stop(sdev->rx_ring.ring); + } else { + tb_ring_flush(sdev->tx_ring.ring, 500); + tb_ring_stop(sdev->tx_ring.ring); + tb_ring_flush(sdev->rx_ring.ring, 500); + tb_ring_stop(sdev->rx_ring.ring); + } xd = tbstream_dev_xdomain(sdev); if (xd) { @@ -633,6 +696,7 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) sdev->tx_ring.ring = NULL; } +/* Use only with read_iter/write_iter() to handle nowait */ static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) { if (nowait) { @@ -662,26 +726,42 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (ret) return ret; - while (!tbstream_ring_available(&sdev->rx_ring)) { + for (;;) { + /* When busy polling, advance any completions manually */ + tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); + if (ret) { + mutex_unlock(&sdev->lock); + return ret; + } + + if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { + mutex_unlock(&sdev->lock); + return 0; + } + + if (tbstream_ring_available(&sdev->rx_ring)) + break; + mutex_unlock(&sdev->lock); if (nowait) return -EAGAIN; - ret = wait_event_interruptible(sdev->wait, - tbstream_ring_available(&sdev->rx_ring) || - tbstream_dev_valid(sdev) != 0 || - tbstream_dev_closed(sdev) || - tbstream_dev_removed(sdev)); - if (ret) - return ret; - - ret = tbstream_dev_valid(sdev); - if (ret) - return ret; - - if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) - return 0; + if (sdev->busy_poll) { + if (signal_pending(current)) + return -ERESTARTSYS; + cond_resched(); + } else { + ret = wait_event_interruptible(sdev->wait, + tbstream_ring_available(&sdev->rx_ring) || + tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || + tbstream_dev_removed(sdev)); + if (ret) + return ret; + } ret = tbstream_dev_lock(sdev, nowait); if (ret) @@ -758,26 +838,41 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) if (ret) return ret; - while (!tbstream_ring_available(&sdev->tx_ring)) { + for (;;) { + tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); + if (ret) { + mutex_unlock(&sdev->lock); + return ret; + } + + if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { + mutex_unlock(&sdev->lock); + return -ENXIO; + } + + if (tbstream_ring_available(&sdev->tx_ring)) + break; + mutex_unlock(&sdev->lock); if (nowait) return -EAGAIN; - ret = wait_event_interruptible(sdev->wait, - tbstream_ring_available(&sdev->tx_ring) || - tbstream_dev_valid(sdev) != 0 || - tbstream_dev_closed(sdev) || - tbstream_dev_removed(sdev)); - if (ret) - return ret; - - ret = tbstream_dev_valid(sdev); - if (ret) - return ret; - - if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) - return -ENXIO; + if (sdev->busy_poll) { + if (signal_pending(current)) + return -ERESTARTSYS; + cond_resched(); + } else { + ret = wait_event_interruptible(sdev->wait, + tbstream_ring_available(&sdev->tx_ring) || + tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || + tbstream_dev_removed(sdev)); + if (ret) + return ret; + } ret = tbstream_dev_lock(sdev, nowait); if (ret) @@ -815,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); __poll_t mask = 0; + /* + * Without interrupts there is nothing that can wake us up so + * return failure instead. + */ + if (sdev->busy_poll) + return EPOLLERR; + poll_wait(file, &sdev->wait, wait); guard(mutex)(&sdev->lock); if (tbstream_dev_valid(sdev) != 0) { @@ -924,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) return container_of(group, struct tbstream_dev, group); } +static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%u\n", sdev->busy_poll); +} + +static ssize_t +tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, + size_t count) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + bool busy_poll; + int ret; + + ret = kstrtobool(buf, &busy_poll); + if (ret) + return ret; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + sdev->busy_poll = busy_poll; + return count; +} +CONFIGFS_ATTR(tbstream_dev_, busy_poll); + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) { struct config_group *group = to_config_group(item); @@ -1230,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, CONFIGFS_ATTR(tbstream_dev_, throttling); static struct configfs_attribute *tbstream_dev_attrs[] = { + &tbstream_dev_attr_busy_poll, &tbstream_dev_attr_index, &tbstream_dev_attr_in_hopid, &tbstream_dev_attr_out_hopid, From f1de1fc5f632cdeae1f5c2984572ab710d4dfcaa Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Thu, 6 Aug 2026 18:29:29 +0530 Subject: [PATCH 123/163] thunderbolt: Add quirk to reset host interface on DMA path teardown for AMD USB4 routers Some AMD USB4 host routers have a bug in the Host Interface where DMA path setup and teardown cycles may cause the Tx ring to hang. Fix this by issuing a Host Interface Reset on every DMA path teardown for affected routers. The Host Interface Reset brings the registers in the memory BAR to their default state and clears the End-to-End Flow Control state, preventing the hang condition. Co-developed-by: Sanath S Signed-off-by: Sanath S Signed-off-by: Basavaraj Natikar Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 26 +++++++++++++++++++++++++- drivers/thunderbolt/nhi.c | 26 ++++++++++++++++++++++++++ drivers/thunderbolt/nhi.h | 21 +++++++++++++++++++-- drivers/thunderbolt/nhi_regs.h | 4 ++++ drivers/thunderbolt/pci.c | 23 +++++++++++++++++++++++ 5 files changed, 97 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 24611f05b3cd..12c88509a54f 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -788,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, transmit_ring, receive_path, receive_ring); } +static void tb_domain_reset_interface(struct tb *tb) +{ + struct tb_nhi *nhi = tb->nhi; + + if (!nhi->ops->reset_interface) + return; + + guard(mutex)(&tb->lock); + + /* The reset clears the ring state so stop the control channel */ + tb_ctl_stop(tb->ctl); + nhi->ops->reset_interface(nhi); + tb_ctl_start(tb->ctl); +} + /** * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain * @tb: Domain disabling the DMA paths @@ -810,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { + int ret; + if (!tb->cm_ops->disconnect_xdomain_paths) return -ENOTSUPP; - return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, transmit_ring, receive_path, receive_ring); + if (ret) + return ret; + + if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) + tb_domain_reset_interface(tb); + + return 0; } static int disconnect_xdomain(struct device *dev, void *data) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index ad6e01ac4c41..914d4f8700b5 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1175,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi) dev_warn(nhi->dev, "timeout resetting host router\n"); } +/** + * nhi_reset_interface() - Reset the host interface + * @nhi: Host interface to reset + * + * Brings the registers in the memory BAR back to their default state and + * clears the End-to-End Flow Control state. The caller is responsible for + * stopping the control channel over the reset because it clears the ring + * state as well. + */ +void nhi_reset_interface(struct tb_nhi *nhi) +{ + u32 val; + + val = ioread32(nhi->iobase + REG_CAPS); + /* Only v1 host interfaces implement the reset */ + if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) + return; + + dev_dbg(nhi->dev, "issuing host interface reset\n"); + + iowrite32(REG_HOST_INTERFACE_RESET_RST, + nhi->iobase + REG_HOST_INTERFACE_RESET); + /* Wait for tHIReset (10 ms) to complete */ + usleep_range(10000, 20000); +} + static struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index d488eadadfce..f72d6b274501 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data); irqreturn_t ring_msix(int irq, void *data); int nhi_probe(struct tb_nhi *nhi); void nhi_shutdown(struct tb_nhi *nhi); +void nhi_reset_interface(struct tb_nhi *nhi); + extern const struct dev_pm_ops nhi_pm_ops; /** @@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops; * @release_ring_irq: NHI specific interrupt release hook * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook + * @reset_interface: Resets the host interface */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -66,6 +69,7 @@ struct tb_nhi_ops { void (*release_ring_irq)(struct tb_ring *ring); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); + void (*reset_interface)(struct tb_nhi *nhi); }; /* @@ -116,11 +120,24 @@ struct tb_nhi_ops { #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 +#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 +#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 +#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b +#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c +#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 +#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 +#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 +#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c +#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d +#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d +#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 /* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) +#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index d6a197fabc74..99df60b6db36 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -115,6 +115,10 @@ struct ring_desc { #define REG_CAPS_VERSION_MASK GENMASK(23, 16) #define REG_CAPS_VERSION_2 0x40 +/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ +#define REG_HOST_INTERFACE_RESET 0x39858 +#define REG_HOST_INTERFACE_RESET_RST BIT(0) + #define REG_DMA_MISC 0x39864 #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index 8462ccb59b7e..99333729f3c2 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) nhi->quirks |= QUIRK_E2E; break; } + } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { + switch (pdev->device) { + case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: + case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: + /* + * These AMD hosts may hang the Tx ring when the + * DMA paths are torn down so they need the host + * interface reset after each teardown. + */ + nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; + break; + } } } @@ -258,6 +279,7 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { .shutdown = nhi_pci_release_irq, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, + .reset_interface = nhi_reset_interface, }; /* Ice Lake specific NHI operations */ @@ -441,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = { .release_ring_irq = nhi_pci_ring_release_msix, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, + .reset_interface = nhi_reset_interface, }; static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) From d37186bd95a07e334447f47274a38a311dad2172 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 6 Aug 2026 15:52:48 +0200 Subject: [PATCH 124/163] USB: serial: spcp8x5: drop broken carrier detect support The driver does not support modem status notifications and instead used to fetch the modem status once at open() and subsequently operate on and report stale state. As part of fixing this, a call to fetch the status was added to carrier_raised(), which does not work as that callback must not sleep (e.g. unlike tiocmget()). Drop the broken carrier detect support. Fixes: e1ed212d8593 ("USB: spcp8x5: add proper modem-status support") Cc: stable@vger.kernel.org # 3.10 Reported-by: syzbot+3b514b87202742f22c44@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a73cea2.01d0871a.3a0d52.000d.GAE@google.com Signed-off-by: Johan Hovold --- drivers/usb/serial/spcp8x5.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index c11d64bf08fb..f610aef6bf59 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -235,18 +235,6 @@ static void spcp8x5_set_work_mode(struct usb_serial_port *port, u16 value, dev_err(&port->dev, "failed to set work mode: %d\n", ret); } -static int spcp8x5_carrier_raised(struct usb_serial_port *port) -{ - u8 msr; - int ret; - - ret = spcp8x5_get_msr(port, &msr); - if (ret || msr & MSR_STATUS_LINE_DCD) - return 1; - - return 0; -} - static void spcp8x5_dtr_rts(struct usb_serial_port *port, int on) { struct spcp8x5_private *priv = usb_get_serial_port_data(port); @@ -458,7 +446,6 @@ static struct usb_serial_driver spcp8x5_device = { .num_bulk_out = 1, .open = spcp8x5_open, .dtr_rts = spcp8x5_dtr_rts, - .carrier_raised = spcp8x5_carrier_raised, .set_termios = spcp8x5_set_termios, .init_termios = spcp8x5_init_termios, .tiocmget = spcp8x5_tiocmget, From f7c0e02eb61284395f74424edaefbbd323f83c4e Mon Sep 17 00:00:00 2001 From: Mahad Ibrahim Date: Sat, 8 Aug 2026 22:38:29 +0000 Subject: [PATCH 125/163] thunderbolt: debugfs: Replace get_zeroed_page() with kzalloc() validate_and_copy_from_user() allocates a page to store data from userspace via get_zeroed_page(), and then returns it as a buffer. Neither the function itself nor its callers require struct page access. This buffer can easily be allocated with kzalloc() as there is nothing special about it that requires going through the page allocator. kzalloc(), which internally reduces to kmalloc() with __GFP_ZERO, provides a better API and kfree() does not need to know the size of the freed object. Additionally it removes the casts of (void *) and (unsigned long) which only obfuscate the code. Replace get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com/ Signed-off-by: Mahad Ibrahim Acked-by: Mike Rapoport (Microsoft) Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index f5cf0e177f40..6e9080e7bcec 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf, if (!access_ok(user_buf, *count)) return ERR_PTR(-EFAULT); - buf = (void *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); nbytes = min_t(size_t, *count, PAGE_SIZE); if (copy_from_user(buf, user_buf, nbytes)) { - free_page((unsigned long)buf); + kfree(buf); return ERR_PTR(-EFAULT); } @@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } @@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } @@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file, out: pm_runtime_mark_last_busy(&rt->dev); pm_runtime_put_autosuspend(&rt->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } @@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf, margining->ber_level = val; out_free: - free_page((unsigned long)buf); + kfree(buf); out_unlock: mutex_unlock(&tb->lock); @@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf, } } - free_page((unsigned long)buf); + kfree(buf); if (lane == -1) return -EINVAL; @@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, else goto err_free; - free_page((unsigned long)buf); + kfree(buf); scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { if (!margining->software) @@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, return count; err_free: - free_page((unsigned long)buf); + kfree(buf); return -EINVAL; } @@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file, mutex_unlock(&tb->lock); out_free: - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file, mutex_unlock(&tb->lock); out_free: - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file, mutex_unlock(&tb->lock); out_free: - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file, ret = -EINVAL; } - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf, out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } From e8158c8a6a232a70ae70c5acfaf7008a99b716c1 Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Mon, 10 Aug 2026 09:38:43 +0000 Subject: [PATCH 126/163] thunderbolt: Use min() for the DMA path credit cap tb_dma_reserve_credits() caps the request against what the adapter has left by decrementing one credit at a time. The other arm of the same if() already caps with min(port->total_credits, credits); use min() here too. No functional change: the object code is unchanged. Assisted-by: Claude:claude-opus-5 Signed-off-by: Fan Ye Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index b7f32305f14a..e9214de5f3b7 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1778,8 +1778,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) if (available < TB_MIN_DMA_CREDITS) return -ENOSPC; - while (credits > available) - credits--; + credits = min(credits, available); tb_port_dbg(port, "reserving %u credits for DMA path\n", credits); From 86feaba911f2f1a540a7695c8f4a98fd0fd60ac4 Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Mon, 10 Aug 2026 12:14:13 +0000 Subject: [PATCH 127/163] thunderbolt: Clamp DMA tunnel credits to what a hop register can hold struct tb_regs_hop::initial_credits is 7 bits wide, but neither of the values tb_tunnel_alloc_dma() picks from is bounded by that: the dma_credits module parameter has no upper limit, and neither does the host router's baMaxHI. A larger count survives until tb_path_activate() copies it into the register and keeps the low bits, leaving the path on a credit count nobody asked for. Clamp it in tb_tunnel_alloc_dma(), the only entry point for DMA tunnels; every step below it can only lower the value further. Carry the count in an unsigned int while at it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Fan Ye Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index e9214de5f3b7..7e8284575dff 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -48,6 +48,9 @@ #define TB_DP_AUX_PRIORITY 2 #define TB_DP_AUX_WEIGHT 1 +/* struct tb_regs_hop::initial_credits is 7 bits wide */ +#define TB_MAX_CREDITS 127 + /* Minimum number of credits needed for PCIe path */ #define TB_MIN_PCIE_CREDITS 6U /* @@ -1907,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_tunnel *tunnel; size_t npaths = 0, i = 0; struct tb_path *path; - int credits; + unsigned int credits; /* Ring 0 is reserved for control channel */ if (WARN_ON(!receive_ring || !transmit_ring)) @@ -1930,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, tunnel->destroy = tb_dma_destroy; credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); + if (credits > TB_MAX_CREDITS) { + tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n", + credits, TB_MAX_CREDITS); + credits = TB_MAX_CREDITS; + } if (receive_ring > 0) { path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, From add8469b3e0031a2e0243dd0d18c555cac8b7eea Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 6 Aug 2026 17:20:57 +0300 Subject: [PATCH 128/163] xhci: fix frame id calculation and checks for isoc URBs Check if the expected frame IDs for a isochronous URB submitted mid stream is within the valid frame time window that xHC controller is capable of queuing TDs. The range only needs to be checked once per URB as the isoc TDs of an URB are queued in one go with spinlock held and interrupts disabled. Calculate the valid frame window start and end frame id in frames instead of microframes to better match how xhci specification section 4.11.2.5 does it. Don't add frame id gaps or change scheduling to SIA mid stream if the start frame is outside the valid frame winow. Only print a debug message. Some devices can't handle gaps in isochronous transfers. Calculate a valid start frame for the first URB of a stream, and align it to a full frame, or to interval start if interval is longer than a frame Set urb->start_frame value for every URB cc: Dylan Robinson Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 1 + drivers/usb/host/xhci-ring.c | 182 +++++++++++++++-------------------- drivers/usb/host/xhci.h | 7 +- 3 files changed, 86 insertions(+), 104 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index a5e7f363922f..2d7a61b3aaf2 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, return -ENOMEM; virt_dev->eps[ep_index].skip = false; + virt_dev->eps[ep_index].next_uframe = -1; ep_ring = virt_dev->eps[ep_index].new_ring; xhci_ring_init(xhci, ep_ring); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 4f98d8269625..bc998692589d 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3956,80 +3956,77 @@ static int xhci_ist_microframes(struct xhci_hcd *xhci) } /* - * Calculates Frame ID field of the isochronous TRB identifies the - * target frame that the Interval associated with this Isochronous - * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec. - * - * Returns actual frame id on success, negative value on error. + * Check if frame is in the valid frame window, including start and end. + * If start > end then assume window wrapped around at a limit the frame + * value won't exceed. */ -static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, - struct urb *urb, int index) +static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) { - int start_frame, ist, ret = 0; - int start_frame_id, end_frame_id, current_frame_id; + /* frame window end wrapped around */ + if (start > end) + return frame >= start || frame <= end; - if (urb->dev->speed == USB_SPEED_LOW || - urb->dev->speed == USB_SPEED_FULL) - start_frame = urb->start_frame + index * urb->interval; - else - start_frame = (urb->start_frame + index * urb->interval) >> 3; + return frame >= start && frame <= end; +} +/* + * Set the urb->start_frame of the URB. + * + * Returns microframe index of first TD + */ +static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_virt_ep *ep) +{ + u32 curr_frame, start_uframe; + u32 urb_start, urb_end; + u32 win_start, win_end; + bool frame_unit; + int uinterval; + u32 mfindex; + int ist; + + /* check if urb uses frame units instead of microframes */ + frame_unit = (urb->dev->speed == USB_SPEED_FULL || + urb->dev->speed == USB_SPEED_LOW); + + uinterval = urb->interval; + if (frame_unit) + uinterval *= 8; + + /* get current microframe index and isoc scheduling threshold */ + mfindex = readl(&xhci->run_regs->microframe_index); ist = xhci_ist_microframes(xhci); - /* Software shall not schedule an Isoch TD with a Frame ID value that - * is less than the Start Frame ID or greater than the End Frame ID, - * where: - * - * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048 - * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048 - * - * Both the End Frame ID and Start Frame ID values are calculated - * in microframes. When software determines the valid Frame ID value; - * The End Frame ID value should be rounded down to the nearest Frame - * boundary, and the Start Frame ID value should be rounded up to the - * nearest Frame boundary. - */ - current_frame_id = readl(&xhci->run_regs->microframe_index); - start_frame_id = roundup(current_frame_id + ist + 1, 8); - end_frame_id = rounddown(current_frame_id + 895 * 8, 8); + /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ + curr_frame = MFINDEX_TO_FRAME(mfindex); + win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; + win_end = (curr_frame + 895) % MAX_FRAMES; - start_frame &= 0x7ff; - start_frame_id = (start_frame_id >> 3) & 0x7ff; - end_frame_id = (end_frame_id >> 3) & 0x7ff; - - if (start_frame_id < end_frame_id) { - if (start_frame > end_frame_id || - start_frame < start_frame_id) - ret = -EINVAL; - } else if (start_frame_id > end_frame_id) { - if ((start_frame > end_frame_id && - start_frame < start_frame_id)) - ret = -EINVAL; + /* Is this the first URB starting the whole isoc data flow? */ + if (ep->next_uframe < 0) { + /* align first URB to next interval boundary, or at last to full frame */ + start_uframe = mfindex + ist + XHCI_CFC_DELAY; + start_uframe = roundup(start_uframe, 8); + start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; } else { - ret = -EINVAL; - } + /* URB is mid stream and expected to handle the next frame */ + start_uframe = ep->next_uframe; + urb_start = start_uframe / 8; + urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; + urb_end %= MAX_FRAMES; - if (index == 0) { - if (ret == -EINVAL || start_frame == start_frame_id) { - start_frame = start_frame_id + 1; - if (urb->dev->speed == USB_SPEED_LOW || - urb->dev->speed == USB_SPEED_FULL) - urb->start_frame = start_frame; - else - urb->start_frame = start_frame << 3; - ret = 0; - } - } + if (!xhci_frame_in_range(urb_start, win_start, win_end)) + xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", + urb, urb_start, win_start, win_end); - if (ret) { - xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n", - start_frame, current_frame_id, index, - start_frame_id, end_frame_id); - xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n"); - return ret; + if (!xhci_frame_in_range(urb_end, win_start, win_end)) + xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", + urb, urb_start, win_start, win_end); } + /* set urb->start_frame */ + urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; - return start_frame; + return start_uframe; } /* Check if we should generate event interrupt for a TD in an isoc URB */ @@ -4070,6 +4067,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, bool more_trbs_coming; struct xhci_virt_ep *xep; int frame_id; + int uinterval = urb->interval; + int start_uframe; xep = &xhci->devs[slot_id]->eps[ep_index]; ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; @@ -4085,6 +4084,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, start_cycle = ep_ring->cycle_state; urb_priv = urb->hcpriv; + + if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) + uinterval = urb->interval * 8; + + start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); + /* Queue the TRBs for each TD, even if they are zero-length */ for (i = 0; i < num_tds; i++) { unsigned int total_pkt_count, max_pkt; @@ -4116,14 +4121,15 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, goto cleanup; } td = &urb_priv->td[i]; + /* use SIA as default, if frame id is used overwrite it */ sia_frame_id = TRB_SIA; - if (!(urb->transfer_flags & URB_ISO_ASAP) && - (xhci->hcc_params & HCC_CFC)) { - frame_id = xhci_get_isoc_frame_id(xhci, urb, i); - if (frame_id >= 0) - sia_frame_id = TRB_FRAME_ID(frame_id); + if (!(urb->transfer_flags & URB_ISO_ASAP) && (xhci->hcc_params & HCC_CFC)) { + frame_id = (start_uframe + i * uinterval) / 8; + frame_id %= MAX_FRAMES; + sia_frame_id = TRB_FRAME_ID(frame_id); } + /* * Set isoc specific data for the first TRB in a TD. * Prevent HW from getting the TRBs by keeping the cycle state @@ -4202,9 +4208,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } } - /* store the next frame id */ - if (xhci->hcc_params & HCC_CFC) - xep->next_frame_id = urb->start_frame + num_tds * urb->interval; + xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { if (xhci->quirks & XHCI_AMD_PLL_FIX) @@ -4251,11 +4255,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, struct xhci_virt_device *xdev; struct xhci_ring *ep_ring; struct xhci_ep_ctx *ep_ctx; - int start_frame; + struct xhci_virt_ep *xep; int num_tds, num_trbs, i; int ret; - struct xhci_virt_ep *xep; - int ist; xdev = xhci->devs[slot_id]; xep = &xhci->devs[slot_id]->eps[ep_index]; @@ -4281,38 +4283,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, */ check_interval(urb, ep_ctx); - /* Calculate the start frame and put it in urb->start_frame. */ - if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) { - if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) { - urb->start_frame = xep->next_frame_id; - goto skip_start_over; - } - } - - start_frame = readl(&xhci->run_regs->microframe_index); - start_frame &= 0x3fff; /* - * Round up to the next frame and consider the time before trb really - * gets scheduled by hardare. + * Check if this starts the isoc data flow. Relies on hw setting ep ctx + * state after doorbell ring. Consider adding list_empty(td_list) check */ - ist = xhci_ist_microframes(xhci); - start_frame += ist + XHCI_CFC_DELAY; - start_frame = roundup(start_frame, 8); - - /* - * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT - * is greate than 8 microframes. - */ - if (urb->dev->speed == USB_SPEED_LOW || - urb->dev->speed == USB_SPEED_FULL) { - start_frame = roundup(start_frame, urb->interval << 3); - urb->start_frame = start_frame >> 3; - } else { - start_frame = roundup(start_frame, urb->interval); - urb->start_frame = start_frame; - } - -skip_start_over: + if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) + xep->next_uframe = -1; return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 708e3ccc5d87..8705988264f9 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -290,6 +290,11 @@ struct xhci_run_regs { struct xhci_intr_reg ir_set[1024]; }; +/* Bits [13:3] of the microframe index equals the 1ms frame index */ +#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) +#define MAX_FRAMES 2048 +#define MAX_UFRAMES (MAX_FRAMES * 8) + /** * struct doorbell_array * @@ -699,7 +704,7 @@ struct xhci_virt_ep { struct list_head bw_endpoint_list; unsigned long stop_time; /* Isoch Frame ID checking storage */ - int next_frame_id; + int next_uframe; /* Use new Isoch TRB layout needed for extended TBC support */ bool use_extended_tbc; /* set if this endpoint is controlled via sideband access*/ From b08d153c95f1164093b14b8656bfcf4b6e1b0835 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 6 Aug 2026 17:20:58 +0300 Subject: [PATCH 129/163] xhci: Set frame ID field of isoc TRB when starting an isoch stream The frame id field can be set for the first TD of the first isoc URB to schedule the start of an isoc stream even in host doesn't support CFC (Contiguous Frame ID Capability) Set the frame ID TRB field of the first isoc TD unless URB has the schedule immediately 'URB_ISO_ASAP' transfer flag set. cc: Dylan Robinson Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index bc998692589d..3ab0d1b68d85 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3955,6 +3955,23 @@ static int xhci_ist_microframes(struct xhci_hcd *xhci) return ist; } + +static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_virt_ep *ep, int i) +{ + if (urb->transfer_flags & URB_ISO_ASAP) + return false; + + if (xhci->hcc_params & HCC_CFC) + return true; + + /* set frame id for first TD of first URB in stream */ + if (ep->next_uframe == -1 && i == 0) + return true; + + return false; +} + /* * Check if frame is in the valid frame window, including start and end. * If start > end then assume window wrapped around at a limit the frame @@ -4066,7 +4083,6 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, int i, j; bool more_trbs_coming; struct xhci_virt_ep *xep; - int frame_id; int uinterval = urb->interval; int start_uframe; @@ -4122,12 +4138,13 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } td = &urb_priv->td[i]; - /* use SIA as default, if frame id is used overwrite it */ - sia_frame_id = TRB_SIA; - if (!(urb->transfer_flags & URB_ISO_ASAP) && (xhci->hcc_params & HCC_CFC)) { - frame_id = (start_uframe + i * uinterval) / 8; - frame_id %= MAX_FRAMES; - sia_frame_id = TRB_FRAME_ID(frame_id); + + /* Choose SIA or frame ID based scheduling for this TD */ + if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { + sia_frame_id = (start_uframe + i * uinterval) / 8; + sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); + } else { + sia_frame_id = TRB_SIA; } /* From e3d757dc9257f2638ca61eda4faef26c2daeea10 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 6 Aug 2026 17:20:59 +0300 Subject: [PATCH 130/163] xhci: include all root port children in recovery prevention on link error Driver already prevents useless transfer retry and endpoint recovery for devices directly connected to a root port with link errors. These devices are either disconnecting or will be reset. Link is gone. Move the flag indicating link error from the xhci device structure to the root port strucure, allowing all child devices behind hubs to easily check for root port link errors, avoiding useless transfer retries and endpoint recovery. This extends the previous endpoint recovery prevention in commit b8c3b718087b ("usb: xhci: Don't try to recover an endpoint if port is in error state.") Only root port link errors can be detected early by xhci driver, not link errors between external hubs and their children. Tested-by: Xu Rao Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 27 +++++++++++++++------------ drivers/usb/host/xhci.c | 4 +--- drivers/usb/host/xhci.h | 9 +-------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3ab0d1b68d85..544749b607a4 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -993,7 +993,7 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, * Avoid resetting endpoint if link is inactive. Can cause host hang. * Device will be reset soon to recover the link so don't do anything */ - if (ep->vdev->flags & VDEV_PORT_ERROR) + if (ep->vdev->rhub_port->link_inactive) return -ENODEV; /* add td to cancelled list and let reset ep handler take care of it */ @@ -1992,13 +1992,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) { struct xhci_virt_device *vdev = NULL; - struct usb_hcd *hcd; - u32 port_id; - u32 portsc, cmd_reg; - unsigned int hcd_portnum; struct xhci_bus_state *bus_state; - bool bogus_port_status = false; struct xhci_port *port; + struct usb_hcd *hcd; + bool bogus_port_status = false; + unsigned int hcd_portnum; + u32 cmd_reg; + u32 port_id; + u32 portsc; + u32 pls; /* Port status change events always have a successful completion code */ if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) @@ -2035,6 +2037,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) bus_state = &port->rhub->bus_state; hcd_portnum = port->hcd_portnum; portsc = xhci_portsc_readl(port); + pls = portsc & PORT_PLS_MASK; xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", hcd->self.busnum, hcd_portnum + 1, port_id, portsc); @@ -2046,12 +2049,12 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) usb_hcd_resume_root_hub(hcd); } - if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { - if (!(portsc & PORT_RESET)) - vdev->flags |= VDEV_PORT_ERROR; - } else if (vdev && portsc & PORT_RC) { - vdev->flags &= ~VDEV_PORT_ERROR; - } + /* + * Tag broken links to avoid retries while hub driver sorts it out. + * Link status is not relible while port is in reset. + */ + if (!(portsc & PORT_RESET)) + port->link_inactive = (pls == XDEV_INACTIVE); if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { xhci_dbg(xhci, "port resume event for port %d\n", port_id); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 091c82ca8ee2..6f830a43963f 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1664,7 +1664,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag goto free_priv; } - if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { + if (xhci->devs[slot_id]->rhub_port->link_inactive) { xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); ret = -ENODEV; goto free_priv; @@ -4035,7 +4035,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, xhci_get_slot_state(xhci, virt_dev->out_ctx)); xhci_dbg(xhci, "Not freeing device rings.\n"); /* Don't treat this as an error. May change my mind later. */ - virt_dev->flags = 0; ret = 0; goto command_cleanup; case COMP_SUCCESS: @@ -4087,7 +4086,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, } /* If necessary, update the number of active TTs on this root port */ xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); - virt_dev->flags = 0; ret = 0; command_cleanup: diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 8705988264f9..9b6c7aeb1dca 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -758,14 +758,6 @@ struct xhci_virt_device { struct xhci_port *rhub_port; struct xhci_interval_bw_table *bw_table; struct xhci_tt_bw_info *tt_info; - /* - * flags for state tracking based on events and issued commands. - * Software can not rely on states from output contexts because of - * latency between events and xHC updating output context values. - * See xhci 1.1 section 4.8.3 for more details - */ - unsigned long flags; -#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */ /* The current max exit latency for the enabled USB3 link states. */ u16 current_mel; @@ -1485,6 +1477,7 @@ struct xhci_port { int hcd_portnum; struct xhci_hub *rhub; struct xhci_port_cap *port_cap; + unsigned int link_inactive:1; unsigned int lpm_incapable:1; unsigned long resume_timestamp; bool rexit_active; From 042aad8d0db6607ac9789fc43b6221909bd6a3bc Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 6 Aug 2026 17:21:00 +0300 Subject: [PATCH 131/163] xhci: prevent endpoint recovery after roothub disconnect Prevent transfer retry and endpoint recovery if the device or its parent disconnected from the roothub. Just like link error case. There is a suspicion some xHC controllers may stop processing endpoint related commands after the last USB device disconnects from the host. Disconnect often causes transaction errors, xhci driver tries to (soft) reset and restart the endpoint to recover it. Hub driver again will cancel all pending URBs once disconnect is detected, stopping the endpoint right after (soft) reset restarted it. xHC controller sometimes fail to complete the stop endpoint command, leading to driver timing out, and tearing down xhci Prevent extra endpoint (soft) reset after xhci driver is aware of the parent roothub port disconnect. Tested-by: Xu Rao Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 12 ++++++++---- drivers/usb/host/xhci.h | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 544749b607a4..656ed6470e4a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -986,14 +986,16 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, struct xhci_td *td, enum xhci_ep_reset_type reset_type) { + struct xhci_port *rhub_port = ep->vdev->rhub_port; unsigned int slot_id = ep->vdev->slot_id; int err; /* - * Avoid resetting endpoint if link is inactive. Can cause host hang. - * Device will be reset soon to recover the link so don't do anything + * Avoid resetting endpoint if link is inactive or device disonnected. + * Can cause host hang. + * Device will be reset to recover an inactive link, so don't do anything */ - if (ep->vdev->rhub_port->link_inactive) + if (rhub_port->link_inactive || !rhub_port->connected) return -ENODEV; /* add td to cancelled list and let reset ep handler take care of it */ @@ -2053,8 +2055,10 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) * Tag broken links to avoid retries while hub driver sorts it out. * Link status is not relible while port is in reset. */ - if (!(portsc & PORT_RESET)) + if (!(portsc & PORT_RESET)) { port->link_inactive = (pls == XDEV_INACTIVE); + port->connected = !!(portsc & PORT_CONNECT); + } if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { xhci_dbg(xhci, "port resume event for port %d\n", port_id); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 9b6c7aeb1dca..0cc9edfd86f3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1478,6 +1478,7 @@ struct xhci_port { struct xhci_hub *rhub; struct xhci_port_cap *port_cap; unsigned int link_inactive:1; + unsigned int connected:1; unsigned int lpm_incapable:1; unsigned long resume_timestamp; bool rexit_active; From 7e0ef4332ed9b70309fa1f22ec26a8c3c63686ea Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 6 Aug 2026 17:21:01 +0300 Subject: [PATCH 132/163] xhci: avoid xHC endpoint changes after disconnect or link error. Avoid all extra endpoint state changes after the roothub link is lost due to disconnect or link error, and endpoint is known to be in a non-running state. Rapid endpoint state changes involving endpoint reset, restart, and stopping the endpoint have caused xHC failures to complete stop endpoint command. xhci driver sees this as a fatal flaw and tears down xhci. These endpoint state changes are normally part of recovery from transaction errors or URB cancel. In this case recovery is not needed. Add an endpoint state called EP_DROP_PENDING. Set ep->ep_state |= EP_DROP_PENDING when an endpoint is found in a halted or stopped non-running state, and the roothub link is lost. Prevent endpoint from restarting. URB cancel doesn't need to stop the endpoint if EP_DROP_PENDONG is set. URBs can be given back directly. Endpoint is, and will remain stopped until it's dropped. Tested-by: Xu Rao Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 19 ++++++++++++++++--- drivers/usb/host/xhci.c | 5 ++++- drivers/usb/host/xhci.h | 1 + 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 656ed6470e4a..51008bad16cd 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -561,8 +561,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, * pointer command pending because the device can choose to start any * stream once the endpoint is on the HW schedule. */ - if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) || - (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT)) + if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | + EP_CLEARING_TT | EP_DROP_PENDING)) return; trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); @@ -995,8 +995,10 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, * Can cause host hang. * Device will be reset to recover an inactive link, so don't do anything */ - if (rhub_port->link_inactive || !rhub_port->connected) + if (rhub_port->link_inactive || !rhub_port->connected) { + ep->ep_state |= EP_DROP_PENDING; return -ENODEV; + } /* add td to cancelled list and let reset ep handler take care of it */ if (reset_type == EP_HARD_RESET) { @@ -1066,6 +1068,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) td->urb, td->urb->stream_id); continue; } + + /* device disconnected or link error, ep will be dropped */ + if (ep->ep_state & EP_DROP_PENDING) { + td->cancel_status = TD_CLEARED; + continue; + } + /* * If a ring stopped on the TD we need to cancel then we have to * move the xHC endpoint ring dequeue pointer past this TD. @@ -1296,6 +1305,10 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, } } + /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ + if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) + ep->ep_state |= EP_DROP_PENDING; + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ xhci_invalidate_cancelled_tds(ep); ep->ep_state &= ~EP_STOP_CMD_PENDING; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6f830a43963f..71c25c2e71b9 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1852,7 +1852,7 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) } /* In this case no commands are pending but the endpoint is stopped */ - if (ep->ep_state & EP_CLEARING_TT) { + if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { /* and cancelled TDs can be given back right away */ xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", urb->dev->slot_id, ep_index, ep->ep_state); @@ -4089,6 +4089,9 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, ret = 0; command_cleanup: + for (i = 0; i < EP_CTX_PER_DEV; i++) + virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; + xhci_free_command(xhci, reset_device_cmd); return ret; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 0cc9edfd86f3..18d710abaf98 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -682,6 +682,7 @@ struct xhci_virt_ep { #define EP_SOFT_CLEAR_TOGGLE BIT(7) /* usb_hub_clear_tt_buffer is in progress */ #define EP_CLEARING_TT BIT(8) +#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; struct xhci_hcd *xhci; From 7c0c31c66a7f9daace156bac427aafb2f4bbb5fc Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 6 Aug 2026 17:21:02 +0300 Subject: [PATCH 133/163] xhci: move dequeue to next valid td instead of past cancelled one If a ring stops on a TD that is about to be cancelled then the xHC ring hardware dequeue pointer needs to move past the TD to flush TRBs from xHC cache. The TRB after the cancelled TD might be a no-op TRB, or a link TRB. Moving the dequeue to a link TRB has caused isses on some hosts, and moving it to a no-op TRB can be an issue for control endpoints as xhci specification 4.8.3 'Endpoint Context State" states that The Default Control Endpoint shall return to the Running state when the Doorbell is rung for the next Setup Stage TD sent to the endpoint. Solve this by always moving the dequeue pointer to the next valid TD. If ring is empty and there are no queued TDs then move the dequeue pointer to the enqueue pointer. If enqueue points to a link TRB on a empty ring then propagate enqueue to next segment before pointing dequeue to it. Note that this patch ended up almost identical to a simplifiaction patch done earlier by Michal Pecio, see link. That patch was not added due to a potential, somewhat theoretical issue of moving dequeue backwards. Turns out improving cancelled control transfers end up with the same code, and is now worth taking. Code is very likely subconsciously based the patch by Michal Pecio. Link: https://lore.kernel.org/linux-usb/20250225125939.7a248e38@foxbook/ Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 140 ++++++++++++++++------------------- 1 file changed, 63 insertions(+), 77 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 51008bad16cd..c868bf4deaef 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -686,112 +686,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, return le64_to_cpu(ep_ctx->deq); } -static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_td *td) +/* + * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or + * to enqueue if no TDs are queued (empty ring) + * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list + * before calling this function + */ +static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, + unsigned int stream_id) { - struct xhci_virt_device *dev = xhci->devs[slot_id]; - struct xhci_virt_ep *ep = &dev->eps[ep_index]; - struct xhci_ring *ep_ring; struct xhci_command *cmd; - struct xhci_segment *new_seg; - union xhci_trb *new_deq; - int new_cycle; + struct xhci_ring *ring; + struct xhci_td *td; dma_addr_t addr; - u64 hw_dequeue; - bool hw_dequeue_found = false; - bool td_last_trb_found = false; + int new_cycle; u32 trb_sct = 0; - int ret; + int ret = 0; - ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, - ep_index, stream_id); - if (!ep_ring) { + ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); + if (!ring) { xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", stream_id); return -ENODEV; } - hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; - new_seg = ep_ring->deq_seg; - new_deq = ep_ring->dequeue; - new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; - - /* - * Walk the ring until both the next TRB and hw_dequeue are found (don't - * move hw_dequeue back if it went forward due to a HW bug). Cycle state - * is loaded from a known good TRB, track later toggles to maintain it. - */ - do { - if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) - == (dma_addr_t)hw_dequeue) { - hw_dequeue_found = true; - if (td_last_trb_found) - break; - } - if (new_deq == td->end_trb) - td_last_trb_found = true; - - if (td_last_trb_found && trb_is_link(new_deq) && - link_trb_toggles_cycle(new_deq)) - new_cycle ^= 0x1; - - next_trb(&new_seg, &new_deq); - - /* Search wrapped around, bail out */ - if (new_deq == ep->ring->dequeue) { - xhci_err(xhci, "Error: Failed finding new dequeue state\n"); - return -EINVAL; - } - - } while (!hw_dequeue_found || !td_last_trb_found); - - /* Don't update the ring cycle state for the producer (us). */ - addr = xhci_trb_virt_to_dma(new_seg, new_deq); - if (addr == 0) { - xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); - xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); - return -EINVAL; - } - if ((ep->ep_state & SET_DEQ_PENDING)) { - xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", - &addr); + xhci_warn(xhci, "Set TR Deq already pending\n"); return -EBUSY; } /* This function gets called from contexts where it cannot sleep */ cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); if (!cmd) { - xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); + xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); return -ENOMEM; } + /* + * Move dequeue to the beginning of next td, or to enqueue if ring is + * empty. Avoid moving dequeue to a link trb (empty ring) as it causes + * issues on some hosts. In that case advance the enqueue to next segment + * before moving dequeue to it + */ + + if (list_empty(&ring->td_list)) { + if (trb_is_link(ring->enqueue)) + inc_enq_past_link(xhci, ring, 0); + ep->queued_deq_seg = ring->enq_seg; + ep->queued_deq_ptr = ring->enqueue; + new_cycle = ring->cycle_state; + } else { + td = list_first_entry(&ring->td_list, struct xhci_td, td_list); + ep->queued_deq_seg = td->start_seg; + ep->queued_deq_ptr = td->start_trb; + new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; + } + + addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); + if (addr == 0) { + xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", + ep->queued_deq_seg, ep->queued_deq_ptr); + ret = -EINVAL; + goto err_out; + } + if (stream_id) trb_sct = SCT_FOR_TRB(SCT_PRI_TR); ret = queue_command(xhci, cmd, lower_32_bits(addr) | trb_sct | new_cycle, upper_32_bits(addr), - STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | - EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); - if (ret < 0) { - xhci_free_command(xhci, cmd); - return ret; - } - ep->queued_deq_seg = new_seg; - ep->queued_deq_ptr = new_deq; + STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | + EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); + if (ret < 0) + goto err_out; xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); - /* Stop the TD queueing code from ringing the doorbell until - * this command completes. The HC won't set the dequeue pointer - * if the ring is running, and ringing the doorbell starts the - * ring running. + /* + * Stop the TD queueing code from ringing the doorbell until this + * command completes. The HC won't set the dequeue pointer if the ring + * is running, and ringing the doorbell starts the ring. */ ep->ep_state |= SET_DEQ_PENDING; xhci_ring_cmd_db(xhci); + return 0; + +err_out: + xhci_free_command(xhci, cmd); + ep->queued_deq_seg = NULL; + ep->queued_deq_ptr = NULL; + + return ret; } /* flip_cycle means flip the cycle bit of all but the first and last TRB. @@ -1043,7 +1031,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) struct xhci_td *cached_td = NULL; struct xhci_ring *ring; u64 hw_deq; - unsigned int slot_id = ep->vdev->slot_id; int err; /* @@ -1126,9 +1113,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) if (!cached_td) return 0; - err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, - cached_td->urb->stream_id, - cached_td); + err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); + if (err) { /* Failed to move past cached td, just set cached TDs to no-op */ list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { From a916fa66a43e10f63198b6ce978badffc678821a Mon Sep 17 00:00:00 2001 From: Lucas De Marchi Date: Thu, 6 Aug 2026 17:21:03 +0300 Subject: [PATCH 134/163] xhci: dbgtty: Fix unregister on tty_register_driver() failure If tty_register_driver() fails, it drops the reference, but fails to set the global dbc_tty_driver to NULL, causing the unregister to be called again when module exits. On module unload dbc_tty_exit() only gates its cleanup on the driver pointer being non-NULL, so it operates on the already-freed driver: module_init(xhci_hcd_init) xhci_hcd_init() xhci_dbc_init() [return value ignored] dbc_tty_init() tty_register_driver() fails tty_driver_kref_put() -> driver freed (dbc_tty_driver left dangling) ... module_exit(xhci_hcd_fini) xhci_hcd_fini() xhci_dbc_exit() dbc_tty_exit() if (dbc_tty_driver) -> true (dangling) tty_unregister_driver() -> use-after-free Fixes: 4521f1613940 ("xhci: dbctty: split dbc tty driver registration and unregistration functions.") Cc: stable@vger.kernel.org # v5.10 Cc: Mathias Nyman Cc: Greg Kroah-Hartman Signed-off-by: Lucas De Marchi Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-dbgtty.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 2e7384c6b6ec..01decd46f313 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -651,6 +651,7 @@ int dbc_tty_init(void) pr_err("Can't register dbc tty driver\n"); tty_driver_kref_put(dbc_tty_driver); idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; } return ret; From 25b8dfc13495a6c1cf4abacc8ef20196c7f20e5c Mon Sep 17 00:00:00 2001 From: Lucas De Marchi Date: Thu, 6 Aug 2026 17:21:04 +0300 Subject: [PATCH 135/163] xhci: dbgtty: Fix unregister on tty_alloc_driver() failure Make sure to set dbc_tty_driver to NULL to match the check in dbc_tty_exit(). For that, make detached error handling path common to the other branch in the same function. Fixes: 4521f1613940 ("xhci: dbctty: split dbc tty driver registration and unregistration functions.") Cc: stable@vger.kernel.org # v5.10 Cc: Mathias Nyman Cc: Greg Kroah-Hartman Signed-off-by: Lucas De Marchi Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-dbgtty.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 01decd46f313..7e44e62d6ea9 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -628,8 +628,8 @@ int dbc_tty_init(void) dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); if (IS_ERR(dbc_tty_driver)) { - idr_destroy(&dbc_tty_minors); - return PTR_ERR(dbc_tty_driver); + ret = PTR_ERR(dbc_tty_driver); + goto fail; } dbc_tty_driver->driver_name = "dbc_serial"; @@ -649,11 +649,17 @@ int dbc_tty_init(void) ret = tty_register_driver(dbc_tty_driver); if (ret) { pr_err("Can't register dbc tty driver\n"); - tty_driver_kref_put(dbc_tty_driver); - idr_destroy(&dbc_tty_minors); - dbc_tty_driver = NULL; + goto fail_put; } + return ret; + +fail_put: + tty_driver_kref_put(dbc_tty_driver); +fail: + idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; + return ret; } From d4dd5d43fadf6953e2527de02d090c50de6e0fd4 Mon Sep 17 00:00:00 2001 From: Lucas De Marchi Date: Thu, 6 Aug 2026 17:21:05 +0300 Subject: [PATCH 136/163] xhci: dbgtty: Drop extra call to idr_destroy() idr_destroy() is already called on error paths in dbc_tty_init(). Do not call it again on exit. For symmetry with the init side, also use IS_ERR_OR_NULL() to gate the exit steps. Signed-off-by: Lucas De Marchi Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-dbgtty.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 7e44e62d6ea9..3d51e8d82659 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -665,11 +665,11 @@ int dbc_tty_init(void) void dbc_tty_exit(void) { - if (dbc_tty_driver) { - tty_unregister_driver(dbc_tty_driver); - tty_driver_kref_put(dbc_tty_driver); - dbc_tty_driver = NULL; - } + if (IS_ERR_OR_NULL(dbc_tty_driver)) + return; + tty_unregister_driver(dbc_tty_driver); + tty_driver_kref_put(dbc_tty_driver); idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; } From 78203d5b54a40f0e36196ebf31c9c7a380fc8811 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 6 Aug 2026 17:21:06 +0300 Subject: [PATCH 137/163] usb: xhci: bail out of setup if the controller is inaccessible xhci_gen_setup() locates the operational registers using the capability length read from the very first register: xhci->op_regs = hcd->regs + HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)); If the controller is dead or has dropped off the bus, that read returns ~0, HC_LENGTH() truncates it to 0xff, and op_regs ends up 0xff bytes past the page-aligned MMIO base, i.e. unaligned. The first access through it, xhci_halt() -> xhci_handshake() reading op_regs->status, is then an unaligned readl() on device memory. arm64 faults on unaligned device accesses, so instead of xhci_handshake() catching the all-ones value and returning -ENODEV, setup oopses: xhci-pci-renesas 0005:08:00.0: Unable to change power state from D3cold to D0, device inaccessible xhci-pci-renesas 0005:08:00.0: xHCI Host Controller xhci-pci-renesas 0005:08:00.0: new USB bus registered, assigned bus number 1 Unable to handle kernel paging request at virtual address ffff80030a770103 ESR = 0x0000000096000021 FSC = 0x21: alignment fault Internal error: Oops: 0000000096000021 [#1] SMP pc : xhci_halt [xhci_hcd] Call trace: xhci_halt xhci_gen_setup xhci_pci_setup usb_add_hcd usb_hcd_pci_probe xhci_pci_common_probe xhci_pci_renesas_probe This was hit with a Renesas uPD720201 that failed to power up ("Unable to change power state from D3cold to D0, device inaccessible") yet still reached the HCD probe path. Read the capability register once, and if it reads back the all-ones value (as xhci_handshake() and xhci_reset() already test for), abort setup with -ENODEV before op_regs is derived from it. Reading it once also avoids re-reading a register that may change under a concurrent hot-removal. Fixes: 66d4eadd8d06 ("USB: xhci: BIOS handoff and HW initialization.") Cc: stable@vger.kernel.org Signed-off-by: Breno Leitao Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 71c25c2e71b9..4473b3cd1d36 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5434,6 +5434,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) struct device *dev = hcd->self.sysdev; int retval; u32 hcs_params1; + u32 hc_capbase; /* Accept arbitrarily long scatter-gather lists */ hcd->self.sg_tablesize = ~0; @@ -5454,15 +5455,19 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) mutex_init(&xhci->mutex); xhci->main_hcd = hcd; xhci->cap_regs = hcd->regs; - xhci->op_regs = hcd->regs + - HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)); + hc_capbase = readl(&xhci->cap_regs->hc_capbase); + if (hc_capbase == U32_MAX) { + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } + xhci->op_regs = hcd->regs + HC_LENGTH(hc_capbase); xhci->run_regs = hcd->regs + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); /* Cache read-only capability registers */ hcs_params1 = readl(&xhci->cap_regs->hcs_params1); xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); - xhci->hci_version = HC_VERSION(readl(&xhci->cap_regs->hc_capbase)); + xhci->hci_version = HC_VERSION(hc_capbase); xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); From 6d45e9556d4a11b726e897d86e095b96db4550d8 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 6 Aug 2026 17:21:07 +0300 Subject: [PATCH 138/163] usb: xhci: standardize multi bit-field macros This patch aims to unify the format of register macros and masks within the xHCI driver. Currently, register macros have inconsistent bit-field masks, get macros, and set macros, with varying naming conventions and functionalities. ==================== Proposal ==================== * Introduce a standardized approach by using only mask macros for each bit field, leveraging GENMASK() for enhanced clarity. #define HCC_MAX_PSA GENMASK(15, 12) * Utilize FIELD_GET() and FIELD_PREP() macros directly in the C code for getting and setting values, ensuring consistency and readability. u32 psa = FIELD_GET(HCC_MAX_PSA, reg); * Maintain exceptions for macros that perform custom operations. #define CTX_SIZE(_hcc) (_hcc & HCC_64BYTE_CONTEXT ? 64 : 32) * Note, while FIELD_*() macros are beneficial, I am not suggesting that they should always be used. Instead, use them where they simplify the code and eliminate the necessity for custom get/set macros. In the example below, additional FIELD_PREP() or FIELD_MODIFY() is not beneficial. #define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ FIELD_GET(HCS_MAX_SP_LO, (p))) ==================== Improvements ==================== Simplified Macros: By reducing custom macros, the code becomes more straightforward. Macros FIELD_GET() and FIELD_PREP() are commonly used, which contributes to the code readability and consistency. $ git grep -n 'FIELD_GET' | wc -l 9027 $ git grep -n 'FIELD_PREP' | wc -l 15407 Consistent Return Type: All bit macros will return unsigned 64-bit values, mitigating potential cross-architecture issues. Unified Bit Range Definition: The mask macro will define bit ranges, eliminating separate definitions for get/set macros. Because, FIELD_GET() & FIELD_PREP() use mask macro. Cleaner header file with less macros: Fewer macros result in a cleaner and more manageable header file. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-12-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/host.c | 5 ++-- drivers/usb/host/xhci-caps.h | 41 ++++++++++++++++++--------------- drivers/usb/host/xhci-debugfs.c | 3 ++- drivers/usb/host/xhci-histb.c | 2 +- drivers/usb/host/xhci-hub.c | 4 ++-- drivers/usb/host/xhci-mem.c | 3 ++- drivers/usb/host/xhci-mtk.c | 4 ++-- drivers/usb/host/xhci-pci.c | 4 +++- drivers/usb/host/xhci-plat.c | 2 +- drivers/usb/host/xhci-ring.c | 4 +++- drivers/usb/host/xhci-tegra.c | 5 ++-- drivers/usb/host/xhci.c | 23 ++++++++++-------- 12 files changed, 57 insertions(+), 43 deletions(-) diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 96b588bd08cd..c5674161b2b0 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "../host/xhci-port.h" #include "../host/xhci-ext-caps.h" @@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) return; } - op_regs_base = HC_LENGTH(readl(xhci_regs)); + op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); reg = readl(xhci_regs + XHCI_HCSPARAMS1); - port_num = HCS_MAX_PORTS(reg); + port_num = FIELD_GET(HCS_MAX_PORTS, reg); for (i = 1; i <= port_num; i++) { offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h index 2f59b6ab1e45..68c27831e07e 100644 --- a/drivers/usb/host/xhci-caps.h +++ b/drivers/usb/host/xhci-caps.h @@ -5,22 +5,23 @@ */ #include +#include /* hc_capbase - bitmasks */ /* bits 7:0 - Capability Registers Length */ -#define HC_LENGTH(p) ((p) & 0xff) +#define HC_LENGTH GENMASK(7, 0) /* bits 15:8 - Rsvd */ /* bits 31:16 - Host Controller Interface Version Number */ -#define HC_VERSION(p) (((p) >> 16) & 0xffff) +#define HC_VERSION GENMASK(31, 16) /* HCSPARAMS1 - hcs_params1 - bitmasks */ /* bits 7:0 - Number of Device Slots */ -#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) -#define HCS_SLOTS_MASK 0xff -/* bits 18:8 - Number of Interrupters, max values is 1024 */ -#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) -/* bits 31:24, Max Ports - max value is 255 */ -#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff) +#define HCS_SLOTS_MASK GENMASK(7, 0) +/* bits 18:8 - Number of Interrupters, max values is 1024 */ +#define HCS_MAX_INTRS GENMASK(18, 8) +/* bits 23:19 - Rsvd */ +/* bits 31:24 - Max Ports, max values is 255 */ +#define HCS_MAX_PORTS GENMASK(31, 24) /* HCSPARAMS2 - hcs_params2 - bitmasks */ /* @@ -33,24 +34,25 @@ * Note: 1 Frame = 8 Microframes * xHCI specification section 5.3.4. */ -#define HCS_IST_VALUE(p) ((p) & 0x7) +#define HCS_IST_VALUE GENMASK(2, 0) #define HCS_IST_UNIT BIT(3) /* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ -#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +#define HCS_ERST_MAX GENMASK(7, 4) /* bits 20:8 - Rsvd */ /* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ -#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f) +#define HCS_MAX_SP_HI GENMASK(25, 21) /* bit 26 - Scratchpad restore, for save/restore HW state */ /* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ -#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f) -#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p)) +#define HCS_MAX_SP_LO GENMASK(31, 27) +#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ + FIELD_GET(HCS_MAX_SP_LO, (p))) /* HCSPARAMS3 - hcs_params3 - bitmasks */ /* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ -#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +#define HCS_U1_LATENCY GENMASK(7, 0) /* bits 15:8 - Rsvd */ /* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ -#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) +#define HCS_U2_LATENCY GENMASK(31, 16) /* HCCPARAMS1 - hcc_params - bitmasks */ /* bit 0 - 64-bit Addressing Capability */ @@ -77,19 +79,20 @@ /* bit 11 - Contiguous Frame ID Capability */ #define HCC_CFC BIT(11) /* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ -#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) +#define HCC_MAX_PSA GENMASK(15, 12) +#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) /* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ -#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) +#define HCC_EXT_CAPS GENMASK(31, 16) /* DBOFF - db_off - bitmasks */ /* bits 1:0 - Rsvd */ /* bits 31:2 - Doorbell Array Offset */ -#define DBOFF_MASK (0xfffffffc) +#define DBOFF_MASK GENMASK(31, 2) /* RTSOFF - run_regs_off - bitmasks */ /* bits 4:0 - Rsvd */ /* bits 31:5 - Runtime Register Space Offse */ -#define RTSOFF_MASK (~0x1f) +#define RTSOFF_MASK GENMASK(31, 5) /* HCCPARAMS2 - hcc_params2 - bitmasks */ /* bit 0 - U3 Entry Capability */ diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index d07276192256..2aa01d99f23d 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -9,6 +9,7 @@ #include #include +#include #include "xhci.h" #include "xhci-debugfs.h" @@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) xhci->debugfs_root, "reg-cap"); xhci_debugfs_regset(xhci, - HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), + FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), xhci_op_regs, ARRAY_SIZE(xhci_op_regs), xhci->debugfs_root, "reg-op"); diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c index 02396c8721dc..fddb43bf6323 100644 --- a/drivers/usb/host/xhci-histb.c +++ b/drivers/usb/host/xhci-histb.c @@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev) if (ret) goto put_usb3_hcd; - if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) xhci->shared_hcd->can_do_streams = 1; ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index b0264bd8577a..17ac05516642 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, if ((xhci->quirks & XHCI_LPM_SUPPORT)) { reg = readl(&xhci->cap_regs->hcs_params3); - ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); - ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); + ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); + ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); } if (wLength < le16_to_cpu(bos->wTotalLength)) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 2d7a61b3aaf2..5717bd830189 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -2301,7 +2302,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) if (!segs) segs = ERST_DEFAULT_SEGS; - max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2)); + max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; segs = min(segs, max_segs); ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index d9b865546a67..60af5fe29bd4 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, * and it's 3 when support it. */ - if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) + if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) xhci->quirks |= XHCI_BROKEN_STREAMS; } @@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) } usb3_hcd = xhci_get_usb3_hcd(xhci); - if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && + if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && !(xhci->quirks & XHCI_BROKEN_STREAMS)) usb3_hcd->can_do_streams = 1; diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 6b3fcba44b08..a8889081ae82 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) } usb3_hcd = xhci_get_usb3_hcd(xhci); - if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) + if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && + GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) usb3_hcd->can_do_streams = 1; /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 074d9c731639..6fd595f81a30 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s } usb3_hcd = xhci_get_usb3_hcd(xhci); - if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && + if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && !(xhci->quirks & XHCI_BROKEN_STREAMS)) usb3_hcd->can_do_streams = 1; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c868bf4deaef..80d53acc37fd 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -57,6 +57,8 @@ #include #include #include +#include + #include "xhci.h" #include "xhci-trace.h" @@ -3954,7 +3956,7 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, /* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ static int xhci_ist_microframes(struct xhci_hcd *xhci) { - int ist = HCS_IST_VALUE(xhci->hcs_params2); + int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); if (xhci->hcs_params2 & HCS_IST_UNIT) ist *= 8; diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index e7e6d569f1db..6f235d1e117e 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -6,6 +6,7 @@ * Copyright (C) 2014 Google, Inc. */ +#include #include #include #include @@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) u32 value; cap_regs = tegra->regs; - op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); + op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); @@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto remove_usb2; } - if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) xhci->shared_hcd->can_do_streams = 1; err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 4473b3cd1d36..2faf91966890 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, * level page entries), but that's an optional feature for xHCI host * controllers. xHCs must support at least 4 stream IDs. */ - max_streams = HCC_MAX_PSA(xhci->hcc_params); + max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params); if (*num_stream_ctxs > max_streams) { xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", max_streams); @@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, /* MaxPSASize value 0 (2 streams) means streams are not supported */ if ((xhci->quirks & XHCI_BROKEN_STREAMS) || - HCC_MAX_PSA(xhci->hcc_params) < 4) { + GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { xhci_dbg(xhci, "xHCI controller does not support streams.\n"); return -ENOSYS; } @@ -4608,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, int besl_device = 0; u32 field; - u2del = HCS_U2_LATENCY(xhci->hcs_params3); + u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3); field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); if (field & USB_BESL_SUPPORT) { @@ -5460,26 +5461,28 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) xhci_warn(xhci, "Host controller not accessible, removed?\n"); return -ENODEV; } - xhci->op_regs = hcd->regs + HC_LENGTH(hc_capbase); + xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); + xhci->run_regs = hcd->regs + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); /* Cache read-only capability registers */ hcs_params1 = readl(&xhci->cap_regs->hcs_params1); xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); - xhci->hci_version = HC_VERSION(hc_capbase); + xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); 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->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); + xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); + /* xhci-plat or xhci-pci might have set max_interrupters already */ if (!xhci->max_interrupters) - xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS); - else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1)) - xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1); + xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); + else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) + xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); xhci->quirks |= quirks; From bd15c4cb9e44b94023586f4fa8d500277d373793 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 6 Aug 2026 17:21:08 +0300 Subject: [PATCH 139/163] usb: xhci: use 64-bit Addressing Capability macro Simplify by replace BIT(0) call with its relevant macro. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-13-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2faf91966890..174fea16cd50 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5523,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) * DMA_BIT_MASK(32)) in this xhci_gen_setup(). */ if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) - xhci->hcc_params &= ~BIT(0); + xhci->hcc_params &= ~HCC_64BIT_ADDR; /* * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports From 77f60a6e8e83526fe8c17167349b87d6291fc1d2 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 6 Aug 2026 17:21:09 +0300 Subject: [PATCH 140/163] usb: xhci: remove redundant function wrapper The function ring_doorbell_for_active_rings() rings the doorbell for any rings with pending URBs. It has a trivial wrapper, xhci_ring_doorbell_for_active_rings(), which takes the same arguments and simply calls the former. Since the wrapper adds no functionality, remove it and rename ring_doorbell_for_active_rings() to xhci_ring_doorbell_for_active_rings(). Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-14-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 80d53acc37fd..21ef7284e957 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -575,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, } /* Ring the doorbell for any rings with pending URBs */ -static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, - unsigned int slot_id, - unsigned int ep_index) +void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index) { unsigned int stream_id; struct xhci_virt_ep *ep; @@ -600,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, } } -void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, - unsigned int slot_id, - unsigned int ep_index) -{ - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); -} - static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index) @@ -1303,7 +1295,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, /* Otherwise ring the doorbell(s) to restart queued transfers */ xhci_giveback_invalidated_tds(ep); - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) @@ -1546,13 +1538,13 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, __func__); xhci_invalidate_cancelled_tds(ep); /* Try to restart the endpoint if all is done */ - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); /* Start giving back any TDs invalidated above */ xhci_giveback_invalidated_tds(ep); } else { /* Restart any rings with pending URBs */ xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } } @@ -1587,7 +1579,7 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, /* if this was a soft reset, then restart */ if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, From bf9acb77c88f22c984ae1b78473deb5430265782 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 6 Aug 2026 17:21:10 +0300 Subject: [PATCH 141/163] usb: xhci: remove redundant 'xhci' pointer from endpoint struct The 'xhci_virt_ep' struct currently contains a pointer to its parent 'xhci_hcd' struct. Since all endpoint-related structs are contained within 'xhci_hcd', this pointer is redundant. Remove the 'xhci' pointer from 'xhci_virt_ep' and instead pass it explicitly to functions that require it, as some already do it. This change reduces unnecessary complexity and aligns the code with the rest of the xhci driver. Memory impact: For each device connected a struct 'xhci_virt_device' is allocated, this struct conatains a 31 slot array of struct 'xhci_virt_ep'. A USB hub consumes 1 slot, but every downstream device consumes another slot. This means that the total memory saved buy this patch is: Devices * 31 * 8 bytes Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-15-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 1 - drivers/usb/host/xhci-ring.c | 53 +++++++++++++++++------------------- drivers/usb/host/xhci.c | 2 +- drivers/usb/host/xhci.h | 3 +- 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 5717bd830189..cc916ee3cb71 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1002,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, for (i = 0; i < 31; i++) { dev->eps[i].ep_index = i; dev->eps[i].vdev = dev; - dev->eps[i].xhci = xhci; INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); } diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 21ef7284e957..6e60959e3faa 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -916,7 +916,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh } /* Complete the cancelled URBs we unlinked from td_list. */ -static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) +static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { struct xhci_ring *ring; struct xhci_td *td, *tmp_td; @@ -924,17 +924,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { - ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); + ring = xhci_urb_to_transfer_ring(xhci, td->urb); if (td->cancel_status == TD_CLEARED) { - xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", + xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", __func__, td->urb); - xhci_td_cleanup(ep->xhci, td, ring, td->status); + xhci_td_cleanup(xhci, td, ring, td->status); } else { - xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", __func__, td->urb, td->cancel_status); } - if (ep->xhci->xhc_state & XHCI_STATE_DYING) + if (xhci->xhc_state & XHCI_STATE_DYING) return; } } @@ -1017,9 +1017,8 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, * only call this when ring is not in a running state */ -static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) +static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { - struct xhci_hcd *xhci; struct xhci_td *td = NULL; struct xhci_td *tmp_td = NULL; struct xhci_td *cached_td = NULL; @@ -1034,8 +1033,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) if (ep->ep_state & SET_DEQ_PENDING) return 0; - xhci = ep->xhci; - list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", @@ -1136,23 +1133,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) * * Call under xhci->lock on a stopped endpoint. */ -void xhci_process_cancelled_tds(struct xhci_virt_ep *ep) +void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { - xhci_invalidate_cancelled_tds(ep); - xhci_giveback_invalidated_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); + xhci_giveback_invalidated_tds(xhci, ep); } /* * Returns the TD the endpoint ring halted on. * Only call for non-running rings without streams. */ -static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) +static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { struct xhci_td *td; u64 hw_deq; if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ - hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); + hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); hw_deq &= TR_DEQ_PTR_MASK; td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); if (trb_in_td(td, hw_deq)) @@ -1227,7 +1224,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, reset_type = EP_SOFT_RESET; } else { reset_type = EP_HARD_RESET; - td = find_halted_td(ep); + td = find_halted_td(xhci, ep); if (td) td->status = -EPROTO; } @@ -1290,11 +1287,11 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, ep->ep_state |= EP_DROP_PENDING; /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ - xhci_invalidate_cancelled_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); ep->ep_state &= ~EP_STOP_CMD_PENDING; /* Otherwise ring the doorbell(s) to restart queued transfers */ - xhci_giveback_invalidated_tds(ep); + xhci_giveback_invalidated_tds(xhci, ep); xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } @@ -1516,14 +1513,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, /* HW cached TDs cleared from cache, give them back */ list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { - ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); + ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); if (td->cancel_status == TD_CLEARING_CACHE) { td->cancel_status = TD_CLEARED; - xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", + xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", __func__, td->urb); - xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); + xhci_td_cleanup(xhci, td, ep_ring, td->status); } else { - xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", __func__, td->urb, td->cancel_status); } } @@ -1534,16 +1531,16 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, /* Check for deferred or newly cancelled TDs */ if (!list_empty(&ep->cancelled_td_list)) { - xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", __func__); - xhci_invalidate_cancelled_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); /* Try to restart the endpoint if all is done */ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); /* Start giving back any TDs invalidated above */ - xhci_giveback_invalidated_tds(ep); + xhci_giveback_invalidated_tds(xhci, ep); } else { /* Restart any rings with pending URBs */ - xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); + xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } } @@ -1570,12 +1567,12 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, "Ignoring reset ep completion code of %u", cmd_comp_code); /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ - xhci_invalidate_cancelled_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); /* Clear our internal halted state */ ep->ep_state &= ~EP_HALTED; - xhci_giveback_invalidated_tds(ep); + xhci_giveback_invalidated_tds(xhci, ep); /* if this was a soft reset, then restart */ if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 174fea16cd50..a9e47e178c28 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1857,7 +1857,7 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) /* and cancelled TDs can be given back right away */ xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", urb->dev->slot_id, ep_index, ep->ep_state); - xhci_process_cancelled_tds(ep); + xhci_process_cancelled_tds(xhci, ep); } else { /* Otherwise, queue a new Stop Endpoint command */ command = xhci_alloc_command(xhci, false, GFP_ATOMIC); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 18d710abaf98..c7bfa7f028d3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -685,7 +685,6 @@ struct xhci_virt_ep { #define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; - struct xhci_hcd *xhci; /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue * command. We'll need to update the ring's dequeue segment and dequeue * pointer after the command completes. @@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len); unsigned int xhci_num_trbs_free(struct xhci_ring *ring); int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, gfp_t gfp_flags); -void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); +void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool clear_ehb); From 91be401f28059b3bdbaedf82629ecade37c81c6c Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 6 Aug 2026 17:21:11 +0300 Subject: [PATCH 142/163] usb: xhci: replace Unicode quotes with ASCII apostrophes Non-ASCII characters trigger git send-email to prompt for encoding on each modification near them, which is unnecessary and annoying. Using plain ASCII avoids these prompts and does not change its meaning. This change only affects comments and has no functional impact. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-16-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 2 +- drivers/usb/host/xhci-mem.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 17ac05516642..470bafe1802b 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } /* In spec software should not attempt to suspend * a port unless the port reports that it is in the - * enabled (PED = ‘1’,PLS < ‘3’) state. + * enabled (PED = '1',PLS < '3') state. */ portsc = xhci_portsc_readl(port); if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index cc916ee3cb71..7a21ac81f9c8 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2084,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, addr, port_offset, port_count, major_revision); /* Port count includes the current port offset */ if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) - /* WTF? "Valid values are ‘1’ to MaxPorts" */ + /* WTF? "Valid values are '1' to MaxPorts" */ return; port_cap = &xhci->port_caps[xhci->num_port_caps++]; From 3e91ec3e7d80a327fb558207613c80415d3bf756 Mon Sep 17 00:00:00 2001 From: Semih Baskan Date: Thu, 6 Aug 2026 17:21:12 +0300 Subject: [PATCH 143/163] usb: xhci: Handle USB3 port events when there is one roothub handle_port_status() drops every USB3 port event when xhci->shared_hcd is NULL. The check dates from a time when xhci-plat always created a shared hcd, so a NULL one could only mean the hcd had been removed. Since commit 4736ebd7fcaf ("usb: host: xhci-plat: omit shared hcd if either root hub has no ports") that is no longer true. A controller whose USB2 root hub has no ports gets a single roothub, the USB3 rhub is served by the main hcd, and shared_hcd stays NULL for the lifetime of the device. Every SuperSpeed port event is then thrown away as bogus behind a debug message, so devices never enumerate even though the port sees the device and its change bits stay set: 0x006a1203 Powered Connected Enabled Link:U0 PortSpeed:4 Change: CSC WRC PRC PLC Broadcom Northstar is such a controller. USB3 works there up to 5.15 and stops working from 5.19 onwards. Ask xhci_get_usb3_hcd() instead. It returns the shared hcd when there is one, the main hcd when the USB2 root hub has no ports, and NULL once the shared hcd is gone, which keeps the original meaning of the check. Tested on an Asus RT-N18U (BCM47081), which has a single roothub. Before the change nothing enumerates on the USB3 port; after it SuperSpeed devices enumerate normally over repeated connect and disconnect cycles, the change bits shown above clear, and USB2 is unaffected on both ports. Fixes: 4736ebd7fcaf ("usb: host: xhci-plat: omit shared hcd if either root hub has no ports") Cc: stable@vger.kernel.org Signed-off-by: Semih Baskan Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-17-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 6e60959e3faa..69bd582c7e2b 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2019,7 +2019,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) vdev = xhci->devs[port->slot_id]; /* We might get interrupts after shared_hcd is removed */ - if (port->rhub == &xhci->usb3_rhub && xhci->shared_hcd == NULL) { + if (port->rhub == &xhci->usb3_rhub && xhci_get_usb3_hcd(xhci) == NULL) { xhci_dbg(xhci, "ignore port event for removed USB3 hcd\n"); bogus_port_status = true; goto cleanup; From 3d9eeb336131bc5a174367c384fa00c15c8744fd Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 6 Aug 2026 17:21:13 +0300 Subject: [PATCH 144/163] usb: xhci: Handle bogus TRB pointers in Missed Service Error events xHCI 1.0 allowed these pointers to be zero. Some Intel chipsets from the era usually set it to zero, but sometimes (apparently) to the next TRB after the one referenced by the previous transfer event on the endpoint. Usually that's indeed the missed TD, but it may also be the last TRB of a two-TRB TD already completed with Short Packet on its first TRB. Then the driver skips all pending TDs, failing to find a match. When handling Missed Service Error, scan TD list twice and only really skip TDs in the second pass if the first pass found a match. This won't catch bogus pointers to wrong TDs, but such a bug would be practically impossible to detect automatically and isn't known to exist. Reported-by: Bart Nagel Closes: https://lore.kernel.org/linux-usb/al_hchyOdPoPWKEo@spiral/ Suggested-by: Mathias Nyman Fixes: d0b619599e52 ("usb: xhci: Expedite skipping missed isoch TDs on modern HCs") Cc: stable@vger.kernel.org Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260806142113.2436238-18-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 69bd582c7e2b..97a1b53c18ef 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2597,6 +2597,17 @@ static bool xhci_spurious_success_tx_event(struct xhci_hcd *xhci, } } +static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, dma_addr_t dma) +{ + struct xhci_td *td; + + if (dma) + list_for_each_entry(td, &ep_ring->td_list, td_list) + if (trb_in_td(td, dma)) + return td; + return NULL; +} + /* * If this function returns an error condition, it means it got a Transfer * event with a corrupted Slot ID, Endpoint ID, or TRB DMA address. @@ -2791,8 +2802,11 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_dequeue_td(xhci, td, ep_ring, td->status); } - /* If the TRB pointer is NULL, missed TDs will be skipped on the next event */ - if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !ep_trb_dma) + /* + * We don't know how many TDs were missed when ep_trb_dma is zero (as permitted by + * xHCI 1.0) or bogus. Bail out leaving ep->skip set, next event will sort it out. + */ + if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !find_td_by_dma(ep_ring, ep_trb_dma)) return 0; if (list_empty(&ep_ring->td_list)) { From 92090f6ff2acc81e9dd99881dcfb4f8c1bdaabd3 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 2 Aug 2026 01:49:59 +0000 Subject: [PATCH 145/163] usb: typec: thunderbolt: Disable work before freeing tbt on remove tbt_altmode_remove() drops the plug and cable references without draining tbt->work. The work function dereferences those references, and can also requeue itself in its error path. The VDM callbacks can queue the same work item. Disable and drain tbt->work before dropping the references. This waits for an existing invocation and prevents subsequent schedule_work() calls from queueing it during teardown. This issue was found by an in-house static analysis tool and confirmed by manual code review. Fixes: 100e25738659 ("usb: typec: Add driver for Thunderbolt 3 Alternate Mode") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260802014959.416687-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/thunderbolt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c index 2eccdddf1b1f..2e1b1a1da7d9 100644 --- a/drivers/usb/typec/altmodes/thunderbolt.c +++ b/drivers/usb/typec/altmodes/thunderbolt.c @@ -307,6 +307,8 @@ static void tbt_altmode_remove(struct typec_altmode *alt) { struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + disable_work_sync(&tbt->work); + for (int i = TYPEC_PLUG_SOP_PP; i >= 0; --i) { if (tbt->plug[i]) typec_altmode_put_plug(tbt->plug[i]); From 5b1da38592efdc1a263d4c0353298cba19e9d6fc Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Tue, 4 Aug 2026 09:13:38 +0530 Subject: [PATCH 146/163] usb: gadget: uvc: Fix null pointer dereference in uvcg_video_init() In uvcg_video_init(), if kthread_run_worker() fails, the error logged uses uvcg_err(), however, the pointer it uses: video->uvc is not assigned at this point, triggering a null pointer dereference. Fix this by directly using uvc->func which is assigned already. Reported-by: syzbot+8dcac923582c28505fd7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=8dcac923582c28505fd7 Fixes: f0bbfbd16b3b ("usb: gadget: uvc: rework to enqueue in pump worker from encoded queue") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Reviewed-by: Xu Yang Link: https://patch.msgid.link/20260804034338.7976-1-jeffinphilip14@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 2f9700b3f1b6..9ba09118bb74 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -821,7 +821,7 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) /* Allocate a kthread for asynchronous hw submit handler. */ video->kworker = kthread_run_worker(0, "UVCG"); if (IS_ERR(video->kworker)) { - uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n"); + uvcg_err(&uvc->func, "failed to create UVCG kworker\n"); return PTR_ERR(video->kworker); } From 0f6bffb5008f0cba9cad5ded2caccc64466a6e54 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Thu, 30 Jul 2026 13:58:11 +0000 Subject: [PATCH 147/163] usb: gadget: midi2: remove default configfs groups on teardown f_midi2_alloc_inst() creates default configfs child groups for the default endpoint and default block using configfs_add_default_group(), setting their internal refcount to 1. However, during function teardown in f_midi2_free_inst() or EP cleanup in f_midi2_ep_opts_release(), configfs_remove_default_groups() is never called, therefore never dropping the refcount and leaking struct f_midi2_ep_opts and f_midi2_block_opts. Add the missing configfs_remove_default_groups() in the afformentioned functions to free the structs properly. Fixes: 8b645922b223 ("usb: gadget: Add support for USB MIDI 2.0 function driver") Cc: stable@vger.kernel.org Reported-by: syzbot+eaa106d192c9daf37f95@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=eaa106d192c9daf37f95 Tested-by: syzbot+eaa106d192c9daf37f95@syzkaller.appspotmail.com Signed-off-by: Joshua Crofts Link: https://patch.msgid.link/20260730135811.1498-1-joshua.crofts1@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index 19fdac024343..a4b72a6fad8a 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -2473,6 +2473,7 @@ static void f_midi2_ep_opts_release(struct config_item *item) { struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); + configfs_remove_default_groups(&opts->group); kfree(opts->info.ep_name); kfree(opts->info.product_id); kfree(opts); @@ -2639,6 +2640,7 @@ static void f_midi2_free_inst(struct usb_function_instance *f) opts = container_of(f, struct f_midi2_opts, func_inst); + configfs_remove_default_groups(&opts->func_inst.group); kfree(opts->info.iface_name); kfree(opts); } From c27d13ce4bab80fbdf6523928071b6c24b37606c Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 19 Jul 2026 04:28:39 +0000 Subject: [PATCH 148/163] usb: gadget: at91_udc: drain polled-VBUS timer/work before udc is freed In polled-VBUS mode (board.vbus_pin && board.vbus_polled), probe arms a self-restarting cycle: at91_vbus_timer() schedules vbus_timer_work, and at91_vbus_timer_work() calls at91_vbus_update() and re-arms the timer via mod_timer(). Both recover the same udc through container_of and dereference it on every iteration. Neither teardown path cancels this cycle. udc is devm-allocated, so it is freed after at91udc_remove() returns, and is likewise freed when probe fails and devres runs. A timer callback or work item that is pending or running at either point dereferences the freed udc. Add at91_udc_shutdown_vbus_timer() and call it from at91udc_remove() and from the usb_add_gadget_udc() failure path in probe; the remaining probe error paths fail before the timer is armed. timer_shutdown_sync() waits for a running callback and clears timer->function, which makes the work handler's mod_timer() a permanent no-op; cancel_work_sync() then drains any pending or running work whose re-arm attempt now does nothing. The timer must be shut down first, since cancelling the work alone would let the timer re-queue it. The guard mirrors probe: in IRQ mode the timer and work_struct are never initialized. This does not require a fault; a normal driver unbind can interleave with an already queued work item. This issue was found by an in-house static analysis tool. Fixes: 4037242c4f5f ("ARM: 6209/3: at91_udc: Add vbus polarity and polling mode") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260719042839.3167094-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/at91_udc.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index 5aa360ba4f03..8e5d7fb71549 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -1794,6 +1794,19 @@ static void at91udc_of_init(struct at91_udc *udc, struct device_node *np) udc->caps = match->data; } +/* + * The work handler re-arms this timer, so shut the timer down before + * draining the work; otherwise it restarts the polling cycle. + */ +static void at91_udc_shutdown_vbus_timer(struct at91_udc *udc) +{ + if (!(udc->board.vbus_pin && udc->board.vbus_polled)) + return; + + timer_shutdown_sync(&udc->vbus_timer); + cancel_work_sync(&udc->vbus_timer_work); +} + static int at91udc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1907,7 +1920,7 @@ static int at91udc_probe(struct platform_device *pdev) } retval = usb_add_gadget_udc(dev, &udc->gadget); if (retval) - goto err_unprepare_iclk; + goto err_shutdown_vbus; dev_set_drvdata(dev, udc); device_init_wakeup(dev, 1); create_debug_file(udc); @@ -1915,6 +1928,8 @@ static int at91udc_probe(struct platform_device *pdev) INFO("%s version %s\n", driver_name, DRIVER_VERSION); return 0; +err_shutdown_vbus: + at91_udc_shutdown_vbus_timer(udc); err_unprepare_iclk: clk_unprepare(udc->iclk); err_unprepare_fclk: @@ -1933,6 +1948,9 @@ static void at91udc_remove(struct platform_device *pdev) DBG("remove\n"); usb_del_gadget_udc(&udc->gadget); + + at91_udc_shutdown_vbus_timer(udc); + if (udc->driver) { dev_err(&pdev->dev, "Driver still in use but removing anyhow\n"); From 569dd7e5dcffe1e1c6b26ca2cd3be57eb433e082 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Fri, 24 Jul 2026 20:41:16 +0000 Subject: [PATCH 149/163] usb: gadget: f_fs: Prevent deadlock during ep0 read loop Currently, ffs_ep0_read() holds ffs->mutex when it prepares to go to sleep waiting for an event. When no setup events are pending, it calls wait_event_interruptible_exclusive_locked_irq() with the mutex still held. The wait macro deliberately drops the waitqueue spinlock before sleeping but does not drop the mutex. If a userspace daemon is polling ep0 via read() and the gadget is asynchronously torn down via configfs (e.g., echo "" > UDC), a deadlock can occur: 1. The configfs teardown calls functionfs_unbind(), which queues a FUNCTIONFS_UNBIND event. 2. The daemon wakes up, consumes the event, and drops the mutex. 3. However, if the daemon loops and immediately issues another read() before exiting, it reacquires ffs->mutex and again goes into an interruptible sleep. 4. Meanwhile, functionfs_unbind() continues execution and attempts to acquire ffs->mutex to tear down ep0req. 5. The kernel deadlocks because the configfs thread is stuck in an uninterruptible sleep waiting for the mutex, while the userspace daemon is in an interruptible sleep holding the mutex forever because no more events will arrive. To fix this, we drop both the waitqueue spinlock and ffs->mutex before going to sleep, and use wait_event_interruptible_exclusive() instead. Upon waking up, we jump back to the `retry` label to safely reacquire the mutex and re-evaluate the state machine. By not sleeping with ffs->mutex held, we natively decouple gadget teardowns (which require the mutex) from userspace polling. Fixes: ddf8abd25994 ("USB: f_fs: the FunctionFS driver") Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20260724204117.4036015-1-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 073c4cbd90fb..cfa05266e551 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -551,6 +551,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; +retry: /* Acquire mutex */ ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); if (ret < 0) @@ -585,10 +586,15 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, break; } - if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq, - ffs->ev.count)) { - ret = -EINTR; - break; + if (!ffs->ev.count) { + spin_unlock_irq(&ffs->ev.waitq.lock); + mutex_unlock(&ffs->mutex); + + if (wait_event_interruptible_exclusive(ffs->ev.waitq, + ffs->ev.count)) + return -EINTR; + + goto retry; } /* unlocks spinlock */ From e78dcb1f7ec271449c54984dc90c62a5ba272de7 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Fri, 24 Jul 2026 23:51:00 +0000 Subject: [PATCH 150/163] usb: gadget: f_fs: Fix Use-After-Free in AIO error path In ffs_epfile_write_iter() and ffs_epfile_read_iter(), when ffs_epfile_io() fails with an error other than -EIOCBQUEUED, the io_data structure (`p`) is freed. However, for AIO operations, the kiocb cancel function was already armed and kiocb->private was set to `p`. If a concurrent cancel operation (such as sys_io_cancel()) executes after ffs_epfile_io() fails but before the function frees `p`, a Use-After-Free can occur when the cancellation handler accesses the freed pointer. To securely fix this race condition, we must properly un-arm the cancellation. Invoking `kiocb->ki_complete()` does exactly this by acquiring `ctx->ctx_lock` and safely removing the kiocb from the active sequence. In doing so, it ensures that a parallel io_cancel can no longer discover the kiocb, effectively closing the race window. We then return -EIOCBQUEUED to notify the VFS layer that the kiocb has been consumed and it should avoid attempting to complete the request again or triggering subsequent completion handlers. Fixes: de2080d41b5d ("gadget/function/f_fs.c: close leaks") Cc: stable@vger.kernel.org Reported-by: Xingyu Jin Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20260724235100.106011-1-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index cfa05266e551..43962e05eacf 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1296,8 +1296,10 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) if (res == -EIOCBQUEUED) return res; if (p->aio) { + kiocb->ki_complete(kiocb, res); mmdrop(p->mm); kfree(p); + return -EIOCBQUEUED; } else { *from = p->data; } @@ -1345,9 +1347,11 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) return res; if (p->aio) { + kiocb->ki_complete(kiocb, res); mmdrop(p->mm); kfree(p->to_free); kfree(p); + return -EIOCBQUEUED; } else { *to = p->data; } From bf1e90189a98ca4a824fd64b4f3c6043d13c98ea Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Tue, 28 Jul 2026 17:44:20 +0200 Subject: [PATCH 151/163] usb: dwc2: gadget: Exit partial power down state when changing USB pull-up When a USB host suspends a connected device, the DWC2 USB device controller enters a partial power down state where controller registers are not accessible. If the USB gadget is then disconnected or deactivated (e.g. when a gadget function is unbound from the controller), the `pullup` callback in struct usb_gadget_ops is invoked; if the controller is kept in partial power down, the register write in dwc2_hsotg_core_disconnect() does not take effect; as a result, the USB host keeps seeing the device as connected, even though the device is disabled. Properly exit partial power down state in the pullup callback, so that the USB host detects a device disconnection as intended. Fixes: 97861781daff ("usb: dwc2: Allow entering hibernation from USB_SUSPEND interrupt") Cc: stable@vger.kernel.org Signed-off-by: Francesco Lavra Link: https://patch.msgid.link/20260728154420.2021519-1-flavra@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c8b02c27d27d..102169b40419 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -4680,6 +4680,7 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) { struct dwc2_hsotg *hsotg = to_hsotg(gadget); unsigned long flags; + int ret = 0; dev_dbg(hsotg->dev, "%s: is_on: %d op_state: %d\n", __func__, is_on, hsotg->op_state); @@ -4691,6 +4692,13 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) } spin_lock_irqsave(&hsotg->lock, flags); + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) { + dev_err(hsotg->dev, "exit partial_power_down failed\n"); + goto exit; + } + } if (is_on) { hsotg->enabled = 1; dwc2_hsotg_core_init_disconnected(hsotg, false); @@ -4704,9 +4712,10 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) } hsotg->gadget.speed = USB_SPEED_UNKNOWN; +exit: spin_unlock_irqrestore(&hsotg->lock, flags); - return 0; + return ret; } static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) From 9dbf74f4022f80f7669d2b3c22c5deb46c1b5674 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 31 Jul 2026 16:11:51 +0800 Subject: [PATCH 152/163] usb: gadget: f_tcm: fix deadlock in usbg_make_tpg() usbg_make_tpg() held dep_lock while calling configfs_depend_item_unlocked(), which acquires the configfs root inode lock when operating across subsystems. This creates a circular lock dependency with configfs_rmdir(): dep_lock -> configfs root inode lock -> su_mutex -> dep_lock In usbg_make_tpg(), dep_lock only serialized the read of opts->ready, which is a monotonic flag that transitions from false to true exactly once (in tcm_set_name()) and never reverts. Remove dep_lock from usbg_make_tpg() entirely and use READ_ONCE/WRITE_ONCE to access opts->ready locklessly instead. Reported-by: syzbot+c9f9d646b08f3b6032fe@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c9f9d646b08f3b6032fe Fixes: 4bb8548df632 ("usb: gadget: f_tcm: add configfs support") Cc: stable@vger.kernel.org Signed-off-by: Yun Zhou Link: https://patch.msgid.link/20260731081151.285599-1-yun.zhou@windriver.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_tcm.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index b3fa5a17fd2d..c727c19db25d 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -1675,19 +1675,25 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts, func_inst); - mutex_lock(&opts->dep_lock); - if (!opts->ready) - goto unlock_dep; + if (!READ_ONCE(opts->ready)) + goto unlock_inst; if (opts->has_dep) { if (!try_module_get(opts->dependent)) - goto unlock_dep; + goto unlock_inst; } else { + /* + * configfs_depend_item_unlocked() may acquire the configfs + * root inode lock when the target belongs to a different + * subsystem. Calling it under dep_lock would create a + * circular dependency: + * dep_lock -> configfs inode lock -> su_mutex -> dep_lock + */ ret = configfs_depend_item_unlocked( wwn->wwn_group.cg_subsys, &opts->func_inst.group.cg_item); if (ret) - goto unlock_dep; + goto unlock_inst; } tpg = kzalloc_obj(struct usbg_tpg); @@ -1714,7 +1720,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, tpg_instances[i].tpg = tpg; tpg->fi = tpg_instances[i].func_inst; - mutex_unlock(&opts->dep_lock); mutex_unlock(&tpg_instances_lock); return &tpg->se_tpg; @@ -1727,8 +1732,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, module_put(opts->dependent); else configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item); -unlock_dep: - mutex_unlock(&opts->dep_lock); unlock_inst: mutex_unlock(&tpg_instances_lock); @@ -2666,9 +2669,7 @@ static int tcm_set_name(struct usb_function_instance *f, const char *name) pr_debug("tcm: Activating %s\n", name); - mutex_lock(&opts->dep_lock); - opts->ready = true; - mutex_unlock(&opts->dep_lock); + WRITE_ONCE(opts->ready, true); return 0; } From 886338ea7d40e4ba5123c58204d7f7e53d825825 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Tue, 4 Aug 2026 23:05:10 +0900 Subject: [PATCH 153/163] usb: gadget: snps_udc_plat: clean up PHY on probe deferral When the referenced extcon device has not registered yet, extcon_get_edev_by_phandle() returns -EPROBE_DEFER after the driver has initialized and powered on the PHY. The direct return bypasses the common cleanup path and leaves both operations unbalanced. Store the lookup error first and route deferred probing through exit_phy, while retaining the existing behavior of suppressing the error message for deferral. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 1b9f35adb0ff ("usb: gadget: udc: Add Synopsys UDC Platform driver") Cc: stable@vger.kernel.org Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260804140510.37639-1-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/snps_udc_plat.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c index db842a6de643..0ee3ed185d9c 100644 --- a/drivers/usb/gadget/udc/snps_udc_plat.c +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -159,10 +159,9 @@ static int udc_plat_probe(struct platform_device *pdev) if (of_property_present(dev->of_node, "extcon")) { udc->edev = extcon_get_edev_by_phandle(dev, 0); if (IS_ERR(udc->edev)) { - if (PTR_ERR(udc->edev) == -EPROBE_DEFER) - return -EPROBE_DEFER; - dev_err(dev, "Invalid or missing extcon\n"); ret = PTR_ERR(udc->edev); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Invalid or missing extcon\n"); goto exit_phy; } From eb4573cf2fd860b20adfae050c3f6ec6ddc3abdb Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Wed, 5 Aug 2026 16:57:25 +0800 Subject: [PATCH 154/163] usb: typec: ucsi: use UCSI_TIMEOUT_MS for sync command completion The synchronous command completion path in ucsi_sync_control_common() hardcodes a 5 second (5 * HZ) timeout when waiting for the PPM to signal command completion via ACPI notification. This value matched UCSI_TIMEOUT_MS when it was still 5000 ms, but it was not updated when that macro was later raised to 10000 ms to fix PPM reset timeouts. As a result, the two PPM communication paths are now inconsistent: the polling path in ucsi_reset_ppm() respects the 10 second timeout, while the event-driven completion path still uses 5 seconds. On machines where the firmware is slow to respond during boot (e.g. some Lenovo ThinkPad models such as the E14 Gen 7), commands sent after the PPM reset, such as SET_NOTIFICATION_ENABLE and GET_CAPABILITY, can exceed 5 seconds and cause UCSI initialization to fail with: ucsi_acpi USBC000:00: error -ETIMEDOUT: PPM init failed Once UCSI init aborts, USB-C PD negotiation never completes, which in turn blocks USB-C dock enumeration since the dock depends on a successful PD contract. Replace the hardcoded 5 * HZ with msecs_to_jiffies(UCSI_TIMEOUT_MS) so that both communication paths share a single, consistent timeout value, and future adjustments to UCSI_TIMEOUT_MS are picked up automatically. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221740 Link: https://bugzilla.kernel.org/show_bug.cgi?id=2183790 Fixes: bf4f9ae1cb08c ("usb: typec: ucsi: increase timeout for PPM reset operations") Cc: stable@vger.kernel.org Signed-off-by: Huang Wei Reviewed-by: Heikki Krogerus Reviewed-by: Fedor Pchelkin Link: https://patch.msgid.link/20260805085725.389761-1-huangwei@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index ecbda2a3783c..bef3f9b71d71 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -91,7 +91,8 @@ int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, if (ret) goto out_clear_bit; - if (!wait_for_completion_timeout(&ucsi->complete, 5 * HZ)) + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) ret = -ETIMEDOUT; out_clear_bit: From b1e24de475bf2d66fffc9103f3444b783527d55a Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Wed, 5 Aug 2026 21:35:02 -0400 Subject: [PATCH 155/163] USB: c67x00: fix use-after-free in c67x00_add_iso_urb() When TD creation fails for the last packet of an isochronous URB, c67x00_add_iso_urb() gives the URB back before updating the endpoint scheduling state. c67x00_giveback_urb() frees the URB private data, and the completion callback may release the final URB reference. The following accesses to urbp->ep_data, urb->interval, and urbp->cnt can therefore use freed memory. Update next_frame and cnt before giving back the failed final packet, making the giveback the last operation that uses the URB and its private data. Fixes: e9b29ffc519b ("USB: add Cypress c67x00 OTG controller HCD driver") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Link: https://patch.msgid.link/20260806013502.322067-1-shuangpeng.kernel@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/c67x00/c67x00-sched.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c index a832f5696f2a..ae6c94f41cb3 100644 --- a/drivers/usb/c67x00/c67x00-sched.c +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -761,13 +761,13 @@ static int c67x00_add_iso_urb(struct c67x00_hcd *c67x00, struct urb *urb) ret); urb->iso_frame_desc[urbp->cnt].actual_length = 0; urb->iso_frame_desc[urbp->cnt].status = ret; - if (urbp->cnt + 1 == urb->number_of_packets) - c67x00_giveback_urb(c67x00, urb, 0); } urbp->ep_data->next_frame = frame_add(urbp->ep_data->next_frame, urb->interval); urbp->cnt++; + if (ret && urbp->cnt == urb->number_of_packets) + c67x00_giveback_urb(c67x00, urb, 0); } return 0; } From b691a07c5f644080374ddd24de6a0e05f5d28744 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 23 Jul 2026 18:46:14 +0800 Subject: [PATCH 156/163] usb: typec: tcpci: pass correct rx_type to tcpm_pd_receive() Previously, tcpci_irq() always passed TCPC_TX_SOP as the receive type to tcpm_pd_receive(), ignoring the actual frame type reported by the TCPC_RX_BUF_FRAME_TYPE register. Cache the TCPC_RX_DETECT register value in rx_type_mask variable. When a PD messageis received, read TCPC_RX_BUF_FRAME_TYPE register and handle the message only if its frame type is enabled in mask. The TCPC_RX_BUF_FRAME_TYPE register records the received message type, which has a 1:1 mapping to enum tcpm_transmit_type. Fixes: fb7ff25ae433 ("usb: typec: tcpm: add discover identity support for SOP'") Cc: stable@vger.kernel.org Signed-off-by: Xu Yang Acked-by: Heikki Krogerus Reviewed-by: Badhri Jagan Sridharan Link: https://patch.msgid.link/20260723104614.3717623-1-xu.yang_2@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 12 +++++++++++- include/linux/usb/tcpci.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 7ac7000b2d13..6717ac914c6a 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -38,6 +38,7 @@ struct tcpci { struct regmap *regmap; unsigned int alert_mask; + unsigned int rx_type_mask; bool controls_vbus; @@ -488,6 +489,8 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) if (tcpci->data->cable_comm_capable) reg |= TCPC_RX_DETECT_SOP1; } + + tcpci->rx_type_mask = reg; ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); if (ret < 0) return ret; @@ -749,6 +752,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) if (status & TCPC_ALERT_RX_STATUS) { struct pd_message msg; unsigned int cnt, payload_cnt; + enum tcpm_transmit_type rx_type; u16 header; regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt); @@ -773,10 +777,16 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) regmap_raw_read(tcpci->regmap, TCPC_RX_DATA, &msg.payload, payload_cnt); + ret = regmap_read(tcpci->regmap, TCPC_RX_BUF_FRAME_TYPE, &rx_type); + if (ret) + return ret; + /* Read complete, clear RX status alert bit */ tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP); + rx_type &= TCPC_RX_BUF_FRAME_TYPE_MASK; + if (tcpci->rx_type_mask & BIT(rx_type)) + tcpm_pd_receive(tcpci->port, &msg, rx_type); } if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) { diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h index f7f5cfbdef12..9b46a6bc762c 100644 --- a/include/linux/usb/tcpci.h +++ b/include/linux/usb/tcpci.h @@ -144,6 +144,7 @@ #define TCPC_RX_BUF_FRAME_TYPE 0x31 #define TCPC_RX_BUF_FRAME_TYPE_SOP 0 #define TCPC_RX_BUF_FRAME_TYPE_SOP1 1 +#define TCPC_RX_BUF_FRAME_TYPE_MASK GENMASK(2, 0) #define TCPC_RX_HDR 0x32 #define TCPC_RX_DATA 0x34 /* through 0x4f */ From 00e2071f6d5621a5ddea311a5e6b143ae6e474af Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Thu, 6 Aug 2026 15:26:51 +0000 Subject: [PATCH 157/163] usb: usbtest: disable dynamic ID support The usbtest driver relies on the driver_info field of struct usb_device_id to point to a valid struct usbtest_info descriptor. This structure contains essential test configurations, such as endpoint addresses and test modes, which are required during probe. When a user dynamically adds a new device ID via the sysfs new_id interface without specifying a reference device, the USB core initializes driver_info to 0 (NULL). When a matching device is subsequently probed, usbtest_probe() unconditionally casts driver_info to a struct usbtest_info pointer and dereferences it, leading to a NULL pointer dereference crash: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000001: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f] RIP: 0010:usbtest_probe+0x3b9/0x1280 drivers/usb/misc/usbtest.c:2822 Because usbtest strictly requires pre-defined usbtest_info descriptors to function, dynamic ID binding via sysfs is fundamentally unsupported for this driver. Fix this by setting .no_dynamic_id = 1 on usbtest_driver. This instructs the USB core to skip creating the new_id and remove_id sysfs interfaces for usbtest, preventing invalid dynamic ID entries from being created. Cc: stable@vger.kernel.org Reported-by: syzbot+7e1e5911f9eac50bedc7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7e1e5911f9eac50bedc7 Signed-off-by: Aleksandr Nogikh Tested-by: syzbot@syzkaller.appspotmail.com Link: https://patch.msgid.link/20260806152651.2370795-1-nogikh@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbtest.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 98071b25ac07..8759df49be28 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -3054,6 +3054,7 @@ static struct usb_driver usbtest_driver = { .disconnect = usbtest_disconnect, .suspend = usbtest_suspend, .resume = usbtest_resume, + .no_dynamic_id = 1, }; /*-------------------------------------------------------------------------*/ From c39d0916da47d94909391876c9e5bd429ea7b1b9 Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Fri, 7 Aug 2026 02:07:33 -0400 Subject: [PATCH 158/163] usb: gadget: f_tcm: keep port count until LUN teardown completes tcm_usbg_drop_nexus() permits session removal once tpg_port_count reaches zero. However, usbg_port_unlink() currently decrements that count from the fabric_pre_unlink() callback, before core_dev_del_lun() waits for active se_lun references to drain. If removal of the last LUN races a nexus removal, the latter can observe a zero port count and call target_remove_session(). This frees sess_cmd_map while an in-flight struct usbg_cmd, including its work item, can still be accessed. Overlapping the last-LUN unlink with nexus removal reproduces this lifetime violation as a DEBUG_OBJECTS "free active" warning for usbg_cmd_work, followed by a target-core BUG/Oops. The generic target-core unlink path has no callback after core_dev_del_lun() completes. Add an optional fabric_post_unlink() callback and use it for the f_tcm port count. The count now remains nonzero until core_dev_del_lun() has finished draining active LUN references, preventing nexus removal from freeing the session during command completion. Fixes: c52661d60f63 ("usb-gadget: Initial merge of target module for UASP + BOT") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Link: https://patch.msgid.link/20260807060733.3186624-1-shuangpeng.kernel@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/target/target_core_fabric_configfs.c | 8 ++++++++ drivers/usb/gadget/function/f_tcm.c | 2 +- include/target/target_core_fabric.h | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/target/target_core_fabric_configfs.c b/drivers/target/target_core_fabric_configfs.c index 166dbf4c4061..ab8f81650710 100644 --- a/drivers/target/target_core_fabric_configfs.c +++ b/drivers/target/target_core_fabric_configfs.c @@ -690,6 +690,14 @@ static void target_fabric_port_unlink( } core_dev_del_lun(se_tpg, lun); + + if (tf->tf_ops->fabric_post_unlink) { + /* + * Allow fabrics to release state that must remain valid until + * core_dev_del_lun() has drained all active LUN references. + */ + tf->tf_ops->fabric_post_unlink(se_tpg, lun); + } } static void target_fabric_port_release(struct config_item *item) diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index c727c19db25d..9e6d4f39900a 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -2027,7 +2027,7 @@ static const struct target_core_fabric_ops usbg_ops = { .fabric_enable_tpg = usbg_enable_tpg, .fabric_drop_tpg = usbg_drop_tpg, .fabric_post_link = usbg_port_link, - .fabric_pre_unlink = usbg_port_unlink, + .fabric_post_unlink = usbg_port_unlink, .fabric_init_nodeacl = usbg_init_nodeacl, .tfc_wwn_attrs = usbg_wwn_attrs, diff --git a/include/target/target_core_fabric.h b/include/target/target_core_fabric.h index e9039e73d058..390ace5bb252 100644 --- a/include/target/target_core_fabric.h +++ b/include/target/target_core_fabric.h @@ -95,6 +95,8 @@ struct target_core_fabric_ops { struct se_lun *); void (*fabric_pre_unlink)(struct se_portal_group *, struct se_lun *); + void (*fabric_post_unlink)(struct se_portal_group *se_tpg, + struct se_lun *lun); struct se_tpg_np *(*fabric_make_np)(struct se_portal_group *, struct config_group *, const char *); void (*fabric_drop_np)(struct se_tpg_np *); From 9c855832790cd488d87de1885974f4c37cfe7358 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Wed, 5 Aug 2026 09:40:49 +0800 Subject: [PATCH 159/163] usb: dwc3: gadget: Fix use-after-free in dwc3_gadget_free_endpoints due to race condition In dwc3_gadget_init_endpoint, &dep->nostream_work is bound with dwc3_nostream_work, and dwc3_gadget_endpoint_stream_event can queue this delayed work on system_percpu_wq when a DEPEVT_STREAM_NOSTREAM event is received. If we remove the gadget, dwc3_gadget_free_endpoints makes cleanup and the memory allocated for dep with kzalloc() is released by kfree(dep), while the delayed work mentioned above may still be pending or running. The sequence of operations that may lead to a UAF bug is as follows: CPU0 CPU1 | dwc3_thread_interrupt | dwc3_endpoint_interrupt | dwc3_gadget_endpoint_stream_event | queue_delayed_work(system_percpu_wq, | &dep->nostream_work) dwc3_gadget_free_endpoints | dwc3_free_trb_pool(dep) | list_del(&dep->endpoint.ep_list) | dwc3_debugfs_remove_endpoint_dir(dep) | kfree(dep) | // dep is freed | | dwc3_nostream_work | // use dep (use-after-free) Fix it by canceling the delayed work before kfree(dep) in dwc3_gadget_free_endpoints. Fixes: dcfe437492e2 ("usb: dwc3: gadget: Reinitiate stream for all host NoStream behavior") Assisted-by: Codex:deepseek-v4-flash Acked-by: Thinh Nguyen Cc: stable@vger.kernel.org Signed-off-by: Pei Xiao Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/331d1d5133496d2b4184e05f8848adb06930a138.1785893865.git.xiaopei01@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index fa0f16ffafef..fa944856f956 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3508,6 +3508,7 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) } dwc3_debugfs_remove_endpoint_dir(dep); + cancel_delayed_work_sync(&dep->nostream_work); kfree(dep); } } From 858965947081d10d41d9a1010a540d3d5eea958b Mon Sep 17 00:00:00 2001 From: Sonali Pradhan Date: Mon, 10 Aug 2026 07:12:37 +0000 Subject: [PATCH 160/163] usb: gadget: u_audio: Fix use-after-free on sound card disconnect g_audio_cleanup() invokes snd_card_free_when_closed() to initiate sound card teardown and immediately frees the underlying struct snd_uac_chip context. However, snd_card_free_when_closed() returns asynchronously while ALSA control elements (kctls) remain open in userspace. When userspace control applications access or close these open file descriptors, kctl callbacks attempt to dereference kctl->private_data pointing to &uac->c_prm or &uac->p_prm within the freed uac structure, resulting in a use-after-free (UAF) memory corruption. Fix this issue by deferring the destruction of struct snd_uac_chip until all references to the ALSA sound card are released. Register a custom card->private_free callback (u_audio_card_free) during g_audio_setup() that frees uac and its associated playback/capture request and ring buffers only when the sound card reference count drops to zero. Fixes: 6c67ed9ad9b8 ("usb: gadget: u_audio: don't let userspace block driver unbind") Cc: stable@vger.kernel.org Signed-off-by: Sonali Pradhan Link: https://patch.msgid.link/20260810071237.2207680-1-sonalipradhan@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index ca26bf9c8040..f0ee83abcebc 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -1183,6 +1183,20 @@ static struct snd_kcontrol_new u_audio_controls[] = { }, }; +static void u_audio_card_free(struct snd_card *card) +{ + struct snd_uac_chip *uac = card->private_data; + + if (!uac) + return; + + kfree(uac->p_prm.reqs); + kfree(uac->c_prm.reqs); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); +} + int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, const char *card_name) { @@ -1262,6 +1276,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, goto fail; uac->card = card; + card->private_data = uac; + card->private_free = u_audio_card_free; /* * Create first PCM device @@ -1430,6 +1446,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_fail: snd_card_free(card); + return err; + fail: kfree(uac->p_prm.reqs); kfree(uac->c_prm.reqs); @@ -1455,12 +1473,6 @@ void g_audio_cleanup(struct g_audio *g_audio) card = uac->card; if (card) snd_card_free_when_closed(card); - - kfree(uac->p_prm.reqs); - kfree(uac->c_prm.reqs); - kfree(uac->p_prm.rbuf); - kfree(uac->c_prm.rbuf); - kfree(uac); } EXPORT_SYMBOL_GPL(g_audio_cleanup); From 0dd68b5d01d022fc9c5e71c82a82b0a94d3d0671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Pe=C3=B1aranda?= Date: Mon, 10 Aug 2026 14:12:09 +0200 Subject: [PATCH 161/163] usb: usbfs: fix use-after-free of usb_device in usbdev_release() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit usbdev_release() drops its reference to the struct usb_device before draining the list of completed async URBs, but that drain path reads back through the same object: free_async() calls dec_usb_memory_use_count() for any URB whose buffer came from the usbfs mmap() region, and its first statement is bus_to_hcd(ps->dev->bus). After a disconnect the usbfs reference can be the last one, in which case usb_put_dev() frees the device and the subsequent loop reads offset 80 of freed memory and uses the result as a struct usb_hcd *, which hcd_buffer_free_pages() then dereferences. This is reachable by an unprivileged process that has read/write access to a /dev/bus/usb node: mmap() the fd, submit one URB with a buffer inside the mapping, wait for the device to be unplugged, then munmap() and close(). It reproduces on every attempt rather than being a race, because a live MAP_SHARED vma holds a reference on the struct file, so usbdev_release() cannot run until the last vma is gone and the freeing branch of dec_usb_memory_use_count() is always taken. BUG: KASAN: slab-use-after-free in dec_usb_memory_use_count+0x3ae/0x410 Read of size 8 at addr ffff8880122ee050 by task poc/769 CPU: 1 UID: 1000 PID: 769 Comm: poc Tainted: G B 6.12.94 #3 Call Trace: dec_usb_memory_use_count+0x3ae/0x410 free_async+0x2aa/0x4f0 usbdev_release+0x375/0x460 __fput+0x3ea/0xb50 __x64_sys_close+0x86/0x100 Allocated by task 11: usb_alloc_dev+0x55/0xd90 hub_event+0x2524/0x43d0 Freed by task 769: kfree+0x121/0x360 device_release+0xd2/0x280 usb_put_dev+0x23/0x30 usbdev_release+0x2d8/0x460 Release the device reference after the drain loop instead. Nothing between the two points requires it to have been dropped. Fixes: f7d34b445abc ("USB: Add support for usbfs zerocopy.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Miguel Peñaranda Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260810121209.795089-1-mig.penaranda07@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 8329d1c7d1b2..101cb9425480 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1113,7 +1113,6 @@ static int usbdev_release(struct inode *inode, struct file *file) if (!ps->suspend_allowed) usb_autosuspend_device(dev); usb_unlock_device(dev); - usb_put_dev(dev); put_pid(ps->disc_pid); put_cred(ps->cred); @@ -1122,6 +1121,7 @@ static int usbdev_release(struct inode *inode, struct file *file) free_async(as); as = async_getcompleted(ps); } + usb_put_dev(dev); kfree(ps); return 0; From 10ff55ff552b3bf1dadba03fcc430ea205fa2761 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Wed, 12 Aug 2026 17:46:32 +0800 Subject: [PATCH 162/163] usb: typec: hd3ss3220: fix VBUS regulator error message hd3ss3220_regulator_control() enables the VBUS regulator when @on is true and disables it when @on is false. However, its error message uses the opposite operation name, so an enable failure is reported as a disable failure and vice versa. Print the operation that was actually attempted. Reporting the opposite regulator operation on failures can mislead debugging of VBUS problems. Fixes: 27fbc19e52b9 ("usb: typec: hd3ss3220: Enable VBUS based on role state") Cc: stable@vger.kernel.org Reviewed-by: Heikki Krogerus Signed-off-by: Xu Rao Link: https://patch.msgid.link/7A42A287B2B588D0+20260812094632.348581-1-raoxu@uniontech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/hd3ss3220.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index 3e39b800e6b5..d0de5a2488f9 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -218,7 +218,7 @@ static void hd3ss3220_regulator_control(struct hd3ss3220 *hd3ss3220, bool on) if (ret) dev_err(hd3ss3220->dev, - "vbus regulator %s failed: %d\n", on ? "disable" : "enable", ret); + "vbus regulator %s failed: %d\n", on ? "enable" : "disable", ret); } static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) From bdab5605259ba5d6ff927c1a85cc83eb3ecfdacc Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Thu, 13 Aug 2026 23:13:11 +0530 Subject: [PATCH 163/163] usb: gadget: uvc: fix dangling pointers in uvc_function_bind() and uvc_function_unbind() In uvc_function_bind() error path, we use usb_ep_free_request which uses uvc->control_req but does not set it to NULL afterwards. Thus, uvc->control_req is a dangling pointer causing a UAF. Also we do not set the uvc->control_buf pointer to NULL after freeing it, which is another dangling pointer. Fix it by setting uvc->control_req to NULL after we run usb_ep_free_request() and uvc->control_buf to NULL after kfree. Do the same for uvc_function_unbind(). Reported-by: syzbot+de553c19cb054f174a35@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=de553c19cb054f174a35 Fixes: 0f9df9393855 ("usb: gadget: uvc: fix error path in uvc_function_bind()") Fixes: 6d11ed76c45d ("usb: gadget: f_uvc: convert f_uvc to new function interface") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260813174311.130823-1-jeffinphilip14@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 73dc7e42875f..d1bf3ea75197 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -889,9 +889,12 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) v4l2_error: v4l2_device_unregister(&uvc->v4l2_dev); error: - if (uvc->control_req) + if (uvc->control_req) { usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + uvc->control_req = NULL; + } kfree(uvc->control_buf); + uvc->control_buf = NULL; usb_free_all_descriptors(f); return ret; @@ -1075,7 +1078,9 @@ static void uvc_function_unbind(struct usb_configuration *c, uvc->vdev_release_done = NULL; usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + uvc->control_req = NULL; kfree(uvc->control_buf); + uvc->control_buf = NULL; usb_free_all_descriptors(f); }