From ae35035da9dace0557ae61f4d4b8e7b720735954 Mon Sep 17 00:00:00 2001 From: Fan Ye Date: Wed, 12 Aug 2026 11:42:09 +0000 Subject: [PATCH 001/158] thunderbolt: Stop waiting on a path pending bit that never clears __tb_path_deactivate_hop() waits up to 500 ms for a hop's pending bit to read back clear. On an ASMedia ASM4242 host router the host interface adapter latches it once enough frames have gone through the DMA ring and never clears it again: the teardown finds it already set, seconds after the last frame and with the path still up. USB4 v2 table 8-23 has the field read only and zero unless packets belonging to the path are waiting to be dequeued, so the wait is right and this adapter is not. Make the wait a per-adapter length and quirk those routers to zero, which leaves the loop reading the bit exactly once. A hop that does drain still says so on that read, and one that does not answers -ETIMEDOUT without burning the timeout: bringing the interface down takes 6 ms where it took 506. Assisted-by: Claude:claude-opus-5 Signed-off-by: Fan Ye Signed-off-by: Mika Westerberg --- drivers/thunderbolt/path.c | 2 +- drivers/thunderbolt/quirks.c | 18 ++++++++++++++++++ drivers/thunderbolt/switch.c | 4 ++++ drivers/thunderbolt/tb.h | 3 +++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index b2c322e76b8a..05249eed3f64 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -398,7 +398,7 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, return ret; /* Wait until it is drained */ - timeout = ktime_add_ms(ktime_get(), 500); + timeout = ktime_add_ms(ktime_get(), port->pp_timeout_msec); do { ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2); if (ret) diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 9f7914ac2f48..f41c5b7ef114 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -52,6 +52,19 @@ static void quirk_block_rpm_in_redrive(struct tb_switch *sw) tb_sw_dbg(sw, "preventing runtime PM in DP redrive mode\n"); } +static void quirk_stuck_pending(struct tb_switch *sw) +{ + struct tb_port *port; + + tb_switch_for_each_port(sw, port) { + if (!tb_port_is_nhi(port)) + continue; + + port->pp_timeout_msec = 0; + tb_port_dbg(port, "pending bit does not clear, reading it once\n"); + } +} + struct tb_quirk { u16 hw_vendor_id; u16 hw_device_id; @@ -114,6 +127,11 @@ static const struct tb_quirk tb_quirks[] = { { 0x0438, 0x0209, 0x0000, 0x0000, quirk_clx_disable }, { 0x0438, 0x020a, 0x0000, 0x0000, quirk_clx_disable }, { 0x0438, 0x020b, 0x0000, 0x0000, quirk_clx_disable }, + /* + * ASMedia ASM4242 never clears the Pending Packets bit of its host + * interface adapter. + */ + { 0x174c, 0x2428, 0x0000, 0x0000, quirk_stuck_pending }, }; /** diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 404c0693df50..4b8bcba7bd2f 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -19,6 +19,9 @@ #include "tb.h" +/* How long a hop is given to drain when a path is deactivated */ +#define TB_PORT_PENDING_TIMEOUT 500 /* ms */ + /* Switch NVM support */ struct nvm_auth_status { @@ -712,6 +715,7 @@ static int tb_init_port(struct tb_port *port) int cap; INIT_LIST_HEAD(&port->list); + port->pp_timeout_msec = TB_PORT_PENDING_TIMEOUT; /* Control adapter does not have configuration space */ if (!port->port) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 4373336d9425..c112954ce3fd 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -273,6 +273,8 @@ struct tb_bandwidth_group { * @max_bw: Maximum possible bandwidth through this adapter if set to * non-zero. * @redrive: For DP IN, if true the adapter is in redrive mode. + * @pp_timeout_msec: How long a hop of this adapter is given to drain when a + * path is deactivated. %0 means a single read. * * In USB4 terminology this structure represents an adapter (protocol or * lane adapter). @@ -302,6 +304,7 @@ struct tb_port { struct list_head group_list; unsigned int max_bw; bool redrive; + unsigned int pp_timeout_msec; }; /** From b946f490be576691c4c47998cf1d4e41d637fbfb Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 13 Aug 2026 23:23:57 +0800 Subject: [PATCH 002/158] thunderbolt: Require complete DROM entry headers tb_drom_parse_entries() checks for one byte remaining before reading a DROM entry header, but the header occupies two bytes. It also accepts a declared length of one byte. A one-byte tail consequently makes the parser read entry->len outside the DROM. A one-byte generic string entry reaches the subtype parser and underflows its payload-length calculation. Require a complete entry header before reading it and require the declared entry length to cover that header. Use subtraction-based bounds checks so the firmware-provided length cannot overflow the DROM extent calculation. While at it, use the actual const type instead of void * to make it clear we only read these. Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Signed-off-by: Mika Westerberg --- drivers/thunderbolt/eeprom.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 2a13fa6888ba..2acfe83928b6 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -324,7 +324,7 @@ int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid) } static int tb_drom_parse_entry_generic(struct tb_switch *sw, - struct tb_drom_entry_header *header) + const struct tb_drom_entry_header *header) { const struct tb_drom_entry_generic *entry = (const struct tb_drom_entry_generic *)header; @@ -360,7 +360,7 @@ static int tb_drom_parse_entry_generic(struct tb_switch *sw, } static int tb_drom_parse_entry_port(struct tb_switch *sw, - struct tb_drom_entry_header *header) + const struct tb_drom_entry_header *header) { struct tb_port *port; int res; @@ -386,11 +386,13 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw, type &= 0xffffff; if (type == TB_TYPE_PORT) { - struct tb_drom_entry_port *entry = (void *) header; + const struct tb_drom_entry_port *entry = + (const struct tb_drom_entry_port *)header; + if (header->len != sizeof(*entry)) { tb_sw_warn(sw, "port entry has size %#x (expected %#zx)\n", - header->len, sizeof(struct tb_drom_entry_port)); + header->len, sizeof(*entry)); return -EIO; } port->link_nr = entry->link_nr; @@ -421,9 +423,16 @@ static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size) int res; while (pos < drom_size) { - struct tb_drom_entry_header *entry = (void *) (sw->drom + pos); - if (pos + 1 == drom_size || pos + entry->len > drom_size - || !entry->len) { + const struct tb_drom_entry_header *entry; + + if (drom_size - pos < sizeof(*entry)) { + tb_sw_warn(sw, "DROM buffer overrun\n"); + return -EIO; + } + + entry = (const struct tb_drom_entry_header *)(sw->drom + pos); + if (entry->len < sizeof(*entry) || + entry->len > drom_size - pos) { tb_sw_warn(sw, "DROM buffer overrun\n"); return -EIO; } From 48e989e33b715611438ce4b8d6ff712d4becd84f Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 25 Aug 2026 11:47:59 +0800 Subject: [PATCH 003/158] thunderbolt: dma_test: Tear down DMA paths before stopping the rings dma_test_stop_rings() stops the RX and TX rings before calling tb_xdomain_disable_paths(). tb_ring_stop() zeroes the ring's descriptor base and the cancellation callbacks unmap and free the frame buffers, so by the time __tb_path_deactivate_hop() polls the hop's 'pending' bit any data still in flight has nowhere to drain to; on ASMedia ASM4242-class host routers the bit never clears and every teardown burns the full 500 ms timeout. This is the same defect as the one in tbnet_tear_down(), fixed as CVE-2026-74691 ("net: thunderbolt: Tear down DMA paths before stopping the rings"). Move the tb_xdomain_disable_paths() call before the tb_ring_stop() calls in dma_test_stop_rings(), so in-flight data drains while the rings are still active, and only then stop the rings and free the frame buffers. Signed-off-by: Haotian Zhang Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_test.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index 519c67678b08..bcecb0edcb81 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -215,11 +215,6 @@ static void dma_test_stop_rings(struct dma_test *dt) { int ret; - if (dt->rx_ring) - tb_ring_stop(dt->rx_ring); - if (dt->tx_ring) - tb_ring_stop(dt->tx_ring); - ret = tb_xdomain_disable_paths(dt->xd, dt->tx_hopid, dt->tx_ring ? dt->tx_ring->hop : -1, dt->rx_hopid, @@ -227,6 +222,11 @@ static void dma_test_stop_rings(struct dma_test *dt) if (ret) dev_warn(&dt->svc->dev, "failed to disable DMA paths\n"); + if (dt->rx_ring) + tb_ring_stop(dt->rx_ring); + if (dt->tx_ring) + tb_ring_stop(dt->tx_ring); + dma_test_free_rings(dt); } From 4894fd8d4250d40a8d43fe1deb4ac6525a7a0baa Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 26 Aug 2026 14:16:35 +0200 Subject: [PATCH 004/158] dt-bindings: usb: gpio-sbu-mux: Add compatible for FUSB340 Add a compatible for the FUSB340 GPIO-based 2:1 SuperSpeed switch with enable-gpios and select-gpios controls. The switch can be used in Type-C applications where a reversible cable requires a switch. Suggested-by: Marek Vasut Acked-by: Conor Dooley Signed-off-by: Fabrice Gasnier Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260826-usb-fusb340-v1-v3-1-11927682504d@foss.st.com --- Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml b/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml index 793662f6f3bf..c0e60848d8ab 100644 --- a/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml +++ b/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml @@ -22,6 +22,7 @@ properties: - nxp,cbdtu02043 - onnn,fsusb42 - onnn,fsusb43l10x + - onnn,fusb340 - pericom,pi3usb102 - ti,tmuxhs4212 - const: gpio-sbu-mux From 40a050c1bca6ddad5546feb88950b03b87dc8fc6 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 26 Aug 2026 14:16:36 +0200 Subject: [PATCH 005/158] usb: typec: mux: gpio-sbu: enable when only used for orientation When used as orientation-switch only (no mode-switch, e.g. no altmode), the optional enable gpio remains disabled. Enable it from the switch_set() routine, in this case, when the orientation is set and the enable-gpios property has been provided. Move enable GPIO configuration after the swapped configuration has been done, to avoid possible glitches in case both signals are changed. Signed-off-by: Fabrice Gasnier Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260826-usb-fusb340-v1-v3-2-11927682504d@foss.st.com --- drivers/usb/typec/mux/gpio-sbu-mux.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/mux/gpio-sbu-mux.c b/drivers/usb/typec/mux/gpio-sbu-mux.c index 1834f1a2dd9d..b1aa976aca24 100644 --- a/drivers/usb/typec/mux/gpio-sbu-mux.c +++ b/drivers/usb/typec/mux/gpio-sbu-mux.c @@ -17,6 +17,7 @@ struct gpio_sbu_mux { struct typec_switch_dev *sw; struct typec_mux_dev *mux; + bool mode_switch; struct mutex lock; /* protect enabled and swapped */ bool enabled; @@ -40,19 +41,24 @@ static int gpio_sbu_switch_set(struct typec_switch_dev *sw, enabled = false; break; case TYPEC_ORIENTATION_NORMAL: + if (!sbu_mux->mode_switch) + enabled = true; swapped = false; break; case TYPEC_ORIENTATION_REVERSE: + if (!sbu_mux->mode_switch) + enabled = true; swapped = true; break; } - if (enabled != sbu_mux->enabled) - gpiod_set_value_cansleep(sbu_mux->enable_gpio, enabled); - if (swapped != sbu_mux->swapped) gpiod_set_value_cansleep(sbu_mux->select_gpio, swapped); + /* If both enabled and swapped transition, set enable GPIO after to avoid glitches */ + if (enabled != sbu_mux->enabled) + gpiod_set_value_cansleep(sbu_mux->enable_gpio, enabled); + sbu_mux->enabled = enabled; sbu_mux->swapped = swapped; @@ -120,6 +126,7 @@ static int gpio_sbu_mux_probe(struct platform_device *pdev) sw_desc.fwnode = dev_fwnode(dev); sw_desc.set = gpio_sbu_switch_set; + sbu_mux->mode_switch = device_property_read_bool(dev, "mode-switch"); sbu_mux->sw = typec_switch_register(dev, &sw_desc); if (IS_ERR(sbu_mux->sw)) return dev_err_probe(dev, PTR_ERR(sbu_mux->sw), From be7b1c68cd3e4fc0f5a4e1b85696de1052f21f2f Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Thu, 23 Jul 2026 16:24:56 +0530 Subject: [PATCH 006/158] usb: dwc3: qcom: Add support to skip phy management by USB core DWC3 driver does manage phys itself sufficiently for Qualcomm platforms. If xhci-skip-phy-init is not set, the HCD core does a phy_init and phy_ exit is done only when roothub is being destroyed. Due to this, during system suspend in host mode, although phy_exit is done by DWC3 core, the init_count on phy is never down to zero since HCD core also did an init. consequently causing phy's exit routine to not be called. Hence, add support to skip phy management by USB core. Some Exynos platforms still do rely on USB core for phy_calibrate calls, hence disable USB core management for Qualcomm platforms only. Signed-off-by: Krishna Kurapati Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260723-dwc3-skip-init-quirk-v1-1-97682bb44ebd@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index ac68b4218b56..ba26e61df915 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "core.h" #include "glue.h" @@ -419,6 +420,16 @@ static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) return IRQ_HANDLED; } +static int dwc3_qcom_set_swnode(struct device *dev) +{ + const struct property_entry props[] = { + PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"), + {} + }; + + return device_create_managed_software_node(dev, props, NULL); +} + static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) { /* Configure dwc3 to use UTMI clock as PIPE clock not present */ @@ -683,6 +694,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); + ret = dwc3_qcom_set_swnode(dev); + if (ret) + goto clk_disable; + qcom->mode = usb_get_dr_mode(dev); if (qcom->mode == USB_DR_MODE_HOST) { From 5208e5c6819a61734a8c439cf964c9991e86fba0 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Tue, 4 Aug 2026 16:49:43 +0300 Subject: [PATCH 007/158] usb: dwc3: simplify dwc3_lsp_write() Simplify 'dwc3_lsp_write()' by using the convenient 'kstrtou32_from_user()'. Signed-off-by: Dmitry Antipov Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260804134943.651893-1-dmantipov@yandex.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/debugfs.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 65d909e4b785..01ebd73198e0 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -367,14 +367,10 @@ static ssize_t dwc3_lsp_write(struct file *file, const char __user *ubuf, struct seq_file *s = file->private_data; struct dwc3 *dwc = s->private; unsigned long flags; - char buf[32] = { 0 }; u32 sel; int ret; - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) - return -EFAULT; - - ret = kstrtouint(buf, 0, &sel); + ret = kstrtou32_from_user(ubuf, count, 0, &sel); if (ret) return ret; From 4f87adc46ec384434304abc6b44ab628dc92896e Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 10 Aug 2026 14:42:31 +0300 Subject: [PATCH 008/158] dt-bindings: usb: qcom,snps-dwc3: document the MSM8974 controller Add the qcom,msm8974-dwc3 compatible. The controller uses the same four clocks as IPQ8074. Unlike other controllers, it has only 3 IRQs routed: core, power event and HS PHY. Assisted-by: Claude:claude-fable-5 Signed-off-by: Dmitry Baryshkov Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260810-msm8974-usb3-v1-1-1a47e1185524@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/qcom,snps-dwc3.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index ea60f7220afe..9f91c8711e02 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -40,6 +40,7 @@ properties: - qcom,maili-dwc3 - qcom,milos-dwc3 - qcom,msm8953-dwc3 + - qcom,msm8974-dwc3 - qcom,msm8994-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 @@ -268,6 +269,7 @@ allOf: contains: enum: - qcom,ipq8074-dwc3 + - qcom,msm8974-dwc3 - qcom,qdu1000-dwc3 then: properties: @@ -453,6 +455,23 @@ allOf: - const: qusb2_phy - const: ss_phy_irq + - if: + properties: + compatible: + contains: + enum: + - qcom,msm8974-dwc3 + then: + properties: + interrupts: + minItems: 3 + maxItems: 3 + interrupt-names: + items: + - const: dwc_usb3 + - const: pwr_event + - const: hs_phy_irq + - if: properties: compatible: From 335f9c105c4daf73050fea19ed74d7b9974ed869 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 28 Aug 2026 17:21:15 +0200 Subject: [PATCH 009/158] dt-bindings: usb: generic-ehci: Document ST STM32MP1 SoC compatibles Document SoC compatibles for ST STM32MP13xx and STM32MP15xx series of SoC, so they can be used as more accurate compatible in SoC DTSI. The SoC DTSI currently use plain "generic-ehci" which is not future proof. Signed-off-by: Marek Vasut Acked-by: Conor Dooley Link: https://patch.msgid.link/20260828152130.176171-1-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-ehci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index ae9fb70d0212..5d9dfea8e216 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -55,6 +55,8 @@ properties: - rockchip,rk3588-ehci - snps,hsdk-v1.0-ehci - socionext,uniphier-ehci + - st,stm32mp13-ehci + - st,stm32mp15-ehci - const: generic-ehci - items: - enum: From 5174bfcdf7759b64cb39d30411f3709cc0134797 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 28 Aug 2026 17:21:16 +0200 Subject: [PATCH 010/158] ARM: dts: stm32: Use STM32MP13xx compatible string for EHCI USB controller Use accurate STM32MP13xx compatible string for EHCI USB controller in addition to "generic-ehci" fallback compatible string. This is future proof, and drivers can match on the accurate compatible string in case they need to discern this controller from the generic-ehci one. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260828152130.176171-2-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/st/stm32mp131.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/st/stm32mp131.dtsi b/arch/arm/boot/dts/st/stm32mp131.dtsi index 83ae59b73dd0..bf0406f9395b 100644 --- a/arch/arm/boot/dts/st/stm32mp131.dtsi +++ b/arch/arm/boot/dts/st/stm32mp131.dtsi @@ -1113,7 +1113,7 @@ usbh_ohci: usb@5800c000 { }; usbh_ehci: usb@5800d000 { - compatible = "generic-ehci"; + compatible = "st,stm32mp13-ehci", "generic-ehci"; reg = <0x5800d000 0x1000>; clocks = <&usbphyc>, <&rcc USBH>; resets = <&rcc USBH_R>; From 4d450e2a367bc744d4c0782baf19388b90b93398 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 28 Aug 2026 17:21:17 +0200 Subject: [PATCH 011/158] ARM: dts: stm32: Use STM32MP15xx compatible string for EHCI USB controller Use accurate STM32MP15xx compatible string for EHCI USB controller in addition to "generic-ehci" fallback compatible string. This is future proof, and drivers can match on the accurate compatible string in case they need to discern this controller from the generic-ehci one. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260828152130.176171-3-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/st/stm32mp151.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/st/stm32mp151.dtsi b/arch/arm/boot/dts/st/stm32mp151.dtsi index 84f68e8563d8..f44dd79109fd 100644 --- a/arch/arm/boot/dts/st/stm32mp151.dtsi +++ b/arch/arm/boot/dts/st/stm32mp151.dtsi @@ -515,7 +515,7 @@ usbh_ohci: usb@5800c000 { }; usbh_ehci: usb@5800d000 { - compatible = "generic-ehci"; + compatible = "st,stm32mp15-ehci", "generic-ehci"; reg = <0x5800d000 0x1000>; clocks = <&usbphyc>, <&rcc USBH>; resets = <&rcc USBH_R>; From 851a5a4699851ee5f6902810c892ecedb63a767a Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 28 Aug 2026 17:01:26 +0200 Subject: [PATCH 012/158] dt-bindings: usb: generic-ohci: Document ST STM32MP1 SoC compatibles Document SoC compatibles for ST STM32MP13xx and STM32MP15xx series of SoC, so they can be used as more accurate compatible in SoC DTSI. The SoC DTSI currently use plain "generic-ohci" which is not future proof. Signed-off-by: Marek Vasut Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260828150202.173109-1-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-ohci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index 322808aaa283..474a84aa32d0 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -49,6 +49,8 @@ properties: - ingenic,jz4740-ohci - rockchip,rk3588-ohci - snps,hsdk-v1.0-ohci + - st,stm32mp13-ohci + - st,stm32mp15-ohci - const: generic-ohci - enum: - generic-ohci From 202b03757cca3d02843c62298ed5b442168cf682 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 28 Aug 2026 17:01:27 +0200 Subject: [PATCH 013/158] ARM: dts: stm32: Use STM32MP13xx compatible string for OHCI USB controller Use accurate STM32MP13xx compatible string for OHCI USB controller in addition to "generic-ohci" fallback compatible string. This is future proof, and drivers can match on the accurate compatible string in case they need to discern this controller from the generic-ohci one. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260828150202.173109-2-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/st/stm32mp131.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/st/stm32mp131.dtsi b/arch/arm/boot/dts/st/stm32mp131.dtsi index bf0406f9395b..5c98e4ad0fa0 100644 --- a/arch/arm/boot/dts/st/stm32mp131.dtsi +++ b/arch/arm/boot/dts/st/stm32mp131.dtsi @@ -1104,7 +1104,7 @@ crc1: crc@58009000 { }; usbh_ohci: usb@5800c000 { - compatible = "generic-ohci"; + compatible = "st,stm32mp13-ohci", "generic-ohci"; reg = <0x5800c000 0x1000>; clocks = <&usbphyc>, <&rcc USBH>; resets = <&rcc USBH_R>; From 4b211420c0416d53c924b51967cf655a33e5a91e Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 28 Aug 2026 17:01:28 +0200 Subject: [PATCH 014/158] ARM: dts: stm32: Use STM32MP15xx compatible string for OHCI USB controller Use accurate STM32MP15xx compatible string for OHCI USB controller in addition to "generic-ohci" fallback compatible string. This is future proof, and drivers can match on the accurate compatible string in case they need to discern this controller from the generic-ohci one. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260828150202.173109-3-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/st/stm32mp151.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/st/stm32mp151.dtsi b/arch/arm/boot/dts/st/stm32mp151.dtsi index f44dd79109fd..a039bae55328 100644 --- a/arch/arm/boot/dts/st/stm32mp151.dtsi +++ b/arch/arm/boot/dts/st/stm32mp151.dtsi @@ -504,7 +504,7 @@ crc1: crc@58009000 { }; usbh_ohci: usb@5800c000 { - compatible = "generic-ohci"; + compatible = "st,stm32mp15-ohci", "generic-ohci"; reg = <0x5800c000 0x1000>; clocks = <&usbphyc>, <&rcc USBH>; resets = <&rcc USBH_R>; From 930be0e71f6106a3ef170dd79b153936a2d8083c Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 1 Sep 2026 01:17:32 +0200 Subject: [PATCH 015/158] dt-bindings: usb: generic-ohci: Document ST STM32MP2 SoC compatibles Document SoC compatibles for ST STM32MP23xx and STM32MP25xx series of SoC, so they can be used as more accurate compatible in SoC DTSI. The SoC uses access controllers, therefore document access-controllers DT property. For details, see access-controllers/access-controllers.yaml. Signed-off-by: Marek Vasut Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260831231814.137120-2-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/generic-ohci.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index 474a84aa32d0..7a8c3c231815 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -51,6 +51,8 @@ properties: - snps,hsdk-v1.0-ohci - st,stm32mp13-ohci - st,stm32mp15-ohci + - st,stm32mp23-ohci + - st,stm32mp25-ohci - const: generic-ohci - enum: - generic-ohci @@ -67,6 +69,9 @@ properties: reg: maxItems: 1 + access-controllers: + maxItems: 1 + dma-coherent: true interrupts: @@ -215,6 +220,23 @@ allOf: clocks: minItems: 1 maxItems: 3 + - if: + properties: + compatible: + contains: + enum: + - st,stm32mp23-ohci + - st,stm32mp25-ohci + then: + properties: + clocks: + items: + - description: PHY clock + - description: Bus clock + + required: + - access-controllers + - clocks unevaluatedProperties: false From d27192623fda5b76e707ec02fc6ac8214494e30b Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 1 Sep 2026 01:17:33 +0200 Subject: [PATCH 016/158] dt-bindings: usb: generic-ehci: Document ST STM32MP2 SoC compatibles Document SoC compatibles for ST STM32MP23xx and STM32MP25xx series of SoC, so they can be used as more accurate compatible in SoC DTSI. The SoC uses access controllers, therefore document access-controllers DT property. For details, see access-controllers/access-controllers.yaml. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260831231814.137120-3-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/generic-ehci.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 5d9dfea8e216..908b02d9175e 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -57,6 +57,8 @@ properties: - socionext,uniphier-ehci - st,stm32mp13-ehci - st,stm32mp15-ehci + - st,stm32mp23-ehci + - st,stm32mp25-ehci - const: generic-ehci - items: - enum: @@ -82,6 +84,9 @@ properties: minItems: 1 maxItems: 2 + access-controllers: + maxItems: 1 + dma-coherent: true interrupts: @@ -195,6 +200,23 @@ allOf: else: properties: clock-names: false + - if: + properties: + compatible: + contains: + enum: + - st,stm32mp23-ehci + - st,stm32mp25-ehci + then: + properties: + clocks: + items: + - description: PHY clock + - description: Bus clock + + required: + - access-controllers + - clocks unevaluatedProperties: false From e6c3dafe94138999f15a814d5d1978fc904a0619 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 1 Sep 2026 01:17:34 +0200 Subject: [PATCH 017/158] arm64: dts: st: Add USB nodes on stm32mp231 Add USB controller and PHY nodes to stm32mp231 DT. This includes two USB 2.0 FEMTO PHYs, USB 2.0 host controller and DWC3 DRD controller nodes. Co-developed-by: Alexandre Torgue Signed-off-by: Alexandre Torgue Co-developed-by: Francois Choleau Signed-off-by: Francois Choleau Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260831231814.137120-4-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/st/stm32mp231.dtsi | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/arch/arm64/boot/dts/st/stm32mp231.dtsi b/arch/arm64/boot/dts/st/stm32mp231.dtsi index 9e1d240888ff..a393bcfd768a 100644 --- a/arch/arm64/boot/dts/st/stm32mp231.dtsi +++ b/arch/arm64/boot/dts/st/stm32mp231.dtsi @@ -778,6 +778,48 @@ stmmac_axi_config_1: stmmac-axi-config { snps,wr_osr_lmt = <0x7>; }; }; + + usbh_ohci: usb@482e0000 { + compatible = "st,stm32mp23-ohci", "generic-ohci"; + reg = <0x482e0000 0x1000>; + access-controllers = <&rifsc 63>; + clocks = <&usb2_phy1 1>, <&rcc CK_BUS_USB2OHCI>; + interrupts = ; + phys = <&usb2_phy1>; + phy-names = "usb"; + resets = <&rcc USBH_R>; + status = "disabled"; + }; + + usbh_ehci: usb@482f0000 { + compatible = "st,stm32mp23-ehci", "generic-ehci"; + reg = <0x482f0000 0x1000>; + access-controllers = <&rifsc 63>; + clocks = <&usb2_phy1 0>, <&rcc CK_BUS_USB2EHCI>; + companion = <&usbh_ohci>; + interrupts = ; + phys = <&usb2_phy1>; + phy-names = "usb"; + resets = <&rcc USBH_R>; + status = "disabled"; + }; + + usb3dr: usb@48300000 { + compatible = "st,stm32mp25-dwc3"; + reg = <0x48300000 0x100000>; + access-controllers = <&rifsc 66>; + clocks = <&rcc CK_KER_USB2PHY2>, + <&rcc CK_BUS_USB3DR>, + <&rcc CK_KER_USB2PHY2>; + clock-names = "ref", "bus_early", "suspend"; + interrupts = ; + maximum-speed = "high-speed"; + phys = <&usb2_phy2>; + phy-names = "usb2-phy"; + resets = <&rcc USB3DR_R>; + st,syscfg = <&syscfg 0x4800>; + status = "disabled"; + }; }; bsec: efuse@44000000 { @@ -981,6 +1023,28 @@ syscfg: syscon@44230000 { compatible = "st,stm32mp23-syscfg", "syscon"; reg = <0x44230000 0x10000>; #clock-cells = <0>; + #address-cells = <1>; + #size-cells = <1>; + + usb2_phy1: phy@2400 { + compatible = "st,stm32mp25-usb2phy1"; + reg = <0x2400 0x24>; + #phy-cells = <0>; + #clock-cells = <1>; + clocks = <&rcc CK_KER_USB2PHY1>; + resets = <&rcc USB2PHY1_R>; + status = "disabled"; + }; + + usb2_phy2: phy@2800 { + compatible = "st,stm32mp25-usb2phy2"; + reg = <0x2800 0x10>; + #phy-cells = <0>; + #clock-cells = <1>; + clocks = <&rcc CK_KER_USB2PHY2EN>; + resets = <&rcc USB2PHY2_R>; + status = "disabled"; + }; }; pinctrl: pinctrl@44240000 { From 20abb757631caed9b76e58b19cb37ecd999586c3 Mon Sep 17 00:00:00 2001 From: Pankaj Dev Date: Tue, 1 Sep 2026 01:17:35 +0200 Subject: [PATCH 018/158] arm64: dts: st: Add USB nodes on stm32mp251 Add USB controller and PHY nodes to stm32mp251 DT. This includes two USB 2.0 FEMTO PHYs, USB 2.0 host controller and DWC3 DRD controller nodes. Signed-off-by: Pankaj Dev Co-developed-by: Marek Vasut Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260831231814.137120-5-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/st/stm32mp251.dtsi | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/arch/arm64/boot/dts/st/stm32mp251.dtsi b/arch/arm64/boot/dts/st/stm32mp251.dtsi index 9c63fdb5a885..0d814ae2476a 100644 --- a/arch/arm64/boot/dts/st/stm32mp251.dtsi +++ b/arch/arm64/boot/dts/st/stm32mp251.dtsi @@ -1716,6 +1716,47 @@ stmmac_axi_config_1: stmmac-axi-config { }; }; + usbh_ohci: usb@482e0000 { + compatible = "st,stm32mp25-ohci", "generic-ohci"; + reg = <0x482e0000 0x1000>; + access-controllers = <&rifsc 63>; + clocks = <&usb2_phy1 1>, <&rcc CK_BUS_USB2OHCI>; + interrupts = ; + phys = <&usb2_phy1>; + phy-names = "usb"; + resets = <&rcc USBH_R>; + status = "disabled"; + }; + + usbh_ehci: usb@482f0000 { + compatible = "st,stm32mp25-ehci", "generic-ehci"; + reg = <0x482f0000 0x1000>; + access-controllers = <&rifsc 63>; + clocks = <&usb2_phy1 0>, <&rcc CK_BUS_USB2EHCI>; + companion = <&usbh_ohci>; + interrupts = ; + phys = <&usb2_phy1>; + phy-names = "usb"; + resets = <&rcc USBH_R>; + status = "disabled"; + }; + + usb3dr: usb@48300000 { + compatible = "st,stm32mp25-dwc3"; + reg = <0x48300000 0x100000>; + access-controllers = <&rifsc 66>; + clocks = <&rcc CK_KER_USB2PHY2>, + <&rcc CK_BUS_USB3DR>, + <&rcc CK_KER_USB2PHY2>; + clock-names = "ref", "bus_early", "suspend"; + interrupts = ; + phys = <&usb2_phy2>, <&combophy PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + resets = <&rcc USB3DR_R>; + st,syscfg = <&syscfg 0x4800>; + status = "disabled"; + }; + pcie_ep: pcie-ep@48400000 { compatible = "st,stm32mp25-pcie-ep"; reg = <0x48400000 0x100000>, @@ -1975,6 +2016,28 @@ syscfg: syscon@44230000 { compatible = "st,stm32mp25-syscfg", "syscon"; reg = <0x44230000 0x10000>; #clock-cells = <0>; + #address-cells = <1>; + #size-cells = <1>; + + usb2_phy1: phy@2400 { + compatible = "st,stm32mp25-usb2phy1"; + reg = <0x2400 0x24>; + #phy-cells = <0>; + #clock-cells = <1>; + clocks = <&rcc CK_KER_USB2PHY1>; + resets = <&rcc USB2PHY1_R>; + status = "disabled"; + }; + + usb2_phy2: phy@2800 { + compatible = "st,stm32mp25-usb2phy2"; + reg = <0x2800 0x10>; + #phy-cells = <0>; + #clock-cells = <1>; + clocks = <&rcc CK_KER_USB2PHY2EN>; + resets = <&rcc USB2PHY2_R>; + status = "disabled"; + }; }; pinctrl: pinctrl@44240000 { From 63dbfd46c96a6060675672913a951c1776780131 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:35 +0800 Subject: [PATCH 019/158] usb: cdns3: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Acked-by: Peter Chen Link: https://patch.msgid.link/20260731035448.252289-2-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/core.c | 4 +--- drivers/usb/cdns3/drd.c | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 504bdf13ea80..87122fb5bdb8 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -449,10 +449,8 @@ int cdns_init(struct cdns *cdns) IRQF_SHARED, dev_name(cdns->dev), cdns); - if (ret) { - dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); + if (ret) goto role_switch_unregister; - } } ret = cdns_drd_init(cdns); diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index d2bb682e4552..42f13757d904 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -509,10 +509,8 @@ int cdns_drd_init(struct cdns *cdns) cdns_drd_thread_irq, IRQF_SHARED, dev_name(cdns->dev), cdns); - if (ret) { - dev_err(cdns->dev, "couldn't get otg_irq\n"); + if (ret) return ret; - } state = readl(&cdns->otg_regs->sts); if (OTGSTS_OTG_NRDY(state)) { From bdda54d76a6ec1d7ed1765f32b26ad356057574e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:36 +0800 Subject: [PATCH 020/158] usb: common: usb-conn-gpio: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-3-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/usb-conn-gpio.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index 421c3af38e06..f6db03d89efd 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -251,10 +251,8 @@ static int usb_conn_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, info->id_irq, NULL, usb_conn_isr, USB_CONN_IRQF, pdev->name, info); - if (ret < 0) { - dev_err(dev, "failed to request ID IRQ\n"); + if (ret < 0) goto put_role_sw; - } } if (info->vbus_gpiod) { @@ -268,10 +266,8 @@ static int usb_conn_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, usb_conn_isr, USB_CONN_IRQF, pdev->name, info); - if (ret < 0) { - dev_err(dev, "failed to request VBUS IRQ\n"); + if (ret < 0) goto put_role_sw; - } } platform_set_drvdata(pdev, info); From a5419bee83db523750beea78ca87d8a7d42bc4c8 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:37 +0800 Subject: [PATCH 021/158] usb: dwc2: gadget: Remove redundant dev_err() 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() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-4-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 102169b40419..f7afbedf9f2c 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -5053,10 +5053,8 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg) ret = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_hsotg_irq, IRQF_SHARED, dev_name(hsotg->dev), hsotg); - if (ret < 0) { - dev_err(dev, "cannot claim IRQ for gadget\n"); + if (ret < 0) return ret; - } /* hsotg->num_of_eps holds number of EPs other than ep0 */ From fc113df71893eedede26bdcca147833cd34c5c56 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:38 +0800 Subject: [PATCH 022/158] usb: dwc3: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Acked-by: Frank Li Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260731035448.252289-5-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-google.c | 4 +--- drivers/usb/dwc3/dwc3-imx.c | 2 +- drivers/usb/dwc3/dwc3-imx8mp.c | 4 +--- drivers/usb/dwc3/dwc3-keystone.c | 5 +---- drivers/usb/dwc3/dwc3-omap.c | 5 +---- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c index 60ee4cc99b28..e581d407b872 100644 --- a/drivers/usb/dwc3/dwc3-google.c +++ b/drivers/usb/dwc3/dwc3-google.c @@ -235,10 +235,8 @@ static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_d dwc3_google_resume_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, req_name, google); - if (ret < 0) { - dev_err(google->dev, "failed to request irq %s\n", req_name); + if (ret < 0) return ret; - } return irq; } diff --git a/drivers/usb/dwc3/dwc3-imx.c b/drivers/usb/dwc3/dwc3-imx.c index 973a486b544d..d967acd43d44 100644 --- a/drivers/usb/dwc3/dwc3-imx.c +++ b/drivers/usb/dwc3/dwc3-imx.c @@ -250,7 +250,7 @@ static int dwc3_imx_probe(struct platform_device *pdev) IRQF_ONESHOT | IRQF_NO_AUTOEN, irq_name, dwc_imx); if (ret) - return dev_err_probe(dev, ret, "failed to request IRQ #%d\n", irq); + return ret; ret = device_add_software_node(dev, &dwc3_imx_swnode); if (ret) diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c index 1cf96540b66e..ee92a5efaa6e 100644 --- a/drivers/usb/dwc3/dwc3-imx8mp.c +++ b/drivers/usb/dwc3/dwc3-imx8mp.c @@ -273,10 +273,8 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev) err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, IRQF_ONESHOT, dev_name(dev), dwc3_imx); - if (err) { - dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); + if (err) goto put_dwc3; - } device_set_wakeup_capable(dev, true); pm_runtime_put(dev); diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c index 7ee1610162b9..8ad302abe883 100644 --- a/drivers/usb/dwc3/dwc3-keystone.c +++ b/drivers/usb/dwc3/dwc3-keystone.c @@ -144,11 +144,8 @@ static int kdwc3_probe(struct platform_device *pdev) error = devm_request_irq(dev, irq, dwc3_keystone_interrupt, IRQF_SHARED, dev_name(dev), kdwc); - if (error) { - dev_err(dev, "failed to request IRQ #%d --> %d\n", - irq, error); + if (error) goto err_irq; - } kdwc3_enable_irqs(kdwc); diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index fe74d11bb629..3bff775fbe4e 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -518,11 +518,8 @@ static int dwc3_omap_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, omap->irq, dwc3_omap_interrupt, dwc3_omap_interrupt_thread, IRQF_SHARED, "dwc3-omap", omap); - if (ret) { - dev_err(dev, "failed to request IRQ #%d --> %d\n", - omap->irq, ret); + if (ret) goto err2; - } dwc3_omap_enable_irqs(omap); return 0; From c936c57e74a1bd6870c4e57d4545ca1a989999d7 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:39 +0800 Subject: [PATCH 023/158] usb: gadget: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Herve Codina # renesas_usbf.c Link: https://patch.msgid.link/20260731035448.252289-6-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed-vhub/core.c | 4 +--- drivers/usb/gadget/udc/aspeed_udc.c | 4 +--- drivers/usb/gadget/udc/atmel_usba_udc.c | 5 +---- drivers/usb/gadget/udc/r8a66597-udc.c | 4 +--- drivers/usb/gadget/udc/renesas_usbf.c | 8 ++------ drivers/usb/gadget/udc/snps_udc_plat.c | 4 +--- drivers/usb/gadget/udc/tegra-xudc.c | 5 +---- 7 files changed, 8 insertions(+), 26 deletions(-) diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 4a8b9ff8368f..4ac990553a86 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -379,10 +379,8 @@ static int ast_vhub_probe(struct platform_device *pdev) } rc = devm_request_irq(&pdev->dev, vhub->irq, ast_vhub_irq, 0, KBUILD_MODNAME, vhub); - if (rc) { - dev_err(&pdev->dev, "Failed to request interrupt\n"); + if (rc) goto err; - } dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev); if (dma_mask_ptr) { diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 54f81e668009..08b24902c7b9 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -1568,10 +1568,8 @@ static int ast_udc_probe(struct platform_device *pdev) 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"); + if (rc) goto err_cleanup; - } rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget); if (rc) { diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 72a2f95ff018..18b4b2358d1e 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -2332,11 +2332,8 @@ static int usba_udc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, usba_udc_irq, 0, "atmel_usba_udc", udc); - if (ret) { - dev_err(&pdev->dev, "Cannot request irq %d (error %d)\n", - irq, ret); + if (ret) return ret; - } udc->irq = irq; if (udc->vbus_pin) { diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index d190e16d43fc..915786c9628c 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1894,10 +1894,8 @@ static int r8a66597_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, r8a66597_irq, IRQF_SHARED, udc_name, r8a66597); - if (ret < 0) { - dev_err(dev, "request_irq error (%d)\n", ret); + if (ret < 0) goto clean_up2; - } INIT_LIST_HEAD(&r8a66597->gadget.ep_list); r8a66597->gadget.ep0 = &r8a66597->ep[0].ep; diff --git a/drivers/usb/gadget/udc/renesas_usbf.c b/drivers/usb/gadget/udc/renesas_usbf.c index d67002ea049a..367c9e73f366 100644 --- a/drivers/usb/gadget/udc/renesas_usbf.c +++ b/drivers/usb/gadget/udc/renesas_usbf.c @@ -3335,19 +3335,15 @@ static int usbf_probe(struct platform_device *pdev) if (irq < 0) return irq; ret = devm_request_irq(dev, irq, usbf_epc_irq, 0, "usbf-epc", udc); - if (ret) { - dev_err(dev, "cannot request irq %d err %d\n", irq, ret); + if (ret) return ret; - } irq = platform_get_irq(pdev, 1); if (irq < 0) return irq; ret = devm_request_irq(dev, irq, usbf_ahb_epc_irq, 0, "usbf-ahb-epc", udc); - if (ret) { - dev_err(dev, "cannot request irq %d err %d\n", irq, ret); + if (ret) return ret; - } usbf_reg_bitset(udc, USBF_REG_AHBMCTR, USBF_SYS_WBURST_TYPE); diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c index 0ee3ed185d9c..8ce43c4f8fba 100644 --- a/drivers/usb/gadget/udc/snps_udc_plat.c +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -192,10 +192,8 @@ static int udc_plat_probe(struct platform_device *pdev) ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED, "snps-udc", udc); - if (ret < 0) { - dev_err(dev, "Request irq %d failed for UDC\n", udc->irq); + if (ret < 0) goto exit_dma; - } platform_set_drvdata(pdev, udc); udc->chiprev = UDC_BCM_REV; diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index e9d33be02866..5042c13f92be 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3803,11 +3803,8 @@ static int tegra_xudc_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, xudc->irq, tegra_xudc_irq, 0, dev_name(&pdev->dev), xudc); - if (err < 0) { - dev_err(xudc->dev, "failed to claim IRQ#%u: %d\n", xudc->irq, - err); + if (err < 0) return err; - } xudc->clks = devm_kcalloc(&pdev->dev, xudc->soc->num_clks, sizeof(*xudc->clks), GFP_KERNEL); From fd5f88c85cfbc500ab48fb5c74777bfc178f4394 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:40 +0800 Subject: [PATCH 024/158] usb: host: xhci-tegra: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-7-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 6f235d1e117e..15dfa7e1a7dd 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1909,20 +1909,16 @@ static int tegra_xusb_probe(struct platform_device *pdev) tegra_xusb_mbox_irq, tegra_xusb_mbox_thread, 0, dev_name(&pdev->dev), tegra); - if (err < 0) { - dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + if (err < 0) goto remove_usb3; - } if (tegra->padctl_irq) { err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, NULL, tegra_xusb_padctl_irq, IRQF_ONESHOT, dev_name(&pdev->dev), tegra); - if (err < 0) { - dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); + if (err < 0) goto remove_usb3; - } } err = tegra_xusb_enable_firmware_messages(tegra); From a353d0e69f4b87cf86538f86d9e0110daf979f36 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:41 +0800 Subject: [PATCH 025/158] usb: misc: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-8-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/brcmstb-usb-pinmap.c | 9 ++------- drivers/usb/misc/qcom_eud.c | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c index 1ce885e4184c..d8e9c80a9c2e 100644 --- a/drivers/usb/misc/brcmstb-usb-pinmap.c +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -299,10 +299,8 @@ static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) brcmstb_usb_pinmap_ovr_isr, IRQF_TRIGGER_RISING, pdev->name, pdata); - if (err < 0) { - dev_err(&pdev->dev, "Error requesting IRQ\n"); + if (err < 0) return err; - } } for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) { @@ -317,11 +315,8 @@ static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, pdev->name, pin); - if (err < 0) { - dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n", - pin->name); + if (err < 0) return err; - } } dev_dbg(&pdev->dev, "Driver probe succeeded\n"); diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 926419ca560f..a7b9ed4ac071 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -225,7 +225,7 @@ static int eud_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq, handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip); if (ret) - return dev_err_probe(chip->dev, ret, "failed to allocate irq\n"); + return ret; enable_irq_wake(chip->irq); From de7cde2007e2a08ca5072ee0bbe9fd74dc1162fe Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:42 +0800 Subject: [PATCH 026/158] usb: mtu3: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-9-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index a40bf5bad2d5..990e2e8e6bc2 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -973,10 +973,8 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb) ret = devm_request_threaded_irq(dev, mtu->irq, NULL, mtu3_irq, IRQF_ONESHOT, dev_name(dev), mtu); - if (ret) { - dev_err(dev, "request irq %d failed!\n", mtu->irq); + if (ret) goto irq_err; - } /* power down device IP for power saving by default */ mtu3_stop(mtu); From e2f3a3936e5f9cf6173cdd717d676159893ef84c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:43 +0800 Subject: [PATCH 027/158] usb: phy: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-10-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-ab8500-usb.c | 12 +++--------- drivers/usb/phy/phy-generic.c | 3 +-- drivers/usb/phy/phy-gpio-vbus-usb.c | 5 +---- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c index 6a98aeeeae31..e2e06e57dced 100644 --- a/drivers/usb/phy/phy-ab8500-usb.c +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -748,10 +748,8 @@ static int ab8500_usb_irq_setup(struct platform_device *pdev, ab8500_usb_link_status_irq, IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, "usb-link-status", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for link status irq\n"); + if (err < 0) return err; - } } if (ab->flags & AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ) { @@ -762,10 +760,8 @@ static int ab8500_usb_irq_setup(struct platform_device *pdev, ab8500_usb_disconnect_irq, IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, "usb-id-fall", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for ID fall irq\n"); + if (err < 0) return err; - } } if (ab->flags & AB8500_USB_FLAG_USE_VBUS_DET_IRQ) { @@ -776,10 +772,8 @@ static int ab8500_usb_irq_setup(struct platform_device *pdev, ab8500_usb_disconnect_irq, IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, "usb-vbus-fall", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); + if (err < 0) return err; - } } return 0; diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index de26b302334d..a41671eda2a1 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -282,8 +282,7 @@ static int usb_phy_generic_probe(struct platform_device *pdev) VBUS_IRQ_FLAGS, "vbus_detect", nop); if (err) - return dev_err_probe(dev, err, "can't request irq %i\n", - gpiod_to_irq(nop->gpiod_vbus)); + return err; nop->phy.otg->state = gpiod_get_value(nop->gpiod_vbus) ? OTG_STATE_B_PERIPHERAL : OTG_STATE_B_IDLE; diff --git a/drivers/usb/phy/phy-gpio-vbus-usb.c b/drivers/usb/phy/phy-gpio-vbus-usb.c index ce09e789afd8..c0135a30cabd 100644 --- a/drivers/usb/phy/phy-gpio-vbus-usb.c +++ b/drivers/usb/phy/phy-gpio-vbus-usb.c @@ -297,11 +297,8 @@ static int gpio_vbus_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, irq, gpio_vbus_irq, irqflags, "vbus_detect", pdev); - if (err) { - dev_err(&pdev->dev, "can't request irq %i, err: %d\n", - irq, err); + if (err) return err; - } INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work); From bafbedf0ea2690cfdcd5973093cfccc044ee35e3 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:44 +0800 Subject: [PATCH 028/158] usb: renesas_usbhs: Remove redundant dev_err() 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() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260731035448.252289-11-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/mod.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c index f2ea3e1412d2..dcaebcc0614d 100644 --- a/drivers/usb/renesas_usbhs/mod.c +++ b/drivers/usb/renesas_usbhs/mod.c @@ -143,10 +143,8 @@ int usbhs_mod_probe(struct usbhs_priv *priv) /* irq settings */ ret = devm_request_irq(dev, priv->irq, usbhs_interrupt, 0, dev_name(dev), priv); - if (ret) { - dev_err(dev, "irq request err\n"); + if (ret) goto mod_init_gadget_err; - } return ret; From 814b6ba12b8f8f8af0431f26971dd04b3fd16605 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 31 Jul 2026 11:54:45 +0800 Subject: [PATCH 029/158] usb: typec: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260731035448.252289-12-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 4 +--- drivers/usb/typec/hd3ss3220.c | 4 +--- drivers/usb/typec/mux/it5205.c | 2 +- drivers/usb/typec/tcpm/tcpci_maxim_core.c | 3 +-- drivers/usb/typec/tcpm/tcpci_mt6360.c | 1 - drivers/usb/typec/tcpm/tcpci_mt6370.c | 2 +- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 41df115912b9..2c41e00819f8 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1529,10 +1529,8 @@ static int anx7411_i2c_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "anx7411-intp", plat); - if (ret) { - dev_err(dev, "fail to request irq\n"); + if (ret) goto free_wq; - } if (anx7411_typec_check_connection(plat)) dev_err(dev, "check status\n"); diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index d0de5a2488f9..d14c308f108e 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -436,10 +436,8 @@ static int hd3ss3220_probe(struct i2c_client *client) IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(hd3ss3220->dev), hd3ss3220); - if (ret < 0) { - dev_err(hd3ss3220->dev, "failed to get ID irq: %d\n", ret); + if (ret < 0) goto err_put_fwnode; - } } typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; diff --git a/drivers/usb/typec/mux/it5205.c b/drivers/usb/typec/mux/it5205.c index 5e1a120b2e3b..2ab2ba6a3cbd 100644 --- a/drivers/usb/typec/mux/it5205.c +++ b/drivers/usb/typec/mux/it5205.c @@ -250,7 +250,7 @@ static int it5205_probe(struct i2c_client *client) if (ret) { typec_mux_unregister(it->mux); typec_switch_unregister(it->sw); - return dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; } } diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 998693a61839..52dbbbe53691 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -542,8 +542,7 @@ static int max_tcpci_probe(struct i2c_client *client) (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev), chip); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "IRQ initialization failed\n"); + return ret; ret = devm_device_init_wakeup(chip->dev); if (ret) diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c index 881ffacbfd77..d4010ea5183b 100644 --- a/drivers/usb/typec/tcpm/tcpci_mt6360.c +++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c @@ -167,7 +167,6 @@ static int mt6360_tcpc_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(mti->dev, mti->irq, NULL, mt6360_irq, IRQF_ONESHOT, dev_name(&pdev->dev), mti); if (ret) { - dev_err(mti->dev, "Failed to register irq\n"); tcpci_unregister_port(mti->tcpci); return ret; } diff --git a/drivers/usb/typec/tcpm/tcpci_mt6370.c b/drivers/usb/typec/tcpm/tcpci_mt6370.c index 7d6c75c70985..2e85cb05ed37 100644 --- a/drivers/usb/typec/tcpm/tcpci_mt6370.c +++ b/drivers/usb/typec/tcpm/tcpci_mt6370.c @@ -168,7 +168,7 @@ static int mt6370_tcpc_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, mt6370_irq_handler, IRQF_ONESHOT, dev_name(dev), priv); if (ret) - return dev_err_probe(dev, ret, "Failed to allocate irq\n"); + return ret; device_init_wakeup(dev, true); dev_pm_set_wake_irq(dev, irq); From e0de3d952dab7807de15c0b3a58af52edcd23ba0 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 21 Aug 2026 20:16:34 +0400 Subject: [PATCH 030/158] dt-bindings: usb: ti,hd3ss3220: Add support for supply regulators HD3SS3220 requires two supply regulators to operate. It also strictly requires that 5V is present before 3.3V, otherwise it gets backpowered in a non-functional state through the 3.3V rail and kills the I2C bus. Add both supply regulators to enable their explicit description in board device trees. Signed-off-by: Alexey Charkov Acked-by: Conor Dooley Link: https://patch.msgid.link/20260821-hd3ss3220-regulators-v1-1-405e5c4fb306@flipper.net Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/ti,hd3ss3220.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml b/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml index 06099e93c6c3..654a982666dd 100644 --- a/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml +++ b/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml @@ -25,6 +25,15 @@ properties: interrupts: maxItems: 1 + vcc33-supply: + description: 3.3V supply (VCC33 pin), powering the SuperSpeed 2:1 MUX. + + vdd5-supply: + description: + 5V supply (VDD5 pin), powering the CC controller and sourcing VCONN. + VDD5 has to be stable for at least tVDD5V_PG (2ms) before VCC33 starts + ramping up, unless ENn_CC is held high while both rails ramp up. + id-gpios: description: An input gpio for USB ID pin. Upon detecting a UFP device, HD3SS3220 @@ -68,6 +77,8 @@ examples: reg = <0x47>; interrupt-parent = <&gpio6>; interrupts = <3>; + vcc33-supply = <&vcc3v3_control>; + vdd5-supply = <&vcc5v0_device_s0>; ports { #address-cells = <1>; From edc1518454f6439e50a9220e9a3ba09044ea8d8b Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 21 Aug 2026 20:16:35 +0400 Subject: [PATCH 031/158] usb: typec: hd3ss3220: Add support for supply regulators HD3SS3220 requires VDD5 input to be present 2ms before VCC33 is applied, or else it gets backpowered via the 3.3V rail in a non-functional state and wedges the I2C bus, bringing down all devices on it. Enable both regulators in the datasheet prescribed sequence if provided. Signed-off-by: Alexey Charkov Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260821-hd3ss3220-regulators-v1-2-405e5c4fb306@flipper.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/hd3ss3220.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index d14c308f108e..ffe0c8233e3d 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -49,6 +49,9 @@ #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP BIT(4) #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP (BIT(5) | BIT(4)) +/* Minimum time VDD5 has to be stable before VCC33 starts ramping up */ +#define HD3SS3220_TVDD5V_PG_US 2000 + struct hd3ss3220 { struct device *dev; struct regmap *regmap; @@ -358,6 +361,31 @@ static irqreturn_t hd3ss3220_id_isr(int irq, void *dev_id) return IRQ_HANDLED; } +/* + * Bring both supplies up in the order the datasheet asks for. Powering VCC33 + * first can back-power the device in a non-functioning state, which grounds + * the I2C bus and takes both this device and any others on the same bus down + */ +static int hd3ss3220_power_up(struct device *dev) +{ + int ret; + + ret = devm_regulator_get_enable(dev, "vdd5"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable VDD5\n"); + + /* Nothing to stagger against unless the board describes both rails */ + if (device_property_present(dev, "vdd5-supply") && + device_property_present(dev, "vcc33-supply")) + fsleep(HD3SS3220_TVDD5V_PG_US); + + ret = devm_regulator_get_enable(dev, "vcc33"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable VCC33\n"); + + return 0; +} + static int hd3ss3220_probe(struct i2c_client *client) { struct typec_capability typec_cap = { }; @@ -379,6 +407,10 @@ static int hd3ss3220_probe(struct i2c_client *client) if (IS_ERR(hd3ss3220->regmap)) return PTR_ERR(hd3ss3220->regmap); + ret = hd3ss3220_power_up(hd3ss3220->dev); + if (ret) + return ret; + /* For backward compatibility check the connector child node first */ connector = device_get_named_child_node(hd3ss3220->dev, "connector"); if (connector) { From 87045fbb8083abb9d77e4874ce5d431b2cdd7ef3 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 14:19:16 +0300 Subject: [PATCH 032/158] thunderbolt: Allow batching of descriptors The hardware allows queueing descriptors ahead of updating producer/consumer fields. This way it is possible to avoid unnecessary register writes on hot-paths such as when transferring networking packets. For this reason introduce an API that allows Thunderbolt service drivers to opt-in for this and take advantage of batching. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 58 ++++++++++++++++++++++++++++++++----- include/linux/thunderbolt.h | 42 +++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 5809809f64d4..a0f7885845bc 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -226,12 +226,37 @@ static bool ring_empty(struct tb_ring *ring) return ring->head == ring->tail; } +static void __ring_notify(struct tb_ring *ring) +{ + lockdep_assert_held(&ring->lock); + + if (ring->notify_pending) { + /* + * The doorbell carries the absolute index of the head + * so a single write covers all the descriptors posted + * since the previous one. + */ + if (ring->is_tx) + ring_iowrite_prod(ring, ring->head); + else + ring_iowrite_cons(ring, ring->head); + } + ring->notify_pending = false; +} + /* * ring_write_descriptors() - post frames from ring->queue to the controller + * @ring: Ring to post the frames to + * @notify: Notify the controller about the posted descriptors + * + * Unless @notify is %true the controller is not notified about the posted + * descriptors and the caller is expected to call tb_ring_notify() once it + * is done queuing frames. This allows batching of frames before + * updating the producer/consumer indices. * * ring->lock is held. */ -static void ring_write_descriptors(struct tb_ring *ring) +static void ring_write_descriptors(struct tb_ring *ring, bool notify) { struct ring_frame *frame, *n; struct ring_desc *descriptor; @@ -255,11 +280,11 @@ static void ring_write_descriptors(struct tb_ring *ring) descriptor->sof = frame->sof; } ring->head = (ring->head + 1) % ring->size; - if (ring->is_tx) - ring_iowrite_prod(ring, ring->head); - else - ring_iowrite_cons(ring, ring->head); + ring->notify_pending = true; } + + if (notify) + __ring_notify(ring); } /* @@ -304,7 +329,7 @@ static void ring_work(struct work_struct *work) } ring->tail = (ring->tail + 1) % ring->size; } - ring_write_descriptors(ring); + ring_write_descriptors(ring, true); invoke_callback: /* allow callbacks to schedule new work */ @@ -323,7 +348,7 @@ static void ring_work(struct work_struct *work) wake_up(&ring->wait); } -int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame) +int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame, bool more) { unsigned long flags; int ret = 0; @@ -331,7 +356,7 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame) spin_lock_irqsave(&ring->lock, flags); if (ring->running) { list_add_tail(&frame->list, &ring->queue); - ring_write_descriptors(ring); + ring_write_descriptors(ring, !more); } else { ret = -ESHUTDOWN; } @@ -340,6 +365,22 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame) } EXPORT_SYMBOL_GPL(__tb_ring_enqueue); +/** + * tb_ring_notify() - Notify the controller about the queued frames + * @ring: Ring to notify + * + * Notifies the controller about frames that were enqueued using + * tb_ring_tx_more() or tb_ring_rx_more(). Does nothing if there are no + * such frames pending. + */ +void tb_ring_notify(struct tb_ring *ring) +{ + guard(spinlock_irqsave)(&ring->lock); + if (ring->running) + __ring_notify(ring); +} +EXPORT_SYMBOL_GPL(tb_ring_notify); + /** * tb_ring_poll() - Poll one completed frame from the ring * @ring: Ring to poll @@ -784,6 +825,7 @@ void tb_ring_stop(struct tb_ring *ring) ring_iowrite32desc(ring, 0, 12); ring->head = 0; ring->tail = 0; + ring->notify_pending = false; ring->running = false; err: diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index d48623fda79b..6fda5596c44b 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -552,6 +552,8 @@ struct tb_nhi { * @work: Interrupt work structure * @is_tx: Is the ring Tx or Rx * @running: Is the ring running + * @notify_pending: Controller has not been notified about the posted + * descriptors yet * @irq: MSI-X irq number if the ring uses MSI-X. %0 otherwise. * @vector: MSI-X vector number the ring uses (only set if @irq is > 0) * @flags: Ring specific flags @@ -580,6 +582,7 @@ struct tb_ring { struct work_struct work; bool is_tx:1; bool running:1; + bool notify_pending:1; int irq; u8 vector; unsigned int flags; @@ -669,7 +672,8 @@ bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec); void tb_ring_stop(struct tb_ring *ring); void tb_ring_free(struct tb_ring *ring); -int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame); +int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame, bool more); +void tb_ring_notify(struct tb_ring *ring); /** * tb_ring_rx() - enqueue a frame on an RX ring @@ -690,7 +694,24 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame); static inline int tb_ring_rx(struct tb_ring *ring, struct ring_frame *frame) { WARN_ON(ring->is_tx); - return __tb_ring_enqueue(ring, frame); + return __tb_ring_enqueue(ring, frame, false); +} + +/** + * tb_ring_rx_more() - enqueue a frame on an RX ring without notifying + * @ring: Ring to enqueue the frame + * @frame: Frame to enqueue + * + * Same as tb_ring_rx() but does not notify the controller about the + * enqueued frame. The caller must call tb_ring_notify() once it is done + * enqueuing frames. + * + * Return: %-ESHUTDOWN if tb_ring_stop() has been called, %0 otherwise. + */ +static inline int tb_ring_rx_more(struct tb_ring *ring, struct ring_frame *frame) +{ + WARN_ON(ring->is_tx); + return __tb_ring_enqueue(ring, frame, true); } /** @@ -711,7 +732,22 @@ static inline int tb_ring_rx(struct tb_ring *ring, struct ring_frame *frame) static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame) { WARN_ON(!ring->is_tx); - return __tb_ring_enqueue(ring, frame); + return __tb_ring_enqueue(ring, frame, false); +} + +/** + * tb_ring_tx_more() - enqueue a frame on a TX ring without notifying + * @ring: Ring to enqueue the frame + * @frame: Frame to enqueue + * + * Same as tb_ring_rx_more() but for TX ring. + * + * Return: %-ESHUTDOWN if tb_ring_stop() has been called, %0 otherwise. + */ +static inline int tb_ring_tx_more(struct tb_ring *ring, struct ring_frame *frame) +{ + WARN_ON(!ring->is_tx); + return __tb_ring_enqueue(ring, frame, true); } /* Used only when the ring is in polling mode */ From 2d8b6e38b86c4cbad9d7bdd8371c696ef34b900c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 14:24:02 +0300 Subject: [PATCH 033/158] net: thunderbolt: Update ring indices only after all frames are queued Take advantage of the new API and update the ring producer/consumer indices only after the frames have been queued. This reduces the number of doorbell register writes from one per frame to one per batch. Measured using iperf3 over a Thunderbolt/USB4 net connection between Arrow Lake and Panther Lake systems (average of three runs): before after ~14.3 Gbit/s ~15.3 Gbit/s Assisted-by: LLM Acked-by: Jakub Kicinski Signed-off-by: Mika Westerberg --- drivers/net/thunderbolt/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index d9fb587a62c5..cf51b9c39f4e 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -540,11 +540,12 @@ static int tbnet_alloc_rx_buffers(struct tbnet *net, unsigned int nbuffers) trace_tbnet_alloc_rx_frame(index, tf->page, dma_addr, DMA_FROM_DEVICE); - tb_ring_rx(ring->ring, &tf->frame); + tb_ring_rx_more(ring->ring, &tf->frame); ring->prod++; } + tb_ring_notify(ring->ring); return 0; err_free: @@ -1243,7 +1244,8 @@ static netdev_tx_t tbnet_start_xmit(struct sk_buff *skb, goto err_drop; for (i = 0; i < frame_index + 1; i++) - tb_ring_tx(net->tx_ring.ring, &frames[i]->frame); + tb_ring_tx_more(net->tx_ring.ring, &frames[i]->frame); + tb_ring_notify(net->tx_ring.ring); if (net->svc->prtcstns & TBNET_MATCH_FRAGS_ID) atomic_inc(&net->frame_id); From 41d5a1fc2810046547b0c5713a4dbf2284edf8f6 Mon Sep 17 00:00:00 2001 From: Atharva Tiwari Date: Tue, 18 Aug 2026 05:50:02 -0400 Subject: [PATCH 034/158] thunderbolt: Move tb_apple_add_links() to pci.c Due to historical reasons the software connection manager in tb.c directly sets up the device link between the tunneled PCIe ports and the NHI. Now as we are starting to support non-PCIe host interfaces as well we are trying to keep the connection manager implementation agnostic of the underlying host interface. For this reason move the device link creation into pci.c and expose it through a new NHI operation that the host interface code calls when it sets up the connection manager. Suggested-by: Mika Westerberg Signed-off-by: Atharva Tiwari Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 34 ++++++++++++----- drivers/thunderbolt/nhi.h | 2 + drivers/thunderbolt/pci.c | 76 ++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 78 --------------------------------------- 4 files changed, 103 insertions(+), 87 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index a0f7885845bc..af050d9853f6 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1245,24 +1245,40 @@ void nhi_reset_interface(struct tb_nhi *nhi) static struct tb *nhi_select_cm(struct tb_nhi *nhi) { + bool linked = false; struct tb *tb; /* * USB4 case is simple. If we got control of any of the * capabilities, we use software CM. */ - if (tb_acpi_is_native()) - return tb_probe(nhi); + if (!tb_acpi_is_native()) { + /* + * Either firmware based CM is running (we did not get + * control from the firmware) or this is pre-USB4 PC so + * try first firmware CM and then fallback to software CM. + */ + tb = icm_probe(nhi); + if (tb) + return tb; + } - /* - * Either firmware based CM is running (we did not get control - * from the firmware) or this is pre-USB4 PC so try first - * firmware CM and then fallback to software CM. - */ - tb = icm_probe(nhi); + tb = tb_probe(nhi); if (!tb) - tb = tb_probe(nhi); + return NULL; + if (nhi->ops->add_links) + linked = nhi->ops->add_links(nhi); + if (!linked) + linked = tb_acpi_add_links(nhi); + /* + * Device links are needed to make sure we establish tunnels + * before protocol stacks are resumed so complain here if we + * found them missing. + */ + if (!linked) + dev_warn(nhi->dev, + "device links to tunneled native ports are missing!\n"); return tb; } diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index f72d6b274501..b2e2e2c413b2 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -43,6 +43,7 @@ extern const struct dev_pm_ops nhi_pm_ops; /** * struct tb_nhi_ops - NHI specific optional operations * @init: NHI specific initialization + * @add_links: Setup device links for tunneling native protocols * @suspend_noirq: NHI specific suspend_noirq hook * @resume_noirq: NHI specific resume_noirq hook * @runtime_suspend: NHI specific runtime_suspend hook @@ -58,6 +59,7 @@ extern const struct dev_pm_ops nhi_pm_ops; */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); + bool (*add_links)(struct tb_nhi *nhi); int (*suspend_noirq)(struct tb_nhi *nhi, bool wakeup); int (*resume_noirq)(struct tb_nhi *nhi); int (*runtime_suspend)(struct tb_nhi *nhi); diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index 99333729f3c2..c9c50004a6ac 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "nhi.h" #include "nhi_regs.h" @@ -129,6 +130,80 @@ static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci) str_enabled_disabled(port_ok)); } +static bool add_link(struct tb_nhi *nhi, struct pci_dev *pdev) +{ + const struct device_link *link; + + link = device_link_add(&pdev->dev, nhi->dev, + DL_FLAG_AUTOREMOVE_SUPPLIER | + DL_FLAG_PM_RUNTIME); + if (!link) { + dev_warn(nhi->dev, "device link creation from %s failed\n", + dev_name(&pdev->dev)); + return false; + } + + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); + return true; +} + +/* + * During suspend the Thunderbolt controller is reset and all PCIe + * tunnels are lost. The NHI driver will try to reestablish all tunnels + * during resume. This adds device links between the tunneled PCIe + * downstream ports and the NHI so that the device core will make sure + * NHI is resumed first before the rest. + */ +static bool nhi_pci_add_links(struct tb_nhi *nhi) +{ + struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev); + struct pci_dev *upstream, *pdev; + bool ret; + + if (!x86_apple_machine) + return false; + + switch (nhi_pdev->device) { + case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE: + case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: + break; + default: + return false; + } + + upstream = pci_upstream_bridge(nhi_pdev); + while (upstream) { + if (!pci_is_pcie(upstream)) + return false; + if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM) + break; + upstream = pci_upstream_bridge(upstream); + } + + if (!upstream) + return false; + + /* + * For each hotplug downstream port, create add device link back + * to NHI so that PCIe tunnels can be re-established after + * sleep. + */ + ret = false; + for_each_pci_bridge(pdev, upstream->subordinate) { + if (!pci_is_pcie(pdev)) + continue; + if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM || + !pdev->is_pciehp) + continue; + + ret |= add_link(nhi, pdev); + } + + return ret; +} + static int nhi_pci_init_msi(struct tb_nhi *nhi) { struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); @@ -272,6 +347,7 @@ static bool nhi_pci_is_present(struct tb_nhi *nhi) } static const struct tb_nhi_ops pci_nhi_default_ops = { + .add_links = nhi_pci_add_links, .pre_nvm_auth = nhi_pci_start_dma_port, .post_nvm_auth = nhi_pci_complete_dma_port, .request_ring_irq = nhi_pci_ring_request_msix, diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 47753a5c0f2e..7e39b067e5ff 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -10,7 +10,6 @@ #include #include #include -#include #include "tb.h" #include "tb_regs.h" @@ -3323,75 +3322,6 @@ static const struct tb_cm_ops tb_cm_ops = { .disconnect_xdomain_paths = tb_disconnect_xdomain_paths, }; -/* - * During suspend the Thunderbolt controller is reset and all PCIe - * tunnels are lost. The NHI driver will try to reestablish all tunnels - * during resume. This adds device links between the tunneled PCIe - * downstream ports and the NHI so that the device core will make sure - * NHI is resumed first before the rest. - */ -static bool tb_apple_add_links(struct tb_nhi *nhi) -{ - struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev); - struct pci_dev *upstream, *pdev; - bool ret; - - if (!x86_apple_machine) - return false; - - switch (nhi_pdev->device) { - case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE: - case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: - break; - default: - return false; - } - - upstream = pci_upstream_bridge(nhi_pdev); - while (upstream) { - if (!pci_is_pcie(upstream)) - return false; - if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM) - break; - upstream = pci_upstream_bridge(upstream); - } - - if (!upstream) - return false; - - /* - * For each hotplug downstream port, create add device link - * back to NHI so that PCIe tunnels can be re-established after - * sleep. - */ - ret = false; - for_each_pci_bridge(pdev, upstream->subordinate) { - const struct device_link *link; - - if (!pci_is_pcie(pdev)) - continue; - if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM || - !pdev->is_pciehp) - continue; - - link = device_link_add(&pdev->dev, nhi->dev, - DL_FLAG_AUTOREMOVE_SUPPLIER | - DL_FLAG_PM_RUNTIME); - if (link) { - dev_dbg(nhi->dev, "created link from %s\n", - dev_name(&pdev->dev)); - ret = true; - } else { - dev_warn(nhi->dev, "device link creation from %s failed\n", - dev_name(&pdev->dev)); - } - } - - return ret; -} - struct tb *tb_probe(struct tb_nhi *nhi) { struct tb_cm *tcm; @@ -3416,13 +3346,5 @@ struct tb *tb_probe(struct tb_nhi *nhi) tb_dbg(tb, "using software connection manager\n"); - /* - * Device links are needed to make sure we establish tunnels - * before the PCIe/USB stack is resumed so complain here if we - * found them missing. - */ - if (!tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi)) - tb_warn(tb, "device links to tunneled native ports are missing!\n"); - return tb; } From 03364ca8c472618b28557b7044d38fc5d5844edd Mon Sep 17 00:00:00 2001 From: Atharva Tiwari Date: Tue, 18 Aug 2026 05:50:03 -0400 Subject: [PATCH 035/158] thunderbolt: Add device links for Apple machines with Titan Ridge Add device links for Apple machines with the Titan Ridge thunderbolt controller. Signed-off-by: Atharva Tiwari Signed-off-by: Mika Westerberg --- drivers/thunderbolt/pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index c9c50004a6ac..996a8ab3cfb7 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -168,6 +168,8 @@ static bool nhi_pci_add_links(struct tb_nhi *nhi) case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI: break; default: return false; From 6cd43dea7b243aba6225a183a9d1c3698b9cc635 Mon Sep 17 00:00:00 2001 From: Atharva Tiwari Date: Tue, 18 Aug 2026 05:50:04 -0400 Subject: [PATCH 036/158] thunderbolt: Add device links for Apple systems with Ice Lake Ice Lake Thunderbolt NHI that are on some Macs. The NHI and its associated PCIe Root Ports all sit directly on the Root Complex with no upstream port. Identify the tunneled PCIe Root Ports by their PCI IDs and create device links back to the NHI so that PCIe tunnels can be re-established after sleep. Co-developed-by: Andre Eikmeyer Signed-off-by: Andre Eikmeyer Signed-off-by: Atharva Tiwari Signed-off-by: Mika Westerberg --- drivers/thunderbolt/pci.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index 996a8ab3cfb7..e40d4d6af071 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -521,6 +521,40 @@ static int icl_nhi_resume(struct tb_nhi *nhi) return 0; } +static bool icl_nhi_add_links(struct tb_nhi *nhi) +{ + struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev); + struct pci_bus *bus = nhi_pdev->bus; + struct pci_dev *pdev; + bool ret; + + if (!x86_apple_machine) + return false; + + /* + * On integrated controllers the tunneled PCIe root ports are + * directly under the host bridge. + */ + if (!pci_is_root_bus(bus)) + return false; + + ret = false; + for_each_pci_bridge(pdev, bus) { + switch (nhi_pdev->device) { + case PCI_DEVICE_ID_INTEL_ICL_NHI0: + if (pdev->device == 0x8a1d || pdev->device == 0x8a1f) + ret |= add_link(nhi, pdev); + break; + case PCI_DEVICE_ID_INTEL_ICL_NHI1: + if (pdev->device == 0x8a21 || pdev->device == 0x8a23) + ret |= add_link(nhi, pdev); + break; + } + } + + return ret; +} + static void icl_nhi_shutdown(struct tb_nhi *nhi) { nhi_pci_release_irq(nhi); @@ -530,6 +564,7 @@ static void icl_nhi_shutdown(struct tb_nhi *nhi) static const struct tb_nhi_ops icl_nhi_ops = { .init = icl_nhi_resume, + .add_links = icl_nhi_add_links, .suspend_noirq = icl_nhi_suspend_noirq, .resume_noirq = icl_nhi_resume, .runtime_suspend = icl_nhi_suspend, From 319d216d6546b2db9de5a818039a65ed62f0a7fd Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:00:21 +0530 Subject: [PATCH 037/158] thunderbolt: Fix typo "forwared" in comment Correct "forwared" to "forwarded", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Mika Westerberg --- include/linux/thunderbolt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 6fda5596c44b..57cdfe774883 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -369,7 +369,7 @@ int tb_xdomain_request(struct tb_xdomain *xd, const void *request, * @uuid: XDomain messages with this UUID are dispatched to this handler * @callback: Callback called with the XDomain message. Returning %1 * here tells the XDomain core that the message was handled - * by this handler and should not be forwared to other + * by this handler and should not be forwarded to other * handlers. * @data: Data passed with the callback * @list: Handlers are linked using this From ea958a19076769ed2bc82efccc0168819e437d57 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 16:41:51 +0300 Subject: [PATCH 038/158] thunderbolt: Do not WARN about already disabled interrupt on polled rings With ->start_poll it is possible that the ring is actually already disabled before tb_ring_stop() is called. This is expected behaviour so there is no point warning about that. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index af050d9853f6..f716910e0372 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -138,10 +138,19 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) "%s interrupt at register %#x bit %d (%#x -> %#x)\n", active ? "enabling" : "disabling", reg, interrupt_bit, old, new); - if (new == old) - dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n", - RING_TYPE(ring), ring->hop, - str_enabled_disabled(active)); + if (new == old) { + /* + * Rings that are polled mask the interrupt using while + * the completions are being advanced (see + * __ring_interrupt()) so for those it can already be + * disabled by the time the ring is stopped. + */ + if (active || !ring->start_poll) + dev_WARN(ring->nhi->dev, + "interrupt for %s %d is already %s\n", + RING_TYPE(ring), ring->hop, + str_enabled_disabled(active)); + } if (active) iowrite32(new, ring->nhi->iobase + reg); From 85e5f54bb3b6e22d8a4a542b098a2600fdb68ced Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 17 Aug 2026 14:09:36 +0300 Subject: [PATCH 039/158] thunderbolt: Clean up ring interrupt register indexing nhi_mask_interrupt() and nhi_clear_interrupt() take "ring" as parameter but in fact it is not an actual ring but a byte offset to the interrupt register. Make this less confusing and name the paramers what it really is and calculate the offset where it is actually needed. In addition ring_interrupt_active() has two variables called "index" with different meanings, and the second one shadows the first one open-coding ring_interrupt_index() as well. Drop that and rename the remaining what they actually hold. While there, make the mask variable u32 and use BIT() to avoid signed shifting. No functional changes intended. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index f716910e0372..dfa9e9afea7b 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -40,6 +40,7 @@ static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); +/* Returns absolute bit number of the ring in the interrupt registers */ static int ring_interrupt_index(const struct tb_ring *ring) { int bit = ring->hop; @@ -48,24 +49,28 @@ static int ring_interrupt_index(const struct tb_ring *ring) return bit; } -static void nhi_mask_interrupt(struct tb_nhi *nhi, int mask, int ring) +static void nhi_mask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index) { + int offset = reg_index * 4; + if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) { u32 val; - val = ioread32(nhi->iobase + REG_RING_INTERRUPT_BASE + ring); - iowrite32(val & ~mask, nhi->iobase + REG_RING_INTERRUPT_BASE + ring); + val = ioread32(nhi->iobase + REG_RING_INTERRUPT_BASE + offset); + iowrite32(val & ~mask, nhi->iobase + REG_RING_INTERRUPT_BASE + offset); } else { - iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + ring); + iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + offset); } } -static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring) +static void nhi_clear_interrupt(struct tb_nhi *nhi, int reg_index) { + int offset = reg_index * 4; + if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) - ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + ring); + ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + offset); else - iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + ring); + iowrite32(~0, nhi->iobase + REG_RING_INT_CLEAR + offset); } /* @@ -75,22 +80,17 @@ static void nhi_clear_interrupt(struct tb_nhi *nhi, int ring) */ static void ring_interrupt_active(struct tb_ring *ring, bool active) { - int index = ring_interrupt_index(ring) / 32 * 4; - int reg = REG_RING_INTERRUPT_BASE + index; - int interrupt_bit = ring_interrupt_index(ring) & 31; - int mask = 1 << interrupt_bit; + int interrupt_index = ring_interrupt_index(ring); + int reg_index = interrupt_index / 32; + int reg = REG_RING_INTERRUPT_BASE + reg_index * 4; + int interrupt_bit = interrupt_index % 32; + u32 mask = BIT(interrupt_bit); u32 old, new; if (ring->irq > 0) { u32 step, shift, ivr, misc, itr; void __iomem *ivr_base; int auto_clear_bit; - int index; - - if (ring->is_tx) - index = ring->hop; - else - index = ring->hop + ring->nhi->hop_count; /* * Intel routers support a bit that isn't part of @@ -113,8 +113,8 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) ring->nhi->iobase + REG_DMA_MISC); ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE; - step = index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS; - shift = index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS; + step = interrupt_index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS; + shift = interrupt_index % REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS; ivr = ioread32(ivr_base + step); ivr &= ~(REG_INT_VEC_ALLOC_MASK << shift); if (active) @@ -155,7 +155,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) if (active) iowrite32(new, ring->nhi->iobase + reg); else - nhi_mask_interrupt(ring->nhi, mask, index); + nhi_mask_interrupt(ring->nhi, mask, reg_index); } /* @@ -168,11 +168,11 @@ void nhi_disable_interrupts(struct tb_nhi *nhi) int i = 0; /* disable interrupts */ for (i = 0; i < RING_INTERRUPT_REG_COUNT(nhi); i++) - nhi_mask_interrupt(nhi, ~0, 4 * i); + nhi_mask_interrupt(nhi, ~0, i); /* clear interrupt status bits */ for (i = 0; i < RING_NOTIFY_REG_COUNT(nhi); i++) - nhi_clear_interrupt(nhi, 4 * i); + nhi_clear_interrupt(nhi, i); } /* ring helper methods */ From 26e6e964d54d893f21f5614671fdcd24eacc3b78 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 16:55:04 +0300 Subject: [PATCH 040/158] thunderbolt: Use shadow copy for ring interrupt mask Each time we mask or unmask ring interrupt we do a read-modify-write and that is not without a cost when on a hot path. We can get rid of that read by making a shadow copy of the interrupt mask register and using that in place of a register read. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 55 +++++++++++++++++++++++-------------- include/linux/thunderbolt.h | 2 ++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index dfa9e9afea7b..521ba8b57b28 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -52,15 +52,28 @@ static int ring_interrupt_index(const struct tb_ring *ring) static void nhi_mask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index) { int offset = reg_index * 4; + u32 val; - if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) { - u32 val; + /* Use shadow copy instead of reading the register */ + val = nhi->interrupt_mask[reg_index] & ~mask; + nhi->interrupt_mask[reg_index] = val; - val = ioread32(nhi->iobase + REG_RING_INTERRUPT_BASE + offset); - iowrite32(val & ~mask, nhi->iobase + REG_RING_INTERRUPT_BASE + offset); - } else { + if (nhi->quirks & QUIRK_AUTO_CLEAR_INT) + iowrite32(val, nhi->iobase + REG_RING_INTERRUPT_BASE + offset); + else iowrite32(mask, nhi->iobase + REG_RING_INTERRUPT_MASK_CLEAR_BASE + offset); - } +} + +static void nhi_unmask_interrupt(struct tb_nhi *nhi, u32 mask, int reg_index) +{ + int offset = reg_index * 4; + u32 val; + + /* Use shadow copy instead of reading the register */ + val = nhi->interrupt_mask[reg_index] | mask; + nhi->interrupt_mask[reg_index] = val; + + iowrite32(val, nhi->iobase + REG_RING_INTERRUPT_BASE + offset); } static void nhi_clear_interrupt(struct tb_nhi *nhi, int reg_index) @@ -128,7 +141,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) ring->vector * 4); } - old = ioread32(ring->nhi->iobase + reg); + old = ring->nhi->interrupt_mask[reg_index]; if (active) new = old | mask; else @@ -140,10 +153,10 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) if (new == old) { /* - * Rings that are polled mask the interrupt using while - * the completions are being advanced (see - * __ring_interrupt()) so for those it can already be - * disabled by the time the ring is stopped. + * Rings that are polled mask the interrupt while the + * completions are being advanced (see __ring_interrupt()) + * so for those it can already be disabled by the time + * the ring is stopped. */ if (active || !ring->start_poll) dev_WARN(ring->nhi->dev, @@ -153,7 +166,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) } if (active) - iowrite32(new, ring->nhi->iobase + reg); + nhi_unmask_interrupt(ring->nhi, mask, reg_index); else nhi_mask_interrupt(ring->nhi, mask, reg_index); } @@ -436,17 +449,14 @@ EXPORT_SYMBOL_GPL(tb_ring_poll); static void __ring_interrupt_mask(struct tb_ring *ring, bool mask) { - int idx = ring_interrupt_index(ring); - int reg = REG_RING_INTERRUPT_BASE + idx / 32 * 4; - int bit = idx % 32; - u32 val; + int interrupt_index = ring_interrupt_index(ring); + int reg_index = interrupt_index / 32; + int interrupt_bit = interrupt_index % 32; - val = ioread32(ring->nhi->iobase + reg); if (mask) - val &= ~BIT(bit); + nhi_mask_interrupt(ring->nhi, BIT(interrupt_bit), reg_index); else - val |= BIT(bit); - iowrite32(val, ring->nhi->iobase + reg); + nhi_unmask_interrupt(ring->nhi, BIT(interrupt_bit), reg_index); } /* Both @nhi->lock and @ring->lock should be held */ @@ -1310,7 +1320,10 @@ int nhi_probe(struct tb_nhi *nhi) sizeof(*nhi->tx_rings), GFP_KERNEL); nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->rx_rings), GFP_KERNEL); - if (!nhi->tx_rings || !nhi->rx_rings) + nhi->interrupt_mask = devm_kcalloc(dev, RING_INTERRUPT_REG_COUNT(nhi), + sizeof(*nhi->interrupt_mask), + GFP_KERNEL); + if (!nhi->tx_rings || !nhi->rx_rings || !nhi->interrupt_mask) return -ENOMEM; nhi_reset(nhi); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 57cdfe774883..335fa31ab1df 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -506,6 +506,7 @@ void tb_service_properties_changed(struct tb_service *svc); * @iobase: MMIO space of the NHI * @tx_rings: All Tx rings available on this host controller * @rx_rings: All Rx rings available on this host controller + * @interrupt_mask: Shadow copy of the ring interrupt mask register * @going_away: The host controller device is about to disappear so when * this flag is set, avoid touching the hardware anymore. * @iommu_dma_protection: An IOMMU will isolate external-facing ports. @@ -527,6 +528,7 @@ struct tb_nhi { void __iomem *iobase; struct tb_ring **tx_rings; struct tb_ring **rx_rings; + u32 *interrupt_mask; bool going_away; bool iommu_dma_protection; struct work_struct interrupt_work; From 20bea8ceeb6ab6ab918ee8fdf5855541214907c4 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 17:00:03 +0300 Subject: [PATCH 041/158] thunderbolt: stream: Use polling with RX ring The ring API already supports polling so that once there is anything in the RX ring, it will mask the ring interrupt and call the ->start_poll callback. This is the same mechanism that the USB4/Thunderbolt networking driver already uses. We can do the same for the USB4STREAM driver and poll any RX frames while they are available, until we re-enable the ring interrupt again. This provides better latency when the stream is in "normal" (non-busy-polling) mode. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 105 ++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index c737dd0ca6e7..56e155750466 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -131,6 +131,7 @@ struct tbstream_ring { * @ring_size: Size of the rings * @throttling: Interrupt throttling rate in ns * @busy_poll: Instead of interrupts, busy poll the rings + * @rx_pending: Receive ring has completions that need to be advanced * @users: Number of times @cdev has been opened * @closed: CLOSE packet was received * @removed: Userspace removed the ConfigFS group underneath. @@ -151,6 +152,7 @@ struct tbstream_dev { unsigned int ring_size; unsigned int throttling; bool busy_poll; + bool rx_pending; int users; bool closed; bool removed; @@ -278,6 +280,14 @@ static inline bool tbstream_ring_available(const struct tbstream_ring *ring) return ring->prod > ring->cons; } +static void tbstream_ring_poll(struct tbstream_ring *ring) +{ + struct ring_frame *frame; + + while ((frame = tb_ring_poll(ring->ring))) + frame->callback(ring->ring, frame, false); +} + static inline struct tb_xdomain *tbstream_dev_xdomain(struct tbstream_dev *sdev) { if (sdev->stream) @@ -540,18 +550,6 @@ 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; @@ -568,7 +566,7 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) do { if (tbstream_ring_available(&sdev->tx_ring)) break; - tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + tbstream_ring_poll(&sdev->tx_ring); fsleep(15); } while (ktime_before(ktime_get(), timeout)); } @@ -579,16 +577,45 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); } +static void tbstream_dev_start_poll(void *data) +{ + struct tbstream_dev *sdev = data; + + WRITE_ONCE(sdev->rx_pending, true); + wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM); +} + +/* sdev->lock must be held */ +static void tbstream_dev_advance_rx(struct tbstream_dev *sdev) +{ + /* + * Clear before running the completions so that an interrupt + * that arrives while we are doing that is not missed. + */ + WRITE_ONCE(sdev->rx_pending, false); + tbstream_ring_poll(&sdev->rx_ring); +} + +/* sdev->lock must be held */ +static void tbstream_dev_complete_rx(struct tbstream_dev *sdev) +{ + if (!sdev->busy_poll) + tb_ring_poll_complete(sdev->rx_ring.ring); +} + 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; + void (*start_poll)(void *) = NULL; u16 sof_mask, eof_mask; struct tb_ring *ring; int ret, e2e_tx_hop; if (sdev->busy_poll) flags |= RING_FLAG_NO_INTERRUPT; + else + start_poll = tbstream_dev_start_poll; ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); if (!ring) @@ -604,7 +631,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, - e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + e2e_tx_hop, sof_mask, eof_mask, start_poll, + sdev); if (!ring) { ret = -ENOMEM; goto err_free_tx_buffers; @@ -621,6 +649,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) tb_ring_throttling(sdev->tx_ring.ring, sdev->throttling); tb_ring_throttling(sdev->rx_ring.ring, sdev->throttling); + sdev->rx_pending = false; + tb_ring_start(sdev->tx_ring.ring); tb_ring_start(sdev->rx_ring.ring); @@ -667,19 +697,16 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) do { if (tbstream_dev_tx_drained(sdev)) break; - tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + tbstream_ring_poll(&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); } + tb_ring_stop(sdev->tx_ring.ring); + tb_ring_stop(sdev->rx_ring.ring); + xd = tbstream_dev_xdomain(sdev); if (xd) { tb_xdomain_disable_paths(xd, sdev->out_hopid, @@ -727,8 +754,8 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) return ret; for (;;) { - /* When busy polling, advance any completions manually */ - tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + /* Advance RX completions */ + tbstream_dev_advance_rx(sdev); ret = tbstream_dev_valid(sdev); if (ret) { @@ -744,6 +771,8 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (tbstream_ring_available(&sdev->rx_ring)) break; + /* Polled all we could. Re-enable the interrupt now. */ + tbstream_dev_complete_rx(sdev); mutex_unlock(&sdev->lock); if (nowait) @@ -755,6 +784,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) cond_resched(); } else { ret = wait_event_interruptible(sdev->wait, + READ_ONCE(sdev->rx_pending) || tbstream_ring_available(&sdev->rx_ring) || tbstream_dev_valid(sdev) != 0 || tbstream_dev_closed(sdev) || @@ -839,7 +869,9 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) return ret; for (;;) { - tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + /* When busy polling, advance any completions manually */ + if (sdev->busy_poll) + tbstream_ring_poll(&sdev->tx_ring); ret = tbstream_dev_valid(sdev); if (ret) { @@ -919,14 +951,23 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) poll_wait(file, &sdev->wait, wait); guard(mutex)(&sdev->lock); - if (tbstream_dev_valid(sdev) != 0) { - mask |= EPOLLHUP | EPOLLERR; - } else { - if (tbstream_ring_available(&sdev->tx_ring)) - mask |= EPOLLOUT | EPOLLWRNORM; - if (tbstream_ring_available(&sdev->rx_ring)) - mask |= EPOLLIN | EPOLLRDNORM; - } + if (tbstream_dev_valid(sdev) != 0) + return EPOLLHUP | EPOLLERR; + + /* + * The RX completions are only advanced from here and from + * read(2) so do that now, otherwise we would never report + * anything to be available. + */ + tbstream_dev_advance_rx(sdev); + + if (tbstream_ring_available(&sdev->tx_ring)) + mask |= EPOLLOUT | EPOLLWRNORM; + if (tbstream_ring_available(&sdev->rx_ring)) + mask |= EPOLLIN | EPOLLRDNORM; + else + tbstream_dev_complete_rx(sdev); + return mask; } From 38cfe418e57005575fa9692a8091aa441bce9fd1 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 13 Jul 2026 16:13:02 -0700 Subject: [PATCH 042/158] usb: ehci-ppc-of: use platform for irq and ioremap Replace the open-coded of_address_to_resource() plus devm_ioremap_resource() sequence with devm_platform_get_and_ioremap_resource(), which looks up the resource and maps it in one call. The helper returns a pointer to the resource, so update rsrc_start / rsrc_len to dereference it, assign the mapped address to hcd->regs, and use a separate on-stack resource (ohci_res) for the unrelated ibm,usb-ohci-440epx erratum lookup rather than aliasing the returned resource pointer. Switch IRQ acquisition from irq_of_parse_and_map() to platform_get_irq(), which only retrieves the interrupt the OF/platform core has already set up rather than transferring mapping ownership to the driver. Drop the now unneeded irq_dispose_mapping() calls (probe error path and ehci_hcd_ppc_of_remove()) and the now-unused of_irq.h and of_platform.h includes, keeping linux/of_address.h for the erratum block's of_address_to_resource(). Behaviorally equivalent with respect to region reservation: the prior code used devm_ioremap_resource(), which already reserved the region. Built for PowerPC (ppc44x_defconfig + CONFIG_USB_EHCI_HCD_PPC_OF) with LLVM=1; drivers/usb/host/ehci-hcd.o (which includes ehci-ppc-of.c) compiles cleanly. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260713231302.1076740-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-ppc-of.c | 47 ++++++++++++---------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index 8063b9d3aebd..457d15da40a3 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -18,8 +18,6 @@ #include #include -#include -#include static const struct hc_driver ehci_ppc_of_hc_driver = { @@ -96,7 +94,9 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op) struct device_node *dn = op->dev.of_node; struct usb_hcd *hcd; struct ehci_hcd *ehci = NULL; - struct resource res; + struct resource *res; + struct resource ohci_res; + void __iomem *regs; int irq; int rv; @@ -107,39 +107,30 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op) dev_dbg(&op->dev, "initializing PPC-OF USB Controller\n"); - rv = of_address_to_resource(dn, 0, &res); - if (rv) - return rv; + regs = devm_platform_get_and_ioremap_resource(op, 0, &res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(op, 0); + if (irq < 0) + return irq; hcd = usb_create_hcd(&ehci_ppc_of_hc_driver, &op->dev, "PPC-OF USB"); if (!hcd) return -ENOMEM; - hcd->rsrc_start = res.start; - hcd->rsrc_len = resource_size(&res); - - irq = irq_of_parse_and_map(dn, 0); - if (!irq) { - dev_err(&op->dev, "%s: irq_of_parse_and_map failed\n", - __FILE__); - rv = -EBUSY; - goto err_irq; - } - - hcd->regs = devm_ioremap_resource(&op->dev, &res); - if (IS_ERR(hcd->regs)) { - rv = PTR_ERR(hcd->regs); - goto err_ioremap; - } + hcd->regs = regs; + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); ehci = hcd_to_ehci(hcd); np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); if (np != NULL) { /* claim we really affected by usb23 erratum */ - if (!of_address_to_resource(np, 0, &res)) + if (!of_address_to_resource(np, 0, &ohci_res)) ehci->ohci_hcctrl_reg = devm_ioremap(&op->dev, - res.start + OHCI_HCCTRL_OFFSET, + ohci_res.start + OHCI_HCCTRL_OFFSET, OHCI_HCCTRL_LEN); else pr_debug("%s: no ohci offset in fdt\n", __FILE__); @@ -170,14 +161,12 @@ static int ehci_hcd_ppc_of_probe(struct platform_device *op) rv = usb_add_hcd(hcd, irq, 0); if (rv) - goto err_ioremap; + goto err; device_wakeup_enable(hcd->self.controller); return 0; -err_ioremap: - irq_dispose_mapping(irq); -err_irq: +err: usb_put_hcd(hcd); return rv; @@ -196,8 +185,6 @@ static void ehci_hcd_ppc_of_remove(struct platform_device *op) usb_remove_hcd(hcd); - irq_dispose_mapping(hcd->irq); - /* use request_mem_region to test if the ohci driver is loaded. if so * ensure the ohci core is operational. */ From 0c4470927e3c2af2b46362fcb7231e3f598610ab Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 13 Jul 2026 16:16:04 -0700 Subject: [PATCH 043/158] usb: ohci-ppc-of: use platform for irq and ioremap Replace the open-coded of_address_to_resource() plus devm_ioremap_resource() sequence with devm_platform_get_and_ioremap_resource(), which looks up the resource and maps it in one call. The helper returns a pointer to the resource, so update rsrc_start / rsrc_len to dereference it and assign the mapped address to hcd->regs. Use a separate on-stack resource (ehci_res) for the unrelated ibm,usb-ehci-440epx erratum lookup rather than aliasing the returned resource pointer. Switch IRQ acquisition from irq_of_parse_and_map() to platform_get_irq(), which only retrieves the interrupt the OF/platform core has already set up rather than transferring mapping ownership to the driver. Drop the now unneeded irq_dispose_mapping() calls (probe error path and ohci_hcd_ppc_of_remove()) and the now-unused of_irq.h include, keeping linux/of_address.h for the erratum block's of_address_to_resource(). Behaviorally equivalent with respect to region reservation: the prior code used devm_ioremap_resource(), which already reserved the region. Built for PowerPC (ppc44x_defconfig + CONFIG_USB_OHCI_HCD_PPC_OF) with LLVM=1; drivers/usb/host/ohci-hcd.o (which includes ohci-ppc-of.c) compiles cleanly. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260713231604.1090044-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-ppc-of.c | 44 +++++++++++++--------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index acd0a0e398a4..3a965b9c04fa 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -17,7 +17,6 @@ #include #include #include -#include #include static int @@ -87,7 +86,9 @@ static int ohci_hcd_ppc_of_probe(struct platform_device *op) struct device_node *dn = op->dev.of_node; struct usb_hcd *hcd; struct ohci_hcd *ohci; - struct resource res; + struct resource *res; + struct resource ehci_res; + void __iomem *regs; int irq; int rv; @@ -103,30 +104,21 @@ static int ohci_hcd_ppc_of_probe(struct platform_device *op) dev_dbg(&op->dev, "initializing PPC-OF USB Controller\n"); - rv = of_address_to_resource(dn, 0, &res); - if (rv) - return rv; + regs = devm_platform_get_and_ioremap_resource(op, 0, &res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(op, 0); + if (irq < 0) + return irq; hcd = usb_create_hcd(&ohci_ppc_of_hc_driver, &op->dev, "PPC-OF USB"); if (!hcd) return -ENOMEM; - hcd->rsrc_start = res.start; - hcd->rsrc_len = resource_size(&res); - - hcd->regs = devm_ioremap_resource(&op->dev, &res); - if (IS_ERR(hcd->regs)) { - rv = PTR_ERR(hcd->regs); - goto err_rmr; - } - - irq = irq_of_parse_and_map(dn, 0); - if (!irq) { - dev_err(&op->dev, "%s: irq_of_parse_and_map failed\n", - __FILE__); - rv = -EBUSY; - goto err_rmr; - } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = regs; ohci = hcd_to_ohci(hcd); if (is_bigendian) { @@ -158,20 +150,18 @@ static int ohci_hcd_ppc_of_probe(struct platform_device *op) * the ehci driver is loaded. */ if (np != NULL) { - if (!of_address_to_resource(np, 0, &res)) { - if (!request_mem_region(res.start, 0x4, hcd_name)) { + if (!of_address_to_resource(np, 0, &ehci_res)) { + if (!request_mem_region(ehci_res.start, 0x4, hcd_name)) { writel_be((readl_be(&ohci->regs->control) | OHCI_USB_SUSPEND), &ohci->regs->control); (void) readl_be(&ohci->regs->control); } else - release_mem_region(res.start, 0x4); + release_mem_region(ehci_res.start, 0x4); } else pr_debug("%s: cannot get ehci offset from fdt\n", __FILE__); of_node_put(np); } - irq_dispose_mapping(irq); -err_rmr: usb_put_hcd(hcd); return rv; @@ -185,8 +175,6 @@ static void ohci_hcd_ppc_of_remove(struct platform_device *op) usb_remove_hcd(hcd); - irq_dispose_mapping(hcd->irq); - usb_put_hcd(hcd); } From d3c4ac41bf1e7989c773370e55c9784e4282894d Mon Sep 17 00:00:00 2001 From: Lucas Martins Alves Date: Fri, 17 Jul 2026 19:44:53 +0000 Subject: [PATCH 044/158] usb: musb: dsps: implement vbus_status and set_vbus platform ops The DSPS glue layer (used on TI AM335x SoCs) was missing implementations for the vbus_status and set_vbus platform ops defined in struct musb_platform_ops. Add dsps_musb_vbus_status() to report VBUS presence by reading the VBUS field of MUSB_DEVCTL, and dsps_musb_set_vbus() to drive the SESSION bit on MUSB_DEVCTL, enabling or disabling the USB session. Register both callbacks in dsps_ops to allow the MUSB core to control VBUS on AM335x-based platforms. This enables the USB core to perform a VBUS power cycle as a recovery mechanism when a USB error condition is detected, and also allows userspace to control VBUS state via the standard USB sysfs interface. Signed-off-by: Lucas Martins Alves Link: https://patch.msgid.link/20260717194449.317520-1-lucas.alves@lumal21.com.br Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_dsps.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index e3935f18dd56..efd1f0dbb926 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -618,6 +618,29 @@ static int dsps_musb_recover(struct musb *musb) return session_restart ? 0 : -EPIPE; } +static int dsps_musb_vbus_status(struct musb *musb) +{ + u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + return (devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS; +} + +static void dsps_musb_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + devctl |= MUSB_DEVCTL_SESSION; + } else { + musb_root_disconnect(musb); + devctl &= ~MUSB_DEVCTL_SESSION; + } + + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); +} + /* Similar to am35x, dm81xx support only 32-bit read operation */ static void dsps_read_fifo32(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) { @@ -702,6 +725,8 @@ static struct musb_platform_ops dsps_ops = { .set_mode = dsps_musb_set_mode, .recover = dsps_musb_recover, + .vbus_status = dsps_musb_vbus_status, + .set_vbus = dsps_musb_set_vbus, .clear_ep_rxintr = dsps_musb_clear_ep_rxintr, }; From a174da76d3625a5e32f816ee27924ad5d6c1f19f Mon Sep 17 00:00:00 2001 From: Shashank Balaji Date: Wed, 22 Jul 2026 12:31:21 +0900 Subject: [PATCH 045/158] usb: core: pass THIS_MODULE implicitly through a macro Rename usb_register_device_driver() to __usb_register_device_driver() and replace it with a macro wrapper that passes THIS_MODULE implicitly. This is in line with what other buses do. Co-developed-by: Rahul Bukte Signed-off-by: Rahul Bukte Signed-off-by: Shashank Balaji Reviewed-by: Bastien Nocera Link: https://patch.msgid.link/20260722-mod_name_usb-v1-1-96d781530f3f@sony.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/usb/r8152.c | 2 +- drivers/usb/core/driver.c | 6 +++--- drivers/usb/core/usb.c | 2 +- drivers/usb/misc/apple-mfi-fastcharge.c | 2 +- drivers/usb/misc/onboard_usb_dev.c | 2 +- drivers/usb/usbip/stub_main.c | 2 +- include/linux/usb.h | 4 +++- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c index f61686433031..73b9b2af1ed2 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -10495,7 +10495,7 @@ static int __init rtl8152_driver_init(void) { int ret; - ret = usb_register_device_driver(&rtl8152_cfgselector_driver, THIS_MODULE); + ret = usb_register_device_driver(&rtl8152_cfgselector_driver); if (ret) return ret; diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 7f33fe5ba03b..a1cb0e469787 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -984,7 +984,7 @@ bool is_usb_device_driver(const struct device_driver *drv) } /** - * usb_register_device_driver - register a USB device (not interface) driver + * __usb_register_device_driver - register a USB device (not interface) driver * @new_udriver: USB operations for the device driver * @owner: module owner of this driver. * @@ -994,7 +994,7 @@ bool is_usb_device_driver(const struct device_driver *drv) * * Return: A negative error code on failure and 0 on success. */ -int usb_register_device_driver(struct usb_device_driver *new_udriver, +int __usb_register_device_driver(struct usb_device_driver *new_udriver, struct module *owner) { int retval = 0; @@ -1027,7 +1027,7 @@ int usb_register_device_driver(struct usb_device_driver *new_udriver, return retval; } -EXPORT_SYMBOL_GPL(usb_register_device_driver); +EXPORT_SYMBOL_GPL(__usb_register_device_driver); /** * usb_deregister_device_driver - unregister a USB device (not interface) driver diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index df166cafe106..2559a180fdee 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1241,7 +1241,7 @@ static int __init usb_init(void) retval = usb_hub_init(); if (retval) goto hub_init_failed; - retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE); + retval = usb_register_device_driver(&usb_generic_driver); if (!retval) goto out; diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c index af266e19f2fd..02adec28d9e6 100644 --- a/drivers/usb/misc/apple-mfi-fastcharge.c +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -245,7 +245,7 @@ static struct usb_device_driver mfi_fc_driver = { static int __init mfi_fc_driver_init(void) { - return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); + return usb_register_device_driver(&mfi_fc_driver); } static void __exit mfi_fc_driver_exit(void) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 11508ed4df25..c60406240416 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -706,7 +706,7 @@ static int __init onboard_dev_init(void) { int ret; - ret = usb_register_device_driver(&onboard_dev_usbdev_driver, THIS_MODULE); + ret = usb_register_device_driver(&onboard_dev_usbdev_driver); if (ret) return ret; diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c index 79110a69d697..01452f7bdabb 100644 --- a/drivers/usb/usbip/stub_main.c +++ b/drivers/usb/usbip/stub_main.c @@ -371,7 +371,7 @@ static int __init usbip_host_init(void) return -ENOMEM; } - ret = usb_register_device_driver(&stub_driver, THIS_MODULE); + ret = usb_register_device_driver(&stub_driver); if (ret) { pr_err("usb_register failed %d\n", ret); goto err_usb_register; diff --git a/include/linux/usb.h b/include/linux/usb.h index 49ab8dbb885f..9d47bcb945a8 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1369,7 +1369,9 @@ extern void usb_deregister(struct usb_driver *); module_driver(__usb_driver, usb_register, \ usb_deregister) -extern int usb_register_device_driver(struct usb_device_driver *, +#define usb_register_device_driver(new_udriver) \ + __usb_register_device_driver(new_udriver, THIS_MODULE) +extern int __usb_register_device_driver(struct usb_device_driver *, struct module *); extern void usb_deregister_device_driver(struct usb_device_driver *); From b1cef6463484c7f41cedd756ba4f41058b20ca5a Mon Sep 17 00:00:00 2001 From: Shashank Balaji Date: Wed, 22 Jul 2026 12:31:22 +0900 Subject: [PATCH 046/158] usb: core: set mod_name in driver registration The driver core only creates the sysfs "module" symlink for a built-in driver when its struct device_driver has mod_name set (see module_add_driver()). This registration path left mod_name unset, so built-in drivers using it had no such symlink. Set mod_name to KBUILD_MODNAME during driver registration. Built-in drivers now gain the symlink, while loadable modules are unaffected. Co-developed-by: Rahul Bukte Signed-off-by: Rahul Bukte Signed-off-by: Shashank Balaji Reviewed-by: Bastien Nocera Link: https://patch.msgid.link/20260722-mod_name_usb-v1-2-96d781530f3f@sony.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 4 +++- include/linux/usb.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index a1cb0e469787..3c3bbaf89609 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -987,6 +987,7 @@ bool is_usb_device_driver(const struct device_driver *drv) * __usb_register_device_driver - register a USB device (not interface) driver * @new_udriver: USB operations for the device driver * @owner: module owner of this driver. + * @mod_name: module name string * * Registers a USB device driver with the USB core. The list of * unattached devices will be rescanned whenever a new driver is @@ -995,7 +996,7 @@ bool is_usb_device_driver(const struct device_driver *drv) * Return: A negative error code on failure and 0 on success. */ int __usb_register_device_driver(struct usb_device_driver *new_udriver, - struct module *owner) + struct module *owner, const char *mod_name) { int retval = 0; @@ -1007,6 +1008,7 @@ int __usb_register_device_driver(struct usb_device_driver *new_udriver, new_udriver->driver.probe = usb_probe_device; new_udriver->driver.remove = usb_unbind_device; new_udriver->driver.owner = owner; + new_udriver->driver.mod_name = mod_name; new_udriver->driver.dev_groups = new_udriver->dev_groups; retval = driver_register(&new_udriver->driver); diff --git a/include/linux/usb.h b/include/linux/usb.h index 9d47bcb945a8..4377eeae541e 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1370,9 +1370,9 @@ extern void usb_deregister(struct usb_driver *); usb_deregister) #define usb_register_device_driver(new_udriver) \ - __usb_register_device_driver(new_udriver, THIS_MODULE) + __usb_register_device_driver(new_udriver, THIS_MODULE, KBUILD_MODNAME) extern int __usb_register_device_driver(struct usb_device_driver *, - struct module *); + struct module *, const char *); extern void usb_deregister_device_driver(struct usb_device_driver *); extern int usb_register_dev(struct usb_interface *intf, From 07ec27b8821d04f84a471ce4e4b7d62937f8751f Mon Sep 17 00:00:00 2001 From: Shashank Balaji Date: Wed, 22 Jul 2026 12:31:23 +0900 Subject: [PATCH 047/158] usb: typec: set mod_name in driver registration The driver core only creates the sysfs "module" symlink for a built-in driver when its struct device_driver has mod_name set (see module_add_driver()). This driver left mod_name unset, so its built-in form had no such symlink. Set mod_name to KBUILD_MODNAME during driver registration. Built-in drivers now gain the symlink, while loadable modules are unaffected. Co-developed-by: Rahul Bukte Signed-off-by: Rahul Bukte Signed-off-by: Shashank Balaji Acked-by: Heikki Krogerus Reviewed-by: Bastien Nocera Link: https://patch.msgid.link/20260722-mod_name_usb-v1-3-96d781530f3f@sony.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/bus.c | 4 +++- include/linux/usb/typec_altmode.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index e84b134a3381..587d3b06ee8d 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -383,12 +383,14 @@ void typec_altmode_put_plug(struct typec_altmode *plug) EXPORT_SYMBOL_GPL(typec_altmode_put_plug); int __typec_altmode_register_driver(struct typec_altmode_driver *drv, - struct module *module) + struct module *module, + const char *mod_name) { if (!drv->probe) return -EINVAL; drv->driver.owner = module; + drv->driver.mod_name = mod_name; drv->driver.bus = &typec_bus; return driver_register(&drv->driver); diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h index ef21ead551be..92309b569c3a 100644 --- a/include/linux/usb/typec_altmode.h +++ b/include/linux/usb/typec_altmode.h @@ -227,9 +227,9 @@ struct typec_altmode_driver { * handle all SVID specific communication. */ #define typec_altmode_register_driver(drv) \ - __typec_altmode_register_driver(drv, THIS_MODULE) + __typec_altmode_register_driver(drv, THIS_MODULE, KBUILD_MODNAME) int __typec_altmode_register_driver(struct typec_altmode_driver *drv, - struct module *module); + struct module *module, const char *mod_name); /** * typec_altmode_unregister_driver - unregisters a USB Type-C alternate mode * device driver From 844e9e545135cf8b14d7fc579ecb54225e43fc99 Mon Sep 17 00:00:00 2001 From: Shashank Balaji Date: Wed, 22 Jul 2026 12:31:24 +0900 Subject: [PATCH 048/158] usb: ulpi: set mod_name in driver registration The driver core only creates the sysfs "module" symlink for a built-in driver when its struct device_driver has mod_name set (see module_add_driver()). This driver left mod_name unset, so its built-in form had no such symlink. Set mod_name to KBUILD_MODNAME during driver registration. Built-in drivers now gain the symlink, while loadable modules are unaffected. Co-developed-by: Rahul Bukte Signed-off-by: Rahul Bukte Signed-off-by: Shashank Balaji Reviewed-by: Bastien Nocera Link: https://patch.msgid.link/20260722-mod_name_usb-v1-4-96d781530f3f@sony.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/ulpi.c | 4 +++- include/linux/ulpi/driver.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c index 7e43429e996e..dec4baca1c53 100644 --- a/drivers/usb/common/ulpi.c +++ b/drivers/usb/common/ulpi.c @@ -147,15 +147,17 @@ static const struct device_type ulpi_dev_type = { * __ulpi_register_driver - register a driver with the ULPI bus * @drv: driver being registered * @module: ends up being THIS_MODULE + * @mod_name: ends up being KBUILD_MODNAME * * Registers a driver with the ULPI bus. */ -int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module) +int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module, const char *mod_name) { if (!drv->probe) return -EINVAL; drv->driver.owner = module; + drv->driver.mod_name = mod_name; drv->driver.bus = &ulpi_bus; return driver_register(&drv->driver); diff --git a/include/linux/ulpi/driver.h b/include/linux/ulpi/driver.h index c668d9c8d876..c899197e0aa8 100644 --- a/include/linux/ulpi/driver.h +++ b/include/linux/ulpi/driver.h @@ -51,8 +51,8 @@ struct ulpi_driver { /* * use a macro to avoid include chaining to get THIS_MODULE */ -#define ulpi_register_driver(drv) __ulpi_register_driver(drv, THIS_MODULE) -int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module); +#define ulpi_register_driver(drv) __ulpi_register_driver(drv, THIS_MODULE, KBUILD_MODNAME) +int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module, const char *mod_name); void ulpi_unregister_driver(struct ulpi_driver *drv); #define module_ulpi_driver(__ulpi_driver) \ From aa106357b4d8491245c272422237942e1e19a631 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Wed, 29 Jul 2026 13:00:37 +0530 Subject: [PATCH 049/158] dt-bindings: usb: ti,da830-ohci: Convert to DT schema Convert Texas Instruments da830-ohci bindings from legacy text to DT schema. Name of binding is changed to match primary compatible property. Add required property clocks and optional property oc-gpios which were missing from legacy binding. Signed-off-by: Bhargav Joshi Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260729-ti-da830-ohci-v3-1-e30da330780f@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/ohci-da8xx.txt | 23 ------- .../bindings/usb/ti,da830-ohci.yaml | 61 +++++++++++++++++++ 2 files changed, 61 insertions(+), 23 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/ohci-da8xx.txt create mode 100644 Documentation/devicetree/bindings/usb/ti,da830-ohci.yaml diff --git a/Documentation/devicetree/bindings/usb/ohci-da8xx.txt b/Documentation/devicetree/bindings/usb/ohci-da8xx.txt deleted file mode 100644 index 24a826d5015e..000000000000 --- a/Documentation/devicetree/bindings/usb/ohci-da8xx.txt +++ /dev/null @@ -1,23 +0,0 @@ -DA8XX USB OHCI controller - -Required properties: - - - compatible: Should be "ti,da830-ohci" - - reg: Should contain one register range i.e. start and length - - interrupts: Description of the interrupt line - - phys: Phandle for the PHY device - - phy-names: Should be "usb-phy" - -Optional properties: - - vbus-supply: phandle of regulator that controls vbus power / over-current - -Example: - -ohci: usb@225000 { - compatible = "ti,da830-ohci"; - reg = <0x225000 0x1000>; - interrupts = <59>; - phys = <&usb_phy 1>; - phy-names = "usb-phy"; - vbus-supply = <®_usb_ohci>; -}; diff --git a/Documentation/devicetree/bindings/usb/ti,da830-ohci.yaml b/Documentation/devicetree/bindings/usb/ti,da830-ohci.yaml new file mode 100644 index 000000000000..148b9d0d83da --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,da830-ohci.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,da830-ohci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: DA8XX USB OHCI controller + +maintainers: + - Axel Haslam + +allOf: + - $ref: usb-hcd.yaml# + +properties: + compatible: + const: ti,da830-ohci + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + phys: + maxItems: 1 + + clocks: + maxItems: 1 + + phy-names: + const: usb-phy + + vbus-supply: + description: + phandle of regulator that controls vbus power / over-current + + oc-gpios: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - phys + - phy-names + - clocks + +unevaluatedProperties: false + +examples: + - | + usb@225000 { + compatible = "ti,da830-ohci"; + reg = <0x225000 0x1000>; + interrupts = <59>; + phys = <&usb_phy 1>; + phy-names = "usb-phy"; + clocks = <&psc1 2>; + vbus-supply = <®_usb_ohci>; + }; From d937ca39cc44a01ce64865dd25b1e205a05ae3a0 Mon Sep 17 00:00:00 2001 From: Diego Fernando Mancera Gomez Date: Thu, 23 Jul 2026 13:56:54 -0600 Subject: [PATCH 050/158] usb: image: microtek: stop SG processing after transfer errors When an SG URB completes with an error, mts_do_sg() calls mts_transfer_cleanup(), which completes the SCSI command through scsi_done(). However, the callback continues advancing the scatterlist and may submit another URB for a command that has already been completed. Return immediately after the cleanup so that no further processing is performed on the completed command. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Diego Fernando Mancera Gomez Acked-by: Oliver Neukum Link: https://patch.msgid.link/20260723195654.152104-1-diegomancera.dev@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 1f0b3c44d388..787cf5aba55d 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -475,6 +475,7 @@ static void mts_do_sg (struct urb* transfer) if (unlikely(status)) { set_host_byte(context->srb, (status == -ENOENT ? DID_ABORT : DID_ERROR)); mts_transfer_cleanup(transfer); + return; } context->curr_sg = sg_next(context->curr_sg); From 0c6cae0f56239e3274b30dbe5b362cd297ccacc7 Mon Sep 17 00:00:00 2001 From: "Jiangong.Han" Date: Sat, 1 Aug 2026 17:51:28 +0800 Subject: [PATCH 051/158] USB: usbmon: fix null-ptr-deref in mon_bus_remove() when mon_bus_init() fails When mon_bus_init() fails to allocate the mon_bus struct, it returns without setting ubus->mon_bus, so mbus->mon_bus remains NULL. mon_bus_add() intentionally ignores this failure because the USB core does not require monitoring to be established for normal operation. However, when the HCD is later removed, usb_remove_hcd() fires the USB_BUS_REMOVE notifier which calls mon_bus_remove(), it dereferences ubus->mon_bus without checking for NULL, causing a null-ptr deference. Fix this by adding a NULL check for mbus at the top of mon_bus_remove(). If mon_bus_init() failed, ubus->mon_bus is NULL, meaning the bus was never tracked by the monitor, so there is nothing to clean up and returning early is correct. Reported-by: syzbot+5f4b29d9fe710a964482@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5f4b29d9fe710a964482 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Jiangong.Han Link: https://patch.msgid.link/20260801095128.46322-1-jiangong.han@windriver.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mon/mon_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/mon/mon_main.c b/drivers/usb/mon/mon_main.c index e4fff194fea0..600c9de84c7e 100644 --- a/drivers/usb/mon/mon_main.c +++ b/drivers/usb/mon/mon_main.c @@ -199,6 +199,9 @@ static void mon_bus_remove(struct usb_bus *ubus) { struct mon_bus *mbus = ubus->mon_bus; + if (mbus == NULL) + return; + mutex_lock(&mon_lock); list_del(&mbus->bus_link); if (mbus->text_inited) From f19023e1e59364b363fc37555811e70b3a46f9da Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:47 +0200 Subject: [PATCH 052/158] USB: OHCI: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260801191002.1383835-1-Julia.Lawall@inria.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-dbg.c | 2 +- drivers/usb/host/ohci-hub.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 23dc9eddc06c..d945737f0196 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -184,7 +184,7 @@ ohci_dump_status (struct ohci_hcd *controller, char **next, unsigned *size) \ (temp & RH_PS_PES) ? " PES" : "", \ (temp & RH_PS_CCS) ? " CCS" : "" \ - ); + ) static void diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index b3d734ab6201..84d10a932942 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -33,7 +33,7 @@ \ (value & RH_PS_PES) ? " PES" : "", \ (value & RH_PS_CCS) ? " CCS" : "" \ - ); + ) /*-------------------------------------------------------------------------*/ From e93731c29f749d5b2495a67109d1f0804ab3b89d Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 6 Aug 2026 22:43:04 -0400 Subject: [PATCH 053/158] USB: pci-quirks: Release EHCI device reference for_each_pci_dev() holds a reference to the current device. The Intel EHCI lookup breaks as soon as it finds a match, so the iterator does not get a chance to drop that reference on its next iteration. Release the reference before leaving the loop. Fixes: 26b76798e050 ("Intel xhci: refactor EHCI/xHCI port switching") Signed-off-by: Yuho Choi Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260807024304.928708-1-dbgh9129@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/pci-quirks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 0404489c2f6a..ba158d63bb8b 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -1073,6 +1073,7 @@ void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev) if (companion->class == PCI_CLASS_SERIAL_USB_EHCI && companion->vendor == PCI_VENDOR_ID_INTEL) { ehci_found = true; + pci_dev_put(companion); break; } } From 9165204c17a38f599713e22087404e32faf1aa7c Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Sat, 22 Aug 2026 02:01:16 +0800 Subject: [PATCH 054/158] usb: storage: sddr09: fix OOB access in sddr09_read_map sddr09_read_map() builds the LBA <-> PBA translation tables while servicing READ_CAPACITY. The logical block address assigned to each physical block is decoded from device-controlled redundancy data: lba = short_pack(ptr[7], ptr[6]); /* 16-bit device value */ lba = (lba & 0x07FF) >> 1; /* 0..1023 */ if (lba >= 1000) goto possibly_erase; lba += 1000*(i/0x400); if (info->lba_to_pba[lba] != UNDEF) /* heap OOB read */ ... info->pba_to_lba[i] = lba; info->lba_to_pba[lba] = i; /* heap OOB write */ The tables are allocated with numblocks entries each, where numblocks is derived from the device-reported NAND chip geometry. For the smallest chip in nand_flash_ids[] (1 MB: chipshift=20, pageshift=8, blockshift=4): numblocks = (1 << 20) >> (8 + 4) = 256 so info->lba_to_pba[] only has indexes 0..255, while a hostile device may report any LBA up to 999 - the "lba >= 1000" check is the only limit on the value, and there is no check that lba < numblocks. On a 1 MB card indexes 256..999 (744 of them) index the table out of bounds, up to ~3 KB (744 * 4 bytes) past the end of the allocation. Attack chain (malicious USB storage device): 1. An attacker presents a USB Mass Storage device spoofing a unit matched in sddr09_usb_ids, e.g. 0x04e6:0x0003 (SanDisk ImageMate SDDR09) or 0x0781:0x0200, on the victim USB bus; the device is enumerated as a normal storage device. 2. ums-sddr09 binds (sddr09_probe -> us->transport = sddr09_transport) and the SCSI layer issues READ_CAPACITY, which is handled via sddr09_get_cardinfo() (chip geometry from the device ID, choosing numblocks) and sddr09_read_map(). 3. The device reports a 1 MB chip (numblocks = 256) and fills the per-block redundancy data with LBA values in the 256..999 range, driving info->lba_to_pba[lba] and info->pba_to_lba[i] accesses out of bounds: a heap OOB read used in the map-building conditionals plus a heap OOB write of the loop index i (the physical block number, 0..255) at an offset the attacker controls via the reported LBA, corrupting adjacent heap memory. The device-controlled inputs, the missing bound check and the OOB indexing are confirmed by end-to-end reproduction with a FunctionFS based malicious device emulator; the driver logged out-of-bounds indexes such as: sddr09: LBA 256 seen for PBA -858993460 and 201 sddr09: LBA 258 seen for PBA 4513 and 203 Add the missing bounds check: since lba is unsigned it can only be too large, so bail out with "lba >= numblocks" and mark the physical block UNUSABLE instead of indexing the tables. This mirrors the max_lba bounds checks already applied to the SCSI-address-derived LBA in sddr09_read_data()/sddr09_write_data() and in the related sddr55 and alauda drivers. Signed-off-by: Haofeng Li Suggested-by: Alan Stern Assisted-by: opencode:deepseek-v4-flash-free Acked-by: Alan Stern Link: https://patch.msgid.link/20260821180116.3490884-1-lihaofeng@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/sddr09.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c index 3d45e1b54c66..313313508bde 100644 --- a/drivers/usb/storage/sddr09.c +++ b/drivers/usb/storage/sddr09.c @@ -1339,6 +1339,15 @@ sddr09_read_map(struct us_data *us) { lba += 1000*(i/0x400); + /* The LBA comes from the card and may exceed the table size */ + if (lba >= numblocks) { + dev_warn_ratelimited(&us->pusb_dev->dev, + "sddr09: Bad LBA %d for block %d exceeds the translation table size %d\n", + lba, i, numblocks); + info->pba_to_lba[i] = UNUSABLE; + continue; + } + if (info->lba_to_pba[lba] != UNDEF) { printk(KERN_WARNING "sddr09: LBA %d seen for PBA %d and %d\n", From 2542613815381e090d5036d5751ab5e2fd9f8b07 Mon Sep 17 00:00:00 2001 From: Jacob Riff Date: Mon, 24 Aug 2026 10:35:36 -0700 Subject: [PATCH 055/158] usb: typec: ucsi: retry init when the PPM answers early commands incorrectly On some platforms the PPM is not ready to answer commands correctly for a short window during boot. On the Lenovo ThinkPad X1 Carbon Gen 14 (21V7, tested on BIOS 1.12 and 1.14) roughly half of all boots fail ucsi_init() with either -ENODEV (GET_CAPABILITY completes but reports zero connectors) or -EINVAL (a standard command is rejected, logged as "possible UCSI driver bug"). The failure is not a timeout: increasing the sync command completion wait does not change the rate. Since ucsi_init_work() only requeues on -EPROBE_DEFER, a single bad answer during that window leaves UCSI dead for the whole session, so Type-C events are never handled again; most visibly, the machine silently never resumes charging after the charger is replugged. Manually reloading ucsi_acpi a few seconds later has succeeded on every attempt observed, which suggests simply retrying is enough. Retry -ENODEV and -EINVAL the same way as the role switch wait, log the retries at debug level, keep the loud report for the case where the retries are exhausted, and note when init only succeeded after retrying. Tested on the affected machine: across 8 consecutive boots with this patch, 5 hit the failure (matching the historical ~50-60% rate) and all 5 recovered on the first retry ("PPM init succeeded after 2 attempts"). 0 of 8 boots ended with UCSI unusable, where ~5 of 8 would have without the patch. Signed-off-by: Jacob Riff Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260824173536.2395830-1-jacob@riff.dk Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index bef3f9b71d71..706a5667e496 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -2212,18 +2212,36 @@ static void ucsi_init_work(struct work_struct *work) int ret; ret = ucsi_init(ucsi); - if (ret) - dev_err_probe(ucsi->dev, ret, "PPM init failed\n"); + if (!ret) { + if (ucsi->work_count) + dev_info(ucsi->dev, + "PPM init succeeded after %u attempts\n", + ucsi->work_count + 1); + return; + } - if (ret == -EPROBE_DEFER) { - if (ucsi->work_count++ > UCSI_ROLE_SWITCH_WAIT_COUNT) { - dev_err(ucsi->dev, "PPM init failed, stop trying\n"); + /* + * On some platforms the PPM is not ready to answer commands + * correctly for a short window during boot: standard commands are + * rejected or GET_CAPABILITY reports zero connectors, seen as + * -EINVAL or -ENODEV from ucsi_init(), and a retry moments later + * succeeds (observed on Lenovo ThinkPad X1 Carbon Gen 14, where + * this affects roughly half of all boots). Retry those like the + * USB role switch wait instead of giving up on the first attempt. + */ + if (ret == -EPROBE_DEFER || ret == -ENODEV || ret == -EINVAL) { + if (ucsi->work_count++ < UCSI_ROLE_SWITCH_WAIT_COUNT) { + dev_dbg(ucsi->dev, "PPM init failed (%pe), retrying\n", + ERR_PTR(ret)); + queue_delayed_work(system_dfl_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); return; } - - queue_delayed_work(system_dfl_long_wq, &ucsi->work, - UCSI_ROLE_SWITCH_INTERVAL); + dev_err(ucsi->dev, "PPM init failed, stop trying\n"); + return; } + + dev_err_probe(ucsi->dev, ret, "PPM init failed\n"); } /** From d0e95f1c08264bc11f84f6acd65d03e8b1773c08 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 1 Sep 2026 04:53:05 +0530 Subject: [PATCH 056/158] dt-bindings: dma: ti,cppi41: Convert to DT schema Convert TI CPPI 4.1 DMA controller text bindings to DT schema. Add ti,am3359-cppi41 and ti,da830-cppi41 into single schema since they share the same hardware IP block and driver. Add undocumented #dma-requests and power-domains properties to match existing DTS usage. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Link: https://patch.msgid.link/20260901-ti-am33-usb-v3-1-5e15c617d2e9@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/dma/ti/ti,cppi41.yaml | 109 ++++++++++++++++++ .../devicetree/bindings/usb/am33xx-usb.txt | 14 --- .../devicetree/bindings/usb/da8xx-usb.txt | 12 -- 3 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 Documentation/devicetree/bindings/dma/ti/ti,cppi41.yaml diff --git a/Documentation/devicetree/bindings/dma/ti/ti,cppi41.yaml b/Documentation/devicetree/bindings/dma/ti/ti,cppi41.yaml new file mode 100644 index 000000000000..a8c28e8ada07 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/ti/ti,cppi41.yaml @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/ti/ti,cppi41.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI CPPI41 DMA Controller + +maintainers: + - Sebastian Andrzej Siewior + +properties: + compatible: + enum: + - ti,am3359-cppi41 + - ti,da830-cppi41 + + reg: + minItems: 3 + maxItems: 4 + + reg-names: + minItems: 3 + maxItems: 4 + + interrupts: + maxItems: 1 + + interrupt-names: + const: glue + + "#dma-cells": + const: 2 + + dma-channels: + description: Number of DMA channels supported. + $ref: /schemas/types.yaml#/definitions/uint32 + + dma-requests: + description: Number of DMA requests supported. + $ref: /schemas/types.yaml#/definitions/uint32 + + power-domains: + maxItems: 1 + + "#dma-channels": + description: Deprecated, use dma-channels instead. + deprecated: true + + "#dma-requests": + description: Deprecated, use dma-requests instead. + deprecated: true + +required: + - compatible + - reg + - reg-names + - "#dma-cells" + - dma-channels + +allOf: + - $ref: /schemas/dma/dma-controller.yaml# + - if: + properties: + compatible: + contains: + const: ti,am3359-cppi41 + then: + properties: + reg: + minItems: 4 + reg-names: + items: + - const: glue + - const: controller + - const: scheduler + - const: queuemgr + power-domains: false + required: + - interrupt-names + - interrupts + - dma-requests + else: + properties: + reg: + maxItems: 3 + reg-names: + items: + - const: controller + - const: scheduler + - const: queuemgr + +unevaluatedProperties: false + +examples: + - | + dma-controller@7402000 { + compatible = "ti,am3359-cppi41"; + reg = <0x47400000 0x1000>, + <0x47402000 0x1000>, + <0x47403000 0x1000>, + <0x47404000 0x4000>; + reg-names = "glue", "controller", "scheduler", "queuemgr"; + interrupts = <17>; + interrupt-names = "glue"; + #dma-cells = <2>; + dma-channels = <30>; + dma-requests = <256>; + }; diff --git a/Documentation/devicetree/bindings/usb/am33xx-usb.txt b/Documentation/devicetree/bindings/usb/am33xx-usb.txt index 654ffc62d013..8431de346129 100644 --- a/Documentation/devicetree/bindings/usb/am33xx-usb.txt +++ b/Documentation/devicetree/bindings/usb/am33xx-usb.txt @@ -51,20 +51,6 @@ USB The controller should have an "usb" alias numbered properly in the alias node. -DMA -~~~ -- compatible: ti,am3359-cppi41 -- reg: offset and length of the following register spaces: USBSS, USB - CPPI DMA Controller, USB CPPI DMA Scheduler, USB Queue Manager -- reg-names: glue, controller, scheduler, queuemgr -- #dma-cells: should be set to 2. The first number represents the - endpoint number (0 … 14 for endpoints 1 … 15 on instance 0 and 15 … 29 - for endpoints 1 … 15 on instance 1). The second number is 0 for RX and - 1 for TX transfers. -- dma-channels: should be set to 30 representing the 15 endpoints for - each USB instance. -- #dma-channels: deprecated - Example: ~~~~~~~~ The following example contains all the nodes as used on am335x-evm: diff --git a/Documentation/devicetree/bindings/usb/da8xx-usb.txt b/Documentation/devicetree/bindings/usb/da8xx-usb.txt index fb2027a7d80d..00320d92d3bb 100644 --- a/Documentation/devicetree/bindings/usb/da8xx-usb.txt +++ b/Documentation/devicetree/bindings/usb/da8xx-usb.txt @@ -27,18 +27,6 @@ Optional properties: ~~~~~~~~~~~~~~~~~~~~ - vbus-supply: Phandle to a regulator providing the USB bus power. -DMA -~~~ -- compatible: ti,da830-cppi41 -- reg: offset and length of the following register spaces: CPPI DMA Controller, - CPPI DMA Scheduler, Queue Manager -- reg-names: "controller", "scheduler", "queuemgr" -- #dma-cells: should be set to 2. The first number represents the - channel number (0 … 3 for endpoints 1 … 4). - The second number is 0 for RX and 1 for TX transfers. -- dma-channels: should be set to 4 representing the 4 endpoints. -- #dma-channels: deprecated - Example: usb_phy: usb-phy { compatible = "ti,da830-usb-phy"; From 267e31adc4831b7c59daa88869e78a08c64af25b Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 1 Sep 2026 04:53:06 +0530 Subject: [PATCH 057/158] dt-bindings: phy: ti,am335x-usb-phy: Convert to DT schema Convert the TI AM335x USB PHY text bindings to DT schema. Add undocumented vcc-supply property to match existing DTS usage. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Acked-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260901-ti-am33-usb-v3-2-5e15c617d2e9@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/phy/ti,am335x-usb-phy.yaml | 49 +++++++++++++++++++ .../devicetree/bindings/usb/am33xx-usb.txt | 9 ---- 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/ti,am335x-usb-phy.yaml diff --git a/Documentation/devicetree/bindings/phy/ti,am335x-usb-phy.yaml b/Documentation/devicetree/bindings/phy/ti,am335x-usb-phy.yaml new file mode 100644 index 000000000000..b28cd42ece5e --- /dev/null +++ b/Documentation/devicetree/bindings/phy/ti,am335x-usb-phy.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/ti,am335x-usb-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI AM335x USB PHY + +maintainers: + - Sebastian Andrzej Siewior + +properties: + compatible: + const: ti,am335x-usb-phy + + reg: + maxItems: 1 + + reg-names: + const: phy + + ti,ctrl_mod: + $ref: /schemas/types.yaml#/definitions/phandle + description: Reference to the reset module node. + + "#phy-cells": + const: 0 + + vcc-supply: + description: Phandle to the regulator that provides power to the PHY. + +required: + - compatible + - reg + - reg-names + - ti,ctrl_mod + - "#phy-cells" + +additionalProperties: false + +examples: + - | + usb-phy@47401300 { + compatible = "ti,am335x-usb-phy"; + reg = <0x47401300 0x100>; + reg-names = "phy"; + ti,ctrl_mod = <&ctrl_mod>; + #phy-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/usb/am33xx-usb.txt b/Documentation/devicetree/bindings/usb/am33xx-usb.txt index 8431de346129..8832843c7d7c 100644 --- a/Documentation/devicetree/bindings/usb/am33xx-usb.txt +++ b/Documentation/devicetree/bindings/usb/am33xx-usb.txt @@ -17,15 +17,6 @@ Reset module - reg-names: "phy_ctrl" for the "USB control registers" and "wakeup" for the USB wake up control register. -USB PHY -~~~~~~~ -compatible: ti,am335x-usb-phy -reg: offset and length of the "USB PHY" register space -ti,ctrl_mod: reference to the "reset module" node -reg-names: phy -The PHY should have a "phy" alias numbered properly in the alias -node. - USB ~~~ - compatible: ti,musb-am33xx From 1c09fe2edad271cb0e1e8c71c2e66a414be7ece8 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 1 Sep 2026 04:53:07 +0530 Subject: [PATCH 058/158] dt-bindings: usb: ti,musb-am33xx: Convert to DT schema Convert the TI AM33xx MUSB text bindings to DT schema. Changes during conversion: - Allow an optional vbus interrupt used in am335x-bone-common. - Add the interface-type and phy-names properties natively used by the dm816x hardware. - Allow 0 or 1 for mentor,multipoint. While the text binding mandated 1, the Mentor USB IP natively supports both single and multipoint configurations as used in existing DTS. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Link: https://patch.msgid.link/20260901-ti-am33-usb-v3-3-5e15c617d2e9@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/am33xx-usb.txt | 25 ---- .../bindings/usb/ti,musb-am33xx.yaml | 113 ++++++++++++++++++ 2 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/ti,musb-am33xx.yaml diff --git a/Documentation/devicetree/bindings/usb/am33xx-usb.txt b/Documentation/devicetree/bindings/usb/am33xx-usb.txt index 8832843c7d7c..5973f88bcc65 100644 --- a/Documentation/devicetree/bindings/usb/am33xx-usb.txt +++ b/Documentation/devicetree/bindings/usb/am33xx-usb.txt @@ -17,31 +17,6 @@ Reset module - reg-names: "phy_ctrl" for the "USB control registers" and "wakeup" for the USB wake up control register. -USB -~~~ -- compatible: ti,musb-am33xx -- reg: offset and length of "USB Controller Registers", and offset and - length of "USB Core" register space. -- reg-names: control for the ""USB Controller Registers" and "mc" for - "USB Core" register space -- interrupts: USB interrupt number -- interrupt-names: mc -- dr_mode: Should be one of "host", "peripheral" or "otg". -- mentor,multipoint: Should be "1" indicating the musb controller supports - multipoint. This is a MUSB configuration-specific setting. -- mentor,num-eps: Specifies the number of endpoints. This is also a - MUSB configuration-specific setting. Should be set to "16" -- mentor,ram-bits: Specifies the ram address size. Should be set to "12" -- mentor,power: Should be "500". This signifies the controller can supply up to - 500mA when operating in host mode. -- phys: reference to the USB phy -- dmas: specifies the dma channels -- dma-names: specifies the names of the channels. Use "rxN" for receive - and "txN" for transmit endpoints. N specifies the endpoint number. - -The controller should have an "usb" alias numbered properly in the alias -node. - Example: ~~~~~~~~ The following example contains all the nodes as used on am335x-evm: diff --git a/Documentation/devicetree/bindings/usb/ti,musb-am33xx.yaml b/Documentation/devicetree/bindings/usb/ti,musb-am33xx.yaml new file mode 100644 index 000000000000..cc01edc75d11 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,musb-am33xx.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,musb-am33xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI AM33xx/DM816 MUSB + +maintainers: + - Ravi B + - Ajay Kumar Gupta + +allOf: + - $ref: /schemas/usb/usb-drd.yaml# + +properties: + compatible: + enum: + - ti,musb-am33xx + - ti,musb-dm816 + + reg: + maxItems: 2 + + reg-names: + items: + - const: mc + - const: control + + interrupts: + minItems: 1 + maxItems: 2 + + interrupt-names: + minItems: 1 + items: + - const: mc + - const: vbus + + mentor,multipoint: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Indicates the musb controller supports multipoint. + enum: [0, 1] + + mentor,num-eps: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Specifies the number of endpoints. + const: 16 + + mentor,ram-bits: + description: Specifies the ram address size. + const: 12 + + mentor,power: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Controller supply capacity (in mA) when operating in host mode. + const: 500 + + phys: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 30 + + dma-names: + minItems: 1 + maxItems: 30 + items: + pattern: "^(rx|tx)([1-9]|1[0-5])$" + + phy-names: + const: usb2-phy + + interface-type: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Describes the type of interface between the controller and the PHY. + enum: [ 0, 1 ] + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - dr_mode + - mentor,multipoint + - mentor,num-eps + - mentor,ram-bits + - mentor,power + - phys + +unevaluatedProperties: false + +examples: + - | + usb@47401000 { + compatible = "ti,musb-am33xx"; + reg = <0x47401400 0x400>, + <0x47401000 0x200>; + reg-names = "mc", "control"; + interrupts = <18>; + interrupt-names = "mc"; + dr_mode = "otg"; + mentor,multipoint = <1>; + mentor,num-eps = <16>; + mentor,ram-bits = <12>; + mentor,power = <500>; + phys = <&usb0_phy>; + dmas = <&cppi41dma 0 0 &cppi41dma 1 0>; + dma-names = "rx1", "tx1"; + }; + From 185bb9305937f5c8718d989139fee7b3aea8683a Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 1 Sep 2026 04:53:08 +0530 Subject: [PATCH 059/158] dt-bindings: usb: ti,am335x-usb-ctrl-module: Convert to DT schema Convert the TI AM335x USB Control Module text bindings to DT schema. No functional changes. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Link: https://patch.msgid.link/20260901-ti-am33-usb-v3-4-5e15c617d2e9@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/am33xx-usb.txt | 9 ----- .../usb/ti,am335x-usb-ctrl-module.yaml | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/ti,am335x-usb-ctrl-module.yaml diff --git a/Documentation/devicetree/bindings/usb/am33xx-usb.txt b/Documentation/devicetree/bindings/usb/am33xx-usb.txt index 5973f88bcc65..94312ad8ec8c 100644 --- a/Documentation/devicetree/bindings/usb/am33xx-usb.txt +++ b/Documentation/devicetree/bindings/usb/am33xx-usb.txt @@ -8,15 +8,6 @@ The glue layer contains multiple child nodes. It is required to have at least a control module node, USB node and a PHY node. The second USB node and its PHY node are optional. The DMA node is also optional. -Reset module -~~~~~~~~~~~~ -- compatible: ti,am335x-usb-ctrl-module -- reg: offset and length of the "USB control registers" in the "Control - Module" block. A second offset and length for the USB wake up control - in the same memory block. -- reg-names: "phy_ctrl" for the "USB control registers" and "wakeup" for - the USB wake up control register. - Example: ~~~~~~~~ The following example contains all the nodes as used on am335x-evm: diff --git a/Documentation/devicetree/bindings/usb/ti,am335x-usb-ctrl-module.yaml b/Documentation/devicetree/bindings/usb/ti,am335x-usb-ctrl-module.yaml new file mode 100644 index 000000000000..f8ac0af745c6 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,am335x-usb-ctrl-module.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,am335x-usb-ctrl-module.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI AM335x USB Control Module + +maintainers: + - Sebastian Andrzej Siewior + +properties: + compatible: + const: ti,am335x-usb-ctrl-module + + reg: + maxItems: 2 + + reg-names: + items: + - const: phy_ctrl + - const: wakeup + +required: + - compatible + - reg + - reg-names + +additionalProperties: false + +examples: + - | + control@44e10000 { + compatible = "ti,am335x-usb-ctrl-module"; + reg = <0x44e10620 0x10>, + <0x44e10648 0x4>; + reg-names = "phy_ctrl", "wakeup"; + }; From ef7e123401d95103be70a6705526594ecc883603 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 1 Sep 2026 04:53:09 +0530 Subject: [PATCH 060/158] dt-bindings: usb: ti,am33xx-usb: Convert to DT schema Convert the TI AM33xx USB Wrapper text binding to DT schema. Changes during conversion: - Drop the USB control module from child nodes, as it physically resides within the System Control Module address space, not the USB wrapper. - Allow standard phy@[0-9a-f]+$ child node names alongside the legacy usb-phy@[0-9a-f]+$. - Mark the legacy ti,hwmods property as deprecated. - Document required #address-cells, #size-cells, and ranges property missing from text binding. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Link: https://patch.msgid.link/20260901-ti-am33-usb-v3-5-5e15c617d2e9@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/am33xx-usb.txt | 143 ------------------ .../bindings/usb/ti,am33xx-usb.yaml | 103 +++++++++++++ 2 files changed, 103 insertions(+), 143 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/am33xx-usb.txt create mode 100644 Documentation/devicetree/bindings/usb/ti,am33xx-usb.yaml diff --git a/Documentation/devicetree/bindings/usb/am33xx-usb.txt b/Documentation/devicetree/bindings/usb/am33xx-usb.txt deleted file mode 100644 index 94312ad8ec8c..000000000000 --- a/Documentation/devicetree/bindings/usb/am33xx-usb.txt +++ /dev/null @@ -1,143 +0,0 @@ - AM33xx MUSB -~~~~~~~~~~~~~~~ -- compatible: ti,am33xx-usb -- reg: offset and length of the usbss register sets -- ti,hwmods : must be "usb_otg_hs" - -The glue layer contains multiple child nodes. It is required to have -at least a control module node, USB node and a PHY node. The second USB -node and its PHY node are optional. The DMA node is also optional. - -Example: -~~~~~~~~ -The following example contains all the nodes as used on am335x-evm: - -aliases { - usb0 = &usb0; - usb1 = &usb1; - phy0 = &usb0_phy; - phy1 = &usb1_phy; -}; - -usb: usb@47400000 { - compatible = "ti,am33xx-usb"; - reg = <0x47400000 0x1000>; - ranges; - #address-cells = <1>; - #size-cells = <1>; - ti,hwmods = "usb_otg_hs"; - - ctrl_mod: control@44e10000 { - compatible = "ti,am335x-usb-ctrl-module"; - reg = <0x44e10620 0x10 - 0x44e10648 0x4>; - reg-names = "phy_ctrl", "wakeup"; - }; - - usb0_phy: usb-phy@47401300 { - compatible = "ti,am335x-usb-phy"; - reg = <0x47401300 0x100>; - reg-names = "phy"; - ti,ctrl_mod = <&ctrl_mod>; - #phy-cells = <0>; - }; - - usb0: usb@47401000 { - compatible = "ti,musb-am33xx"; - reg = <0x47401400 0x400 - 0x47401000 0x200>; - reg-names = "mc", "control"; - - interrupts = <18>; - interrupt-names = "mc"; - dr_mode = "otg" - mentor,multipoint = <1>; - mentor,num-eps = <16>; - mentor,ram-bits = <12>; - mentor,power = <500>; - phys = <&usb0_phy>; - - dmas = <&cppi41dma 0 0 &cppi41dma 1 0 - &cppi41dma 2 0 &cppi41dma 3 0 - &cppi41dma 4 0 &cppi41dma 5 0 - &cppi41dma 6 0 &cppi41dma 7 0 - &cppi41dma 8 0 &cppi41dma 9 0 - &cppi41dma 10 0 &cppi41dma 11 0 - &cppi41dma 12 0 &cppi41dma 13 0 - &cppi41dma 14 0 &cppi41dma 0 1 - &cppi41dma 1 1 &cppi41dma 2 1 - &cppi41dma 3 1 &cppi41dma 4 1 - &cppi41dma 5 1 &cppi41dma 6 1 - &cppi41dma 7 1 &cppi41dma 8 1 - &cppi41dma 9 1 &cppi41dma 10 1 - &cppi41dma 11 1 &cppi41dma 12 1 - &cppi41dma 13 1 &cppi41dma 14 1>; - dma-names = - "rx1", "rx2", "rx3", "rx4", "rx5", "rx6", "rx7", - "rx8", "rx9", "rx10", "rx11", "rx12", "rx13", - "rx14", "rx15", - "tx1", "tx2", "tx3", "tx4", "tx5", "tx6", "tx7", - "tx8", "tx9", "tx10", "tx11", "tx12", "tx13", - "tx14", "tx15"; - }; - - usb1_phy: usb-phy@47401b00 { - compatible = "ti,am335x-usb-phy"; - reg = <0x47401b00 0x100>; - reg-names = "phy"; - ti,ctrl_mod = <&ctrl_mod>; - #phy-cells = <0>; - }; - - usb1: usb@47401800 { - compatible = "ti,musb-am33xx"; - reg = <0x47401c00 0x400 - 0x47401800 0x200>; - reg-names = "mc", "control"; - interrupts = <19>; - interrupt-names = "mc"; - dr_mode = "host" - mentor,multipoint = <1>; - mentor,num-eps = <16>; - mentor,ram-bits = <12>; - mentor,power = <500>; - phys = <&usb1_phy>; - - dmas = <&cppi41dma 15 0 &cppi41dma 16 0 - &cppi41dma 17 0 &cppi41dma 18 0 - &cppi41dma 19 0 &cppi41dma 20 0 - &cppi41dma 21 0 &cppi41dma 22 0 - &cppi41dma 23 0 &cppi41dma 24 0 - &cppi41dma 25 0 &cppi41dma 26 0 - &cppi41dma 27 0 &cppi41dma 28 0 - &cppi41dma 29 0 &cppi41dma 15 1 - &cppi41dma 16 1 &cppi41dma 17 1 - &cppi41dma 18 1 &cppi41dma 19 1 - &cppi41dma 20 1 &cppi41dma 21 1 - &cppi41dma 22 1 &cppi41dma 23 1 - &cppi41dma 24 1 &cppi41dma 25 1 - &cppi41dma 26 1 &cppi41dma 27 1 - &cppi41dma 28 1 &cppi41dma 29 1>; - dma-names = - "rx1", "rx2", "rx3", "rx4", "rx5", "rx6", "rx7", - "rx8", "rx9", "rx10", "rx11", "rx12", "rx13", - "rx14", "rx15", - "tx1", "tx2", "tx3", "tx4", "tx5", "tx6", "tx7", - "tx8", "tx9", "tx10", "tx11", "tx12", "tx13", - "tx14", "tx15"; - }; - - cppi41dma: dma-controller@7402000 { - compatible = "ti,am3359-cppi41"; - reg = <0x47400000 0x1000 - 0x47402000 0x1000 - 0x47403000 0x1000 - 0x47404000 0x4000>; - reg-names = "glue", "controller", "scheduler", "queuemgr"; - interrupts = <17>; - interrupt-names = "glue"; - #dma-cells = <2>; - dma-channels = <30>; - dma-requests = <256>; - }; -}; diff --git a/Documentation/devicetree/bindings/usb/ti,am33xx-usb.yaml b/Documentation/devicetree/bindings/usb/ti,am33xx-usb.yaml new file mode 100644 index 000000000000..0b36b9ddfb4a --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,am33xx-usb.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,am33xx-usb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI AM33xx USB Wrapper + +description: + The glue layer contains multiple child nodes. It is required to have at least + a USB node and a PHY node. The second USB node and its PHY node are optional. + The DMA node is also optional. + +maintainers: + - Sebastian Andrzej Siewior + +properties: + compatible: + const: ti,am33xx-usb + + reg: + maxItems: 1 + + ranges: true + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + ti,hwmods: + $ref: /schemas/types.yaml#/definitions/string + description: Name of the hwmod associated with the USB module. + const: usb_otg_hs + deprecated: true + +patternProperties: + "^(usb-)?phy@[0-9a-f]+$": + $ref: /schemas/phy/ti,am335x-usb-phy.yaml# + + "^usb@[0-9a-f]+$": + $ref: /schemas/usb/ti,musb-am33xx.yaml# + + "^dma-controller@[0-9a-f]+$": + $ref: /schemas/dma/ti/ti,cppi41.yaml# + +required: + - compatible + - reg + - ranges + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + usb@47400000 { + compatible = "ti,am33xx-usb"; + reg = <0x47400000 0x1000>; + ranges; + #address-cells = <1>; + #size-cells = <1>; + ti,hwmods = "usb_otg_hs"; + + usb-phy@47401300 { + compatible = "ti,am335x-usb-phy"; + reg = <0x47401300 0x100>; + reg-names = "phy"; + ti,ctrl_mod = <&ctrl_mod>; + #phy-cells = <0>; + }; + + usb@47401000 { + compatible = "ti,musb-am33xx"; + reg = <0x47401400 0x400>, + <0x47401000 0x200>; + reg-names = "mc", "control"; + interrupts = <18>; + interrupt-names = "mc"; + dr_mode = "otg"; + mentor,multipoint = <1>; + mentor,num-eps = <16>; + mentor,ram-bits = <12>; + mentor,power = <500>; + phys = <&usb0_phy>; + }; + + dma-controller@7402000 { + compatible = "ti,am3359-cppi41"; + reg = <0x47400000 0x1000>, + <0x47402000 0x1000>, + <0x47403000 0x1000>, + <0x47404000 0x4000>; + reg-names = "glue", "controller", "scheduler", "queuemgr"; + interrupts = <17>; + interrupt-names = "glue"; + #dma-cells = <2>; + dma-channels = <30>; + dma-requests = <256>; + }; + }; From 40d79fbfa9ab8ecdcd39df81f05f320acd8897d5 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 1 Sep 2026 04:53:10 +0530 Subject: [PATCH 061/158] dt-bindings: usb: ti,da830-musb: Convert to DT schema Convert the TI DA8xx/OMAP-L1x MUSB text bindings to DT schema. Since DA8xx MUSB node acts as a parent bus for the CPPI41 DMA controller add required #address-cells, #size-cells, and ranges properties. Reviewed-by: Rob Herring (Arm) Signed-off-by: Bhargav Joshi Link: https://patch.msgid.link/20260901-ti-am33-usb-v3-6-5e15c617d2e9@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/da8xx-usb.txt | 69 ----------- .../bindings/usb/ti,da830-musb.yaml | 113 ++++++++++++++++++ 2 files changed, 113 insertions(+), 69 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/da8xx-usb.txt create mode 100644 Documentation/devicetree/bindings/usb/ti,da830-musb.yaml diff --git a/Documentation/devicetree/bindings/usb/da8xx-usb.txt b/Documentation/devicetree/bindings/usb/da8xx-usb.txt deleted file mode 100644 index 00320d92d3bb..000000000000 --- a/Documentation/devicetree/bindings/usb/da8xx-usb.txt +++ /dev/null @@ -1,69 +0,0 @@ -TI DA8xx MUSB -~~~~~~~~~~~~~ -For DA8xx/OMAP-L1x/AM17xx/AM18xx platforms. - -Required properties: -~~~~~~~~~~~~~~~~~~~~ - - compatible : Should be set to "ti,da830-musb". - - - reg: Offset and length of the USB controller register set. - - - interrupts: The USB interrupt number. - - - interrupt-names: Should be set to "mc". - - - dr_mode: The USB operation mode. Should be one of "host", "peripheral" or "otg". - - - phys: Phandle for the PHY device - - - phy-names: Should be "usb-phy" - - - dmas: specifies the dma channels - - - dma-names: specifies the names of the channels. Use "rxN" for receive - and "txN" for transmit endpoints. N specifies the endpoint number. - -Optional properties: -~~~~~~~~~~~~~~~~~~~~ - - vbus-supply: Phandle to a regulator providing the USB bus power. - -Example: - usb_phy: usb-phy { - compatible = "ti,da830-usb-phy"; - #phy-cells = <0>; - }; - usb0: usb@200000 { - compatible = "ti,da830-musb"; - reg = <0x00200000 0x1000>; - ranges; - #address-cells = <1>; - #size-cells = <1>; - interrupts = <58>; - interrupt-names = "mc"; - - dr_mode = "host"; - vbus-supply = <&usb_vbus>; - phys = <&usb_phy 0>; - phy-names = "usb-phy"; - - dmas = <&cppi41dma 0 0 &cppi41dma 1 0 - &cppi41dma 2 0 &cppi41dma 3 0 - &cppi41dma 0 1 &cppi41dma 1 1 - &cppi41dma 2 1 &cppi41dma 3 1>; - dma-names = - "rx1", "rx2", "rx3", "rx4", - "tx1", "tx2", "tx3", "tx4"; - - - cppi41dma: dma-controller@201000 { - compatible = "ti,da830-cppi41"; - reg = <0x201000 0x1000 - 0x202000 0x1000 - 0x204000 0x4000>; - reg-names = "controller", "scheduler", "queuemgr"; - interrupts = <58>; - #dma-cells = <2>; - dma-channels = <4>; - }; - - }; diff --git a/Documentation/devicetree/bindings/usb/ti,da830-musb.yaml b/Documentation/devicetree/bindings/usb/ti,da830-musb.yaml new file mode 100644 index 000000000000..fefae735b34d --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,da830-musb.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,da830-musb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI DA8xx MUSB + +maintainers: + - Bin Liu + +allOf: + - $ref: /schemas/usb/usb-drd.yaml# + +properties: + compatible: + const: ti,da830-musb + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-names: + const: mc + + phys: + maxItems: 1 + + phy-names: + const: usb-phy + + dmas: + minItems: 1 + maxItems: 8 + + dma-names: + minItems: 1 + maxItems: 8 + items: + pattern: "^(rx|tx)[1-4]$" + + vbus-supply: + description: Phandle to a regulator providing the USB bus power. + + clocks: + maxItems: 1 + + clock-ranges: true + + ranges: true + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + +patternProperties: + "^dma-controller@[0-9a-f]+$": + $ref: /schemas/dma/ti/ti,cppi41.yaml# + +required: + - compatible + - reg + - interrupts + - interrupt-names + - phys + - phy-names + - ranges + - "#address-cells" + - "#size-cells" + +unevaluatedProperties: false + +examples: + - | + usb@200000 { + compatible = "ti,da830-musb"; + reg = <0x00200000 0x1000>; + ranges; + #address-cells = <1>; + #size-cells = <1>; + interrupts = <58>; + interrupt-names = "mc"; + + dr_mode = "host"; + vbus-supply = <&usb_vbus>; + phys = <&usb_phy 0>; + phy-names = "usb-phy"; + + clocks = <&psc1 1>; + clock-ranges; + + dmas = <&cppi41dma 0 0>, <&cppi41dma 1 0>, + <&cppi41dma 2 0>, <&cppi41dma 3 0>, + <&cppi41dma 0 1>, <&cppi41dma 1 1>, + <&cppi41dma 2 1>, <&cppi41dma 3 1>; + dma-names = "rx1", "rx2", "rx3", "rx4", + "tx1", "tx2", "tx3", "tx4"; + + dma-controller@201000 { + compatible = "ti,da830-cppi41"; + reg = <0x201000 0x1000>, + <0x202000 0x1000>, + <0x204000 0x4000>; + reg-names = "controller", "scheduler", "queuemgr"; + interrupts = <58>; + #dma-cells = <2>; + dma-channels = <4>; + }; + }; From ec67dacb75ebd11fbe6d727e6f37c0ef5e3d3431 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Tue, 1 Sep 2026 15:58:29 +0900 Subject: [PATCH 062/158] usb-storage: alauda: do not do DMA from the stack alauda_check_status2(), alauda_get_redu_data(), alauda_erase_block(), alauda_read_block_raw() and alauda_write_block() build their commands in stack arrays and pass them to usb_stor_bulk_transfer_buf(). alauda_check_status2() and alauda_erase_block() also read the reply back into a stack array. usb_stor_msg_common() sets URB_NO_TRANSFER_DMA_MAP only when the buffer is us->iobuf. Every other buffer reaches usb_hcd_map_urb_for_dma(), which has warned and returned -EAGAIN for a buffer on the stack since commit 4568136620c6 ("usb: core: Check URB setup_packet and transfer_buffer sanity"). On a host controller that uses DMA, all of these commands fail. Use us->iobuf, as alauda_reset_media() in this driver and the other usb-storage subdrivers already do. The command is sent before the reply is read, so both can use it; sddr09_read_status() does the same. Fixes: e80b0fade09e ("[PATCH] USB Storage: add alauda support") Link: https://bugzilla.kernel.org/show_bug.cgi?id=217862 Signed-off-by: Arka Mondal Acked-by: Alan Stern Link: https://patch.msgid.link/20260901065831.43567-2-arka@arkamondal.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/alauda.c | 70 ++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c index 691fe47009cf..878c404106c5 100644 --- a/drivers/usb/storage/alauda.c +++ b/drivers/usb/storage/alauda.c @@ -500,11 +500,14 @@ static int alauda_check_media(struct us_data *us) static int alauda_check_status2(struct us_data *us) { int rc; - unsigned char command[] = { - ALAUDA_BULK_CMD, ALAUDA_BULK_GET_STATUS2, - 0, 0, 0, 0, 3, 0, MEDIA_PORT(us) - }; - unsigned char data[3]; + unsigned char *command = us->iobuf; + unsigned char *data = us->iobuf; + + memset(command, 0, 9); + command[0] = ALAUDA_BULK_CMD; + command[1] = ALAUDA_BULK_GET_STATUS2; + command[6] = 3; + command[8] = MEDIA_PORT(us); rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, command, 9, NULL); @@ -530,10 +533,15 @@ static int alauda_check_status2(struct us_data *us) static int alauda_get_redu_data(struct us_data *us, u16 pba, unsigned char *data) { int rc; - unsigned char command[] = { - ALAUDA_BULK_CMD, ALAUDA_BULK_GET_REDU_DATA, - PBA_HI(pba), PBA_ZONE(pba), 0, PBA_LO(pba), 0, 0, MEDIA_PORT(us) - }; + unsigned char *command = us->iobuf; + + memset(command, 0, 9); + command[0] = ALAUDA_BULK_CMD; + command[1] = ALAUDA_BULK_GET_REDU_DATA; + command[2] = PBA_HI(pba); + command[3] = PBA_ZONE(pba); + command[5] = PBA_LO(pba); + command[8] = MEDIA_PORT(us); rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, command, 9, NULL); @@ -702,14 +710,20 @@ static void alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone) static int alauda_erase_block(struct us_data *us, u16 pba) { int rc; - unsigned char command[] = { - ALAUDA_BULK_CMD, ALAUDA_BULK_ERASE_BLOCK, PBA_HI(pba), - PBA_ZONE(pba), 0, PBA_LO(pba), 0x02, 0, MEDIA_PORT(us) - }; - unsigned char buf[2]; + unsigned char *command = us->iobuf; + unsigned char *buf = us->iobuf; usb_stor_dbg(us, "Erasing PBA %d\n", pba); + memset(command, 0, 9); + command[0] = ALAUDA_BULK_CMD; + command[1] = ALAUDA_BULK_ERASE_BLOCK; + command[2] = PBA_HI(pba); + command[3] = PBA_ZONE(pba); + command[5] = PBA_LO(pba); + command[6] = 0x02; + command[8] = MEDIA_PORT(us); + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, command, 9, NULL); if (rc != USB_STOR_XFER_GOOD) @@ -732,13 +746,19 @@ static int alauda_read_block_raw(struct us_data *us, u16 pba, unsigned int page, unsigned int pages, unsigned char *data) { int rc; - unsigned char command[] = { - ALAUDA_BULK_CMD, ALAUDA_BULK_READ_BLOCK, PBA_HI(pba), - PBA_ZONE(pba), 0, PBA_LO(pba) + page, pages, 0, MEDIA_PORT(us) - }; + unsigned char *command = us->iobuf; usb_stor_dbg(us, "pba %d page %d count %d\n", pba, page, pages); + memset(command, 0, 9); + command[0] = ALAUDA_BULK_CMD; + command[1] = ALAUDA_BULK_READ_BLOCK; + command[2] = PBA_HI(pba); + command[3] = PBA_ZONE(pba); + command[5] = PBA_LO(pba) + page; + command[6] = pages; + command[8] = MEDIA_PORT(us); + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, command, 9, NULL); if (rc != USB_STOR_XFER_GOOD) @@ -783,13 +803,19 @@ static int alauda_write_block(struct us_data *us, u16 pba, unsigned char *data) { int rc; struct alauda_info *info = (struct alauda_info *) us->extra; - unsigned char command[] = { - ALAUDA_BULK_CMD, ALAUDA_BULK_WRITE_BLOCK, PBA_HI(pba), - PBA_ZONE(pba), 0, PBA_LO(pba), 32, 0, MEDIA_PORT(us) - }; + unsigned char *command = us->iobuf; usb_stor_dbg(us, "pba %d\n", pba); + memset(command, 0, 9); + command[0] = ALAUDA_BULK_CMD; + command[1] = ALAUDA_BULK_WRITE_BLOCK; + command[2] = PBA_HI(pba); + command[3] = PBA_ZONE(pba); + command[5] = PBA_LO(pba); + command[6] = 32; + command[8] = MEDIA_PORT(us); + rc = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, command, 9, NULL); if (rc != USB_STOR_XFER_GOOD) From eb882419790c6710646904c6b25132189152cf33 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Tue, 1 Sep 2026 15:58:30 +0900 Subject: [PATCH 063/158] usb-storage: alauda: check the return value of alauda_read_map() alauda_ensure_map_for_zone() ignores the value alauda_read_map() returns. On failure lba_to_pba[zone] and pba_to_lba[zone] are left NULL, and both callers dereference them immediately: pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset]; alauda_read_map() returns USB_STOR_TRANSPORT_ERROR when either kcalloc() fails or an alauda_get_redu_data() transfer fails, so one failed bulk transfer is enough to reach the NULL dereference. Return that value from alauda_ensure_map_for_zone() and check it in alauda_read_data() and alauda_write_lba(). Fixes: e80b0fade09e ("[PATCH] USB Storage: add alauda support") Link: https://bugzilla.kernel.org/show_bug.cgi?id=217862 Signed-off-by: Arka Mondal Acked-by: Alan Stern Link: https://patch.msgid.link/20260901065831.43567-3-arka@arkamondal.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/alauda.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c index 878c404106c5..d47ce01d519a 100644 --- a/drivers/usb/storage/alauda.c +++ b/drivers/usb/storage/alauda.c @@ -697,11 +697,13 @@ static int alauda_read_map(struct us_data *us, unsigned int zone) * Checks to see whether we have already mapped a certain zone * If we haven't, the map is generated */ -static void alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone) +static int alauda_ensure_map_for_zone(struct us_data *us, unsigned int zone) { if (MEDIA_INFO(us).lba_to_pba[zone] == NULL || MEDIA_INFO(us).pba_to_lba[zone] == NULL) - alauda_read_map(us, zone); + return alauda_read_map(us, zone); + + return 0; } /* @@ -849,7 +851,9 @@ static int alauda_write_lba(struct us_data *us, u16 lba, unsigned int new_pba_offset; unsigned int zone = lba / uzonesize; - alauda_ensure_map_for_zone(us, zone); + result = alauda_ensure_map_for_zone(us, zone); + if (result != USB_STOR_TRANSPORT_GOOD) + return result; pba = MEDIA_INFO(us).lba_to_pba[zone][lba_offset]; if (pba == 1) { @@ -980,7 +984,10 @@ static int alauda_read_data(struct us_data *us, unsigned long address, unsigned int lba_offset = lba - (zone * uzonesize); unsigned int pages; u16 pba; - alauda_ensure_map_for_zone(us, zone); + + result = alauda_ensure_map_for_zone(us, zone); + if (result != USB_STOR_TRANSPORT_GOOD) + break; /* Not overflowing capacity? */ if (lba >= max_lba) { From c4542993c071fcafe7335698bf52650dee7e3555 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Tue, 1 Sep 2026 15:58:31 +0900 Subject: [PATCH 064/158] usb-storage: alauda: fix out-of-bounds zone index MEDIA_INFO(us).lba_to_pba and .pba_to_lba are arrays of num_zones pointers, allocated by kcalloc() in alauda_init_media(). alauda_ensure_map_for_zone() reads lba_to_pba[zone] and pba_to_lba[zone], and when either is NULL alauda_read_map() writes a pointer back to both. Two separate errors let zone reach num_zones, one element past the end. max_lba is capacity >> (blockshift + pageshift), which counts physical blocks, but zone is lba / uzonesize and uzonesize is 125/128 of zonesize. On a 16 MB card (alauda_card_ids id 0x73) num_zones is 1 and uzonesize is 1000, so lba 1000 to 1023 pass the bounds check and index element 1 of a one-element array. alauda_read_data() also calls alauda_ensure_map_for_zone() before it compares lba against max_lba, so any larger lba is used as an index before the check rejects it. alauda_write_data() already checks first. Bound lba by num_zones * uzonesize on both paths and move the read side's call below the check. alauda_transport() reports num_zones * uzonesize * blocksize for READ_CAPACITY, so the new bound is exactly the range the device advertises and no block becomes unreachable. sd never issues an lba past the reported capacity; an SG_IO READ_10 can. Fixes: e80b0fade09e ("[PATCH] USB Storage: add alauda support") Suggested-by: Alan Stern Signed-off-by: Arka Mondal Acked-by: Alan Stern Link: https://patch.msgid.link/20260901065831.43567-4-arka@arkamondal.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/alauda.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c index d47ce01d519a..5e7f99ba8ac0 100644 --- a/drivers/usb/storage/alauda.c +++ b/drivers/usb/storage/alauda.c @@ -949,6 +949,7 @@ static int alauda_read_data(struct us_data *us, unsigned long address, unsigned char *buffer; u16 lba, max_lba; unsigned int page, len, offset; + unsigned int num_zones; unsigned int blockshift = MEDIA_INFO(us).blockshift; unsigned int pageshift = MEDIA_INFO(us).pageshift; unsigned int blocksize = MEDIA_INFO(us).blocksize; @@ -973,7 +974,9 @@ static int alauda_read_data(struct us_data *us, unsigned long address, /* Figure out the initial LBA and page */ lba = address >> blockshift; page = (address & MEDIA_INFO(us).blockmask); - max_lba = MEDIA_INFO(us).capacity >> (blockshift + pageshift); + num_zones = MEDIA_INFO(us).capacity >> (MEDIA_INFO(us).zoneshift + + blockshift + pageshift); + max_lba = num_zones * uzonesize; result = USB_STOR_TRANSPORT_GOOD; offset = 0; @@ -985,10 +988,6 @@ static int alauda_read_data(struct us_data *us, unsigned long address, unsigned int pages; u16 pba; - result = alauda_ensure_map_for_zone(us, zone); - if (result != USB_STOR_TRANSPORT_GOOD) - break; - /* Not overflowing capacity? */ if (lba >= max_lba) { usb_stor_dbg(us, "Error: Requested lba %u exceeds maximum %u\n", @@ -997,6 +996,10 @@ static int alauda_read_data(struct us_data *us, unsigned long address, break; } + result = alauda_ensure_map_for_zone(us, zone); + if (result != USB_STOR_TRANSPORT_GOOD) + break; + /* Find number of pages we can read in this block */ pages = min(sectors, blocksize - page); len = pages << pageshift; @@ -1052,6 +1055,7 @@ static int alauda_write_data(struct us_data *us, unsigned long address, unsigned int pagesize = MEDIA_INFO(us).pagesize; struct scatterlist *sg; u16 lba, max_lba; + unsigned int num_zones; int result; /* @@ -1078,7 +1082,9 @@ static int alauda_write_data(struct us_data *us, unsigned long address, /* Figure out the initial LBA and page */ lba = address >> blockshift; page = (address & MEDIA_INFO(us).blockmask); - max_lba = MEDIA_INFO(us).capacity >> (pageshift + blockshift); + num_zones = MEDIA_INFO(us).capacity >> (MEDIA_INFO(us).zoneshift + + blockshift + pageshift); + max_lba = num_zones * MEDIA_INFO(us).uzonesize; result = USB_STOR_TRANSPORT_GOOD; offset = 0; From 26bf31c2c9fcd58f60496f0c3cd47a3b482f7ca1 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 09:56:09 +0200 Subject: [PATCH 065/158] docs: usbmon: Fix a typo in the example struct ubsmon_packet does not exist; the name is struct usbmon_packet. Use struct usbmon_packet. Fixes: 6f23ee1fefdc ("USB: add binary API to usbmon") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260905075609.32879-1-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/usbmon.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/usb/usbmon.rst b/Documentation/usb/usbmon.rst index 6d5ec1e62d09..667b84d3ae56 100644 --- a/Documentation/usb/usbmon.rst +++ b/Documentation/usb/usbmon.rst @@ -361,7 +361,7 @@ Then, execute a loop similar to the one written in pseudo-code below:: ioctl(fd, MON_IOCX_MFETCH, &fetch); // Process errors, too nflush = fetch.nfetch; // This many packets to flush when done 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 == '@') // Filler packet continue; caddr_t data = &mmap_area[vec[i]] + 64; From a2463e239443d7a132624700153f0cb1fab13b2c Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Fri, 4 Sep 2026 23:41:23 +0000 Subject: [PATCH 066/158] usb: typec: ucsi: allow retries of ucsi_resume_work On resume, the UCSI driver will re-enable notifications by sending SET_NOTIFICATION_ENABLE. The LPM/PPM may be busy during system resume causing SET_NOTIFICATION_ENABLE to fail and preventing the UCSI driver from receiving connection status changes. Change resume work to a delayed workqueue to allow retries on failed SET_NOTIFICATION_ENABLED commands. Additionally, cancel resume work on suspend to prevent pending resume commands from impacting suspend. Signed-off-by: Jameson Thies Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Acked-by: Marco Crivellari Link: https://patch.msgid.link/20260904234124.2336922-1-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 18 +++++++++++++----- drivers/usb/typec/ucsi/ucsi.h | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 706a5667e496..577e764a18e7 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -2158,7 +2158,7 @@ static int ucsi_init(struct ucsi *ucsi) static void ucsi_resume_work(struct work_struct *work) { - struct ucsi *ucsi = container_of(work, struct ucsi, resume_work); + struct ucsi *ucsi = container_of(to_delayed_work(work), struct ucsi, resume_work); struct ucsi_connector *con; u64 command; int ret; @@ -2167,6 +2167,11 @@ static void ucsi_resume_work(struct work_struct *work) command = UCSI_SET_NOTIFICATION_ENABLE | ucsi->ntfy; ret = ucsi_send_command(ucsi, command, NULL, 0); if (ret < 0) { + if (++ucsi->resume_retries < 5) { + queue_delayed_work(system_dfl_long_wq, &ucsi->resume_work, + msecs_to_jiffies(500)); + return; + } dev_err(ucsi->dev, "failed to re-enable notifications (%d)\n", ret); return; } @@ -2187,6 +2192,7 @@ int ucsi_suspend(struct ucsi *ucsi) * EC is stopped for suspend; state is re-read on resume. */ cancel_delayed_work_sync(&ucsi->work); + cancel_delayed_work_sync(&ucsi->resume_work); if (!ucsi->connector) return 0; @@ -2200,8 +2206,10 @@ EXPORT_SYMBOL_GPL(ucsi_suspend); int ucsi_resume(struct ucsi *ucsi) { - if (ucsi->connector) - queue_work(system_long_wq, &ucsi->resume_work); + if (ucsi->connector) { + ucsi->resume_retries = 0; + queue_delayed_work(system_dfl_long_wq, &ucsi->resume_work, 0); + } return 0; } EXPORT_SYMBOL_GPL(ucsi_resume); @@ -2317,7 +2325,7 @@ struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops) if (!ucsi) return ERR_PTR(-ENOMEM); - INIT_WORK(&ucsi->resume_work, ucsi_resume_work); + INIT_DELAYED_WORK(&ucsi->resume_work, ucsi_resume_work); INIT_DELAYED_WORK(&ucsi->work, ucsi_init_work); mutex_init(&ucsi->ppm_lock); init_completion(&ucsi->complete); @@ -2383,7 +2391,7 @@ void ucsi_unregister(struct ucsi *ucsi) /* Make sure that we are not in the middle of driver initialization */ cancel_delayed_work_sync(&ucsi->work); - cancel_work_sync(&ucsi->resume_work); + cancel_delayed_work_sync(&ucsi->resume_work); ucsi_debugfs_unregister(ucsi); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index dc594388dcd0..1996f04ed5e6 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -484,7 +484,8 @@ struct ucsi { struct ucsi_connector *connector; struct ucsi_debugfs_entry *debugfs; - struct work_struct resume_work; + struct delayed_work resume_work; + int resume_retries; struct delayed_work work; int work_count; #define UCSI_ROLE_SWITCH_RETRY_PER_HZ 10 From bb4f62bfaa3fe099071390dcb4d13b61b710a58b Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Wed, 9 Sep 2026 15:19:10 +0530 Subject: [PATCH 067/158] Revert "usb: dwc3: qcom: Add support to skip phy management by USB core" This reverts commit be7b1c68cd3e4fc0f5a4e1b85696de1052f21f2f. Commit be7b1c68cd3e ("usb: dwc3: qcom: Add support to skip phy management by USB core") is causing a regression on qualcomm platforms. During role switch it tends to cause the following crash: [ 5.620951] refcount_t: underflow; use-after-free. [ 5.621114] Call trace: [ 5.621115] refcount_warn_saturate+0xd8/0x140 (P) [ 5.621117] kobject_put+0x11c/0x230 [ 5.621121] software_node_notify_remove+0xdc/0xf8 [ 5.621124] device_del+0x1dc/0x328 [ 5.621126] usb_disconnect+0x1d8/0x348 [ 5.621129] usb_remove_hcd+0x100/0x2a8 [ 5.621131] xhci_plat_remove+0x8c/0x170 [ 5.621134] platform_remove+0x28/0x40 [ 5.621135] device_release_driver_internal+0x174/0x290 [ 5.621138] device_release_driver+0x20/0x38 [ 5.621140] bus_remove_device+0x19c/0x1f8 [ 5.621142] device_del+0x1c4/0x328 [ 5.621143] platform_device_unregister+0x34/0xc0 [ 5.621145] dwc3_host_exit+0x50/0x70 [ 5.621146] __dwc3_set_mode+0xa4/0x378 [ 5.644535] Unable to handle kernel paging request at virtual address [ 5.644657] Workqueue: events_freezable __dwc3_set_mode [ 5.644664] pc : __pi_strcmp+0x9c/0x140 [ 5.644668] lr : software_node_property_present+0x60/0x98 [ 5.644697] Call trace: [ 5.644698] __pi_strcmp+0x9c/0x140 (P) [ 5.644700] device_property_present+0x9c/0xc0 [ 5.644703] dwc3_gadget_init+0x230/0x7b0 [ 5.644704] __dwc3_set_mode+0x314/0x378 [ 5.644707] process_scheduled_works+0x1b8/0x538 [ 5.644710] worker_thread+0x1fc/0x2f8 [ 5.644711] kthread+0x114/0x148 [ 5.644713] ret_from_fork+0x10/0x20 Revert skipping of usb core phy management for Qualcomm platforms to avoid the above issues. Reported-by: Val Packett Closes: https://lore.kernel.org/all/f9926203-ee69-4e18-b6c7-95261ba10807@packett.cool/ Signed-off-by: Krishna Kurapati Acked-by: Thinh Nguyen Tested-by: Shawn Guo Link: https://patch.msgid.link/20260909-xhci-fixes-revert-v1-1-7cc97fa0f307@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index ba26e61df915..ac68b4218b56 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -19,7 +19,6 @@ #include #include #include -#include #include "core.h" #include "glue.h" @@ -420,16 +419,6 @@ static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) return IRQ_HANDLED; } -static int dwc3_qcom_set_swnode(struct device *dev) -{ - const struct property_entry props[] = { - PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"), - {} - }; - - return device_create_managed_software_node(dev, props, NULL); -} - static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) { /* Configure dwc3 to use UTMI clock as PIPE clock not present */ @@ -694,10 +683,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); - ret = dwc3_qcom_set_swnode(dev); - if (ret) - goto clk_disable; - qcom->mode = usb_get_dr_mode(dev); if (qcom->mode == USB_DR_MODE_HOST) { From b3a40b09af68107577a33d48de87009e6235b4fa Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:17:38 +0200 Subject: [PATCH 068/158] usb: ehci: support non-standard port status registers Some EHCI implementations place the port status registers outside the standard operational register layout. Add a per-controller port status base and use a helper for all PORTSC accesses, while preserving the standard location by default. Gate the alternate member and lookup behind the hidden USB_EHCI_DEVIANT_PORT_STATUS_REG option. This avoids growing struct ehci_hcd or adding a runtime branch on systems using the standard port status register location. Suggested-by: Alan Stern Suggested-by: Daniel Palmer Assisted-by: LLM Signed-off-by: Linus Walleij Acked-by: Alan Stern Link: https://patch.msgid.link/20260903-gemini-usb-fotg2-v3-1-dd92ecf5675b@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 4 ++++ drivers/usb/host/ehci-dbg.c | 3 +-- drivers/usb/host/ehci-hcd.c | 7 +++---- drivers/usb/host/ehci-hub.c | 35 +++++++++++++++++------------------ drivers/usb/host/ehci.h | 16 ++++++++++++++++ 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 10cff12a3f31..af4fc14b8783 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -211,6 +211,10 @@ config USB_EHCI_PCI depends on USB_PCI default y +config USB_EHCI_DEVIANT_PORT_STATUS_REG + bool + # Used for hosts with a deviant port status register location + config XPS_USB_HCD_XILINX bool "Use Xilinx usb host EHCI controller core" depends on (PPC32 || MICROBLAZE) diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 3cf743c81eb4..51ec6ea43158 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -869,8 +869,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf) for (i = 1; i <= HCS_N_PORTS(ehci->hcs_params); i++) { temp = dbg_port_buf(scratch, sizeof(scratch), label, i, - ehci_readl(ehci, - &ehci->regs->port_status[i - 1])); + ehci_readl(ehci, ehci_portsc(ehci, i - 1))); temp = scnprintf(next, size, fmt, temp, scratch); size -= temp; next += temp; diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 3c46bb18c7f3..19cc7611d86d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -325,7 +325,7 @@ static void ehci_turn_off_all_ports(struct ehci_hcd *ehci) ehci_port_power(ehci, port, false); spin_lock_irq(&ehci->lock); ehci_writel(ehci, PORT_RWC_BITS, - &ehci->regs->port_status[port]); + ehci_portsc(ehci, port)); } } @@ -811,8 +811,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* leverage per-port change bits feature */ if (!(ppcd & (1 << i))) continue; - pstatus = ehci_readl(ehci, - &ehci->regs->port_status[i]); + pstatus = ehci_readl(ehci, ehci_portsc(ehci, i)); if (pstatus & PORT_OWNER) continue; @@ -1109,7 +1108,7 @@ static void ehci_remove_device(struct usb_hcd *hcd, struct usb_device *udev) /* Clear wakeup signal locked in zhaoxin platform when device plug in. */ static void ehci_zx_wakeup_clear(struct ehci_hcd *ehci) { - u32 __iomem *reg = &ehci->regs->port_status[4]; + u32 __iomem *reg = ehci_portsc(ehci, 4); u32 t1 = ehci_readl(ehci, reg); t1 &= (u32)~0xf0000; diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 1aee392e8492..77d0df41f45f 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -55,7 +55,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) port = HCS_N_PORTS(ehci->hcs_params); while (port--) { if (test_bit(port, &ehci->owned_ports)) { - reg = &ehci->regs->port_status[port]; + reg = ehci_portsc(ehci, port); status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; if (!(status & PORT_POWER)) ehci_port_power(ehci, port, true); @@ -69,7 +69,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) port = HCS_N_PORTS(ehci->hcs_params); while (port--) { if (test_bit(port, &ehci->owned_ports)) { - reg = &ehci->regs->port_status[port]; + reg = ehci_portsc(ehci, port); status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; /* Port already owned by companion? */ @@ -106,7 +106,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) * but if something went wrong the port must not * remain enabled. */ - reg = &ehci->regs->port_status[port]; + reg = ehci_portsc(ehci, port); status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; if (status & PORT_OWNER) ehci_writel(ehci, status | PORT_CSC, reg); @@ -137,7 +137,7 @@ static int ehci_port_change(struct ehci_hcd *ehci) */ while (i--) - if (ehci_readl(ehci, &ehci->regs->port_status[i]) & PORT_CSC) + if (ehci_readl(ehci, ehci_portsc(ehci, i)) & PORT_CSC) return 1; return 0; @@ -175,7 +175,7 @@ void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, port = HCS_N_PORTS(ehci->hcs_params); while (port--) { - u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 __iomem *reg = ehci_portsc(ehci, port); u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; u32 t2 = t1 & ~PORT_WAKE_BITS; @@ -254,7 +254,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) fs_idle_delay = false; port = HCS_N_PORTS(ehci->hcs_params); while (port--) { - u32 __iomem *reg = &ehci->regs->port_status [port]; + u32 __iomem *reg = ehci_portsc(ehci, port); u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; u32 t2 = t1 & ~PORT_WAKE_BITS; @@ -426,7 +426,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) */ i = HCS_N_PORTS(ehci->hcs_params); while (i--) { - temp = ehci_readl(ehci, &ehci->regs->port_status[i]); + temp = ehci_readl(ehci, ehci_portsc(ehci, i)); if ((temp & PORT_PE) && !(temp & (PORT_SUSPEND | PORT_RESUME))) { ehci_dbg(ehci, "Port status(0x%x) is wrong\n", temp); @@ -463,14 +463,14 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); while (i--) { - temp = ehci_readl(ehci, &ehci->regs->port_status [i]); + temp = ehci_readl(ehci, ehci_portsc(ehci, i)); temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); if (test_bit(i, &ehci->bus_suspended) && (temp & PORT_SUSPEND)) { temp |= PORT_RESUME; set_bit(i, &resume_needed); } - ehci_writel(ehci, temp, &ehci->regs->port_status [i]); + ehci_writel(ehci, temp, ehci_portsc(ehci, i)); } /* @@ -487,10 +487,10 @@ static int ehci_bus_resume (struct usb_hcd *hcd) i = HCS_N_PORTS (ehci->hcs_params); while (i--) { - temp = ehci_readl(ehci, &ehci->regs->port_status [i]); + temp = ehci_readl(ehci, ehci_portsc(ehci, i)); if (test_bit(i, &resume_needed)) { temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME); - ehci_writel(ehci, temp, &ehci->regs->port_status [i]); + ehci_writel(ehci, temp, ehci_portsc(ehci, i)); } } @@ -540,7 +540,7 @@ static void set_owner(struct ehci_hcd *ehci, int portnum, int new_owner) u32 port_status; int try; - status_reg = &ehci->regs->port_status[portnum]; + status_reg = ehci_portsc(ehci, portnum); /* * The controller won't set the OWNER bit if the port is @@ -661,7 +661,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) for (i = 0; i < ports; i++) { /* leverage per-port change bits feature */ if (ppcd & (1 << i)) - temp = ehci_readl(ehci, &ehci->regs->port_status[i]); + temp = ehci_readl(ehci, ehci_portsc(ehci, i)); else temp = 0; @@ -753,7 +753,7 @@ int ehci_hub_control( temp = (wIndex - 1) & 0xff; if (temp >= HCS_N_PORTS_MAX) temp = 0; - status_reg = &ehci->regs->port_status[temp]; + status_reg = ehci_portsc(ehci, temp); hostpc_reg = &ehci->regs->hostpc[temp]; /* @@ -1149,8 +1149,7 @@ int ehci_hub_control( /* Put all enabled ports into suspend */ while (ports--) { - u32 __iomem *sreg = - &ehci->regs->port_status[ports]; + u32 __iomem *sreg = ehci_portsc(ehci, ports); temp = ehci_readl(ehci, sreg) & ~PORT_RWC_BITS; if (temp & PORT_PE) @@ -1200,14 +1199,14 @@ static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum) if (ehci_is_TDI(ehci)) return 0; - reg = &ehci->regs->port_status[portnum - 1]; + reg = ehci_portsc(ehci, portnum - 1); return ehci_readl(ehci, reg) & PORT_OWNER; } static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable) { struct usb_hcd *hcd = ehci_to_hcd(ehci); - u32 __iomem *status_reg = &ehci->regs->port_status[portnum]; + u32 __iomem *status_reg = ehci_portsc(ehci, portnum); u32 temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS; if (enable) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index d7a3c8d13f6b..a65fba092e76 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -123,6 +123,9 @@ struct ehci_hcd { /* one per controller */ struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; struct ehci_dbg_port __iomem *debug; +#ifdef CONFIG_USB_EHCI_DEVIANT_PORT_STATUS_REG + u32 __iomem *port_status; +#endif __u32 hcs_params; /* cached register copy */ spinlock_t lock; @@ -278,6 +281,19 @@ static inline struct usb_hcd *ehci_to_hcd(struct ehci_hcd *ehci) /*-------------------------------------------------------------------------*/ +static inline u32 __iomem *ehci_portsc(struct ehci_hcd *ehci, + unsigned int port) +{ +#ifdef CONFIG_USB_EHCI_DEVIANT_PORT_STATUS_REG + if (ehci->port_status) + return ehci->port_status + port; +#endif + + return &ehci->regs->port_status[port]; +} + +/*-------------------------------------------------------------------------*/ + #define QTD_NEXT(ehci, dma) cpu_to_hc32(ehci, (u32)dma) /* From d6bda2cc30a91cf942031e2957ac440ae5c5e6d0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:17:39 +0200 Subject: [PATCH 069/158] usb: ehci: add port reset hooks Some EHCI implementations need controller-specific sequencing around port reset. Add optional hooks for reset preparation and completion. Controllers without the hooks retain the existing behavior. Gate the callbacks behind the hidden USB_EHCI_PORT_RESET_HOOKS option so controllers using standard reset sequencing incur no extra state or runtime checks. Suggested-by: Alan Stern Suggested-by: Daniel Palmer Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-usb-fotg2-v3-2-dd92ecf5675b@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 4 ++++ drivers/usb/host/ehci-hub.c | 16 ++++++++++++++++ drivers/usb/host/ehci.h | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index af4fc14b8783..a0c66191d03c 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -215,6 +215,10 @@ config USB_EHCI_DEVIANT_PORT_STATUS_REG bool # Used for hosts with a deviant port status register location +config USB_EHCI_PORT_RESET_HOOKS + bool + # Used for hosts with controller-specific port reset sequencing + config XPS_USB_HCD_XILINX bool "Use Xilinx usb host EHCI controller core" depends on (PPC32 || MICROBLAZE) diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 77d0df41f45f..5aa629a92542 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -963,6 +963,13 @@ int ehci_hub_control( /* see what we found out */ temp = check_reset_complete (ehci, wIndex, status_reg, ehci_readl(ehci, status_reg)); +#ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS + if (ehci->post_port_reset) { + retval = ehci->post_port_reset(ehci, wIndex); + if (retval) + goto error_exit; + } +#endif } /* transfer dedicated ports to the companion hc */ @@ -1121,7 +1128,16 @@ int ehci_hub_control( */ if (ehci_has_fsl_hs_errata(ehci)) temp |= (1 << PORTSC_FSL_PFSC); + +#ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS + if (ehci->pre_port_reset) { + retval = ehci->pre_port_reset(ehci, wIndex); + if (retval) + goto error_exit; + } +#endif } + ehci_writel(ehci, temp, status_reg); break; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index a65fba092e76..d7e4cd6dc0a3 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -126,6 +126,12 @@ struct ehci_hcd { /* one per controller */ #ifdef CONFIG_USB_EHCI_DEVIANT_PORT_STATUS_REG u32 __iomem *port_status; #endif +#ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS + int (*pre_port_reset)(struct ehci_hcd *ehci, + unsigned int port); + int (*post_port_reset)(struct ehci_hcd *ehci, + unsigned int port); +#endif __u32 hcs_params; /* cached register copy */ spinlock_t lock; From 85a7fce6232ffa0279297d6a11ec87d9ced4ce79 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:17:40 +0200 Subject: [PATCH 070/158] usb: ehci: add port speed hook Some EHCI implementations report device speed in implementation-defined registers. Add an optional hook for port speed reporting. Move the common integrated-TT speed decoding into the hub code, preserving the PORTSC and HOSTPC paths when no hook is supplied. Gate the callback behind the hidden USB_EHCI_PORT_SPEED_HOOK option so controllers using standard speed reporting incur no extra state or runtime checks. The hook is only meaningful for controllers with an integrated root hub transaction translator, so make the option depend on USB_EHCI_ROOT_HUB_TT. Suggested-by: Alan Stern Suggested-by: Daniel Palmer Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-usb-fotg2-v3-3-dd92ecf5675b@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 5 ++++ drivers/usb/host/ehci-hub.c | 53 ++++++++++++++++++++++++++++++++----- drivers/usb/host/ehci.h | 30 +++------------------ 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index a0c66191d03c..05a88969a6a0 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -219,6 +219,11 @@ config USB_EHCI_PORT_RESET_HOOKS bool # Used for hosts with controller-specific port reset sequencing +config USB_EHCI_PORT_SPEED_HOOK + bool + depends on USB_EHCI_ROOT_HUB_TT + # Used for hosts with controller-specific port speed reporting + config XPS_USB_HCD_XILINX bool "Use Xilinx usb host EHCI controller core" depends on (PPC32 || MICROBLAZE) diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 5aa629a92542..e676c98b6baa 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -15,6 +15,44 @@ /*-------------------------------------------------------------------------*/ +/* + * Some EHCI controllers have a Transaction Translator built into the + * root hub. This is a non-standard feature. Each controller will need + * to add code to the following function, and call it as needed. + */ + +/* + * In a bunch of EHCI implementations with transaction translators, + * the port speed can be found in the reserved bits in position 26 and + * 27. Implementations with the HOSTPC register will have this in + * bits 25 and 26 of the HOSTPC registers. + */ +#define PORTSC_SPEED_BITS(a) (((a) >> 26) & 3) +#define HOSTPC_SPEED_BITS(a) (((a) >> 25) & 3) + +/* Returns the speed of a device attached to a port on the root hub. */ +static unsigned int ehci_port_speed(struct ehci_hcd *ehci, + unsigned int port, unsigned int speed) +{ +#ifdef CONFIG_USB_EHCI_PORT_SPEED_HOOK + if (ehci->get_port_speed) + return ehci->get_port_speed(ehci, port); +#endif + + if (!IS_ENABLED(CONFIG_USB_EHCI_ROOT_HUB_TT) || !ehci_is_TDI(ehci)) + return USB_PORT_STAT_HIGH_SPEED; + + switch (speed) { + case 0: + return 0; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; + } +} + #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) #ifdef CONFIG_PM @@ -287,8 +325,9 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) * sake, add a delay if we need one. */ if ((t2 & PORT_WKDISC_E) && - ehci_port_speed(ehci, t2) == - USB_PORT_STAT_HIGH_SPEED) + ehci_port_speed(ehci, port, + PORTSC_SPEED_BITS(t2)) == + USB_PORT_STAT_HIGH_SPEED) fs_idle_delay = true; ehci_writel(ehci, t2, reg); changed = 1; @@ -990,12 +1029,14 @@ int ehci_hub_control( if (temp & PORT_CONNECT) { status |= USB_PORT_STAT_CONNECTION; - // status may be from integrated TT if (ehci->has_hostpc) { temp1 = ehci_readl(ehci, hostpc_reg); - status |= ehci_port_speed(ehci, temp1); - } else - status |= ehci_port_speed(ehci, temp); + status |= ehci_port_speed(ehci, wIndex, + HOSTPC_SPEED_BITS(temp1)); + } else { + status |= ehci_port_speed(ehci, wIndex, + PORTSC_SPEED_BITS(temp)); + } } if (temp & PORT_PE) status |= USB_PORT_STAT_ENABLE; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index d7e4cd6dc0a3..bce4579fb8b2 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -126,6 +126,10 @@ struct ehci_hcd { /* one per controller */ #ifdef CONFIG_USB_EHCI_DEVIANT_PORT_STATUS_REG u32 __iomem *port_status; #endif +#ifdef CONFIG_USB_EHCI_PORT_SPEED_HOOK + unsigned int (*get_port_speed)(struct ehci_hcd *ehci, + unsigned int port); +#endif #ifdef CONFIG_USB_EHCI_PORT_RESET_HOOKS int (*pre_port_reset)(struct ehci_hcd *ehci, unsigned int port); @@ -673,38 +677,12 @@ struct ehci_tt { #ifdef CONFIG_USB_EHCI_ROOT_HUB_TT -/* - * Some EHCI controllers have a Transaction Translator built into the - * root hub. This is a non-standard feature. Each controller will need - * to add code to the following inline functions, and call them as - * needed (mostly in root hub code). - */ - #define ehci_is_TDI(e) (ehci_to_hcd(e)->has_tt) -/* Returns the speed of a device attached to a port on the root hub. */ -static inline unsigned int -ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc) -{ - if (ehci_is_TDI(ehci)) { - switch ((portsc >> (ehci->has_hostpc ? 25 : 26)) & 3) { - case 0: - return 0; - case 1: - return USB_PORT_STAT_LOW_SPEED; - case 2: - default: - return USB_PORT_STAT_HIGH_SPEED; - } - } - return USB_PORT_STAT_HIGH_SPEED; -} - #else #define ehci_is_TDI(e) (0) -#define ehci_port_speed(ehci, portsc) USB_PORT_STAT_HIGH_SPEED #endif /*-------------------------------------------------------------------------*/ From 7a7b1c08dbcded3de761cee4360e2edf3bcf04f8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:17:41 +0200 Subject: [PATCH 071/158] usb: ehci: support additional controller quirks Some EHCI implementations omit CONFIGFLAG and USBMODE, or have an integrated transaction translator that cannot schedule siTDs. Add quirks to skip CONFIGFLAG and TDI mode accesses and to reject full- and low-speed isochronous transfers. Standard controllers retain their current behavior. Suggested-by: Daniel Palmer Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-usb-fotg2-v3-4-dd92ecf5675b@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 24 ++++++++++++++++-------- drivers/usb/host/ehci-timer.c | 3 ++- drivers/usb/host/ehci.h | 3 +++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 19cc7611d86d..75f780ec3ba0 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -187,7 +187,8 @@ static int ehci_halt (struct ehci_hcd *ehci) /* disable any irqs left enabled by previous code */ ehci_writel(ehci, 0, &ehci->regs->intr_enable); - if (ehci_is_TDI(ehci) && !tdi_in_host_mode(ehci)) { + if (ehci_is_TDI(ehci) && !ehci->no_tdi_mode && + !tdi_in_host_mode(ehci)) { spin_unlock_irq(&ehci->lock); return 0; } @@ -254,7 +255,7 @@ int ehci_reset(struct ehci_hcd *ehci) if (retval) return retval; - if (ehci_is_TDI(ehci)) + if (ehci_is_TDI(ehci) && !ehci->no_tdi_mode) tdi_reset (ehci); if (ehci->debug) @@ -342,10 +343,12 @@ static void ehci_silence_controller(struct ehci_hcd *ehci) ehci_turn_off_all_ports(ehci); /* make BIOS/etc use companion controller during reboot */ - ehci_writel(ehci, 0, &ehci->regs->configured_flag); + if (!ehci->no_configured_flag) { + ehci_writel(ehci, 0, &ehci->regs->configured_flag); - /* unblock posted writes */ - ehci_readl(ehci, &ehci->regs->configured_flag); + /* unblock posted writes */ + ehci_readl(ehci, &ehci->regs->configured_flag); + } spin_unlock_irq(&ehci->lock); } @@ -629,7 +632,8 @@ static int ehci_run (struct usb_hcd *hcd) */ down_write(&ehci_cf_port_reset_rwsem); ehci->rh_state = EHCI_RH_RUNNING; - ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + if (!ehci->no_configured_flag) + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); /* Wait until HC become operational */ ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ @@ -909,6 +913,8 @@ static int ehci_urb_enqueue ( case PIPE_ISOCHRONOUS: if (urb->dev->speed == USB_SPEED_HIGH) return itd_submit (ehci, urb, mem_flags); + else if (ehci->no_fsls_isoc) + return -EOPNOTSUPP; else return sitd_submit (ehci, urb, mem_flags); } @@ -1184,7 +1190,8 @@ int ehci_resume(struct usb_hcd *hcd, bool force_reset) * then we maintained suspend power. * Just undo the effect of ehci_suspend(). */ - if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF && + if ((ehci->no_configured_flag || + ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) && !force_reset) { int mask = INTR_MASK; @@ -1216,7 +1223,8 @@ int ehci_resume(struct usb_hcd *hcd, bool force_reset) goto skip; ehci_writel(ehci, ehci->command, &ehci->regs->command); - ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + if (!ehci->no_configured_flag) + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ ehci->rh_state = EHCI_RH_SUSPENDED; diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index a79c8ac0a55f..404069a3dd0b 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -199,7 +199,8 @@ static void ehci_handle_controller_death(struct ehci_hcd *ehci) /* Clean up the mess */ ehci->rh_state = EHCI_RH_HALTED; - ehci_writel(ehci, 0, &ehci->regs->configured_flag); + if (!ehci->no_configured_flag) + ehci_writel(ehci, 0, &ehci->regs->configured_flag); ehci_writel(ehci, 0, &ehci->regs->intr_enable); ehci_work(ehci); end_unlink_async(ehci); diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index bce4579fb8b2..855a9d08b019 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -235,6 +235,9 @@ struct ehci_hcd { /* one per controller */ unsigned spurious_oc:1; unsigned is_aspeed:1; unsigned zx_wakeup_clear_needed:1; + unsigned no_configured_flag:1; + unsigned no_tdi_mode:1; + unsigned no_fsls_isoc:1; /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) From 7dbc2e1ba8fd7793af299d8a813eae727759ecf3 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:17:42 +0200 Subject: [PATCH 072/158] usb: fotg210: use the common EHCI core Replace the private EHCI fork with an hc_driver built from the common EHCI implementation. Configure the FOTG210 register, root-hub TT, speed-reporting, port-reset, and Gemini timing quirks through the new hooks. Keep role selection fixed at probe time, while cleaning up clock/reset handling, interrupt masks, Gemini syscon setup, VBUS sequencing, probe failures, shutdown, and system sleep. Device trees without dr_mode retain their historical host default. Suggested-by: Daniel Palmer Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-usb-fotg2-v3-5-dd92ecf5675b@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/Kconfig | 5 + drivers/usb/fotg210/fotg210-core.c | 272 +- drivers/usb/fotg210/fotg210-hcd.c | 5666 +--------------------------- drivers/usb/fotg210/fotg210-hcd.h | 689 ---- drivers/usb/fotg210/fotg210.h | 26 +- 5 files changed, 335 insertions(+), 6323 deletions(-) delete mode 100644 drivers/usb/fotg210/fotg210-hcd.h diff --git a/drivers/usb/fotg210/Kconfig b/drivers/usb/fotg210/Kconfig index 87a16258274e..2c6e76faac64 100644 --- a/drivers/usb/fotg210/Kconfig +++ b/drivers/usb/fotg210/Kconfig @@ -16,6 +16,11 @@ if USB_FOTG210 config USB_FOTG210_HCD bool "Faraday FOTG210 USB Host Controller support" depends on USB=y || USB=USB_FOTG210 + depends on USB_EHCI_HCD + select USB_EHCI_DEVIANT_PORT_STATUS_REG + select USB_EHCI_PORT_RESET_HOOKS + select USB_EHCI_PORT_SPEED_HOOK + select USB_EHCI_ROOT_HUB_TT help Faraday FOTG210 is an OTG controller which can be configured as an USB2.0 host. It is designed to meet USB2.0 EHCI specification diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index 7fb4d4715e9f..7976bdae5f07 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -1,37 +1,50 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Central probing code for the FOTG210 dual role driver - * We register one driver for the hardware and then we decide - * whether to proceed with probing the host or the peripheral - * driver. + * Central probing code for the FOTG210 dual-role controller. + * + * The role is selected once at probe time. The driver does not attempt to + * switch between the host and peripheral blocks while it is running. */ -#include +#include #include +#include #include +#include #include #include #include #include +#include #include +#include #include #include +#include #include #include "fotg210.h" -/* Role Register 0x80 */ -#define FOTG210_RR 0x80 -#define FOTG210_RR_ID BIT(21) /* 1 = B-device, 0 = A-device */ -#define FOTG210_RR_CROLE BIT(20) /* 1 = device, 0 = host */ +/* OTG control/status register. */ +#define FOTG210_OTGCSR 0x80 +#define FOTG210_OTGCSR_ID BIT(21) +#define FOTG210_OTGCSR_CROLE BIT(20) +#define FOTG210_OTGCSR_A_VBUS_VLD BIT(19) +#define FOTG210_OTGCSR_A_BUS_DROP BIT(5) +#define FOTG210_OTGCSR_A_BUS_REQ BIT(4) +#define FOTG210_OTGCSR_B_HNP_EN BIT(1) -/* - * Gemini-specific initialization function, only executed on the - * Gemini SoC using the global misc control register. - * - * The gemini USB blocks are connected to either Mini-A (host mode) or - * Mini-B (peripheral mode) plugs. There is no role switch support on the - * Gemini SoC, just either-or. - */ +/* OTG interrupt status and enable registers. */ +#define FOTG210_OTGISR 0x84 +#define FOTG210_OTGIEN 0x88 + +/* Global interrupt mask register; set bits mask the corresponding source. */ +#define FOTG210_GINTM 0xc4 +#define FOTG210_GINTM_INT_POLARITY BIT(3) +#define FOTG210_GINTM_MHC_INT BIT(2) +#define FOTG210_GINTM_MOTG_INT BIT(1) +#define FOTG210_GINTM_MDEV_INT BIT(0) + +/* Gemini global miscellaneous-control register. */ #define GEMINI_GLOBAL_MISC_CTRL 0x30 #define GEMINI_MISC_USB0_WAKEUP BIT(14) #define GEMINI_MISC_USB1_WAKEUP BIT(15) @@ -40,94 +53,133 @@ #define GEMINI_MISC_USB0_MINI_B BIT(29) #define GEMINI_MISC_USB1_MINI_B BIT(30) -static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res, - enum usb_dr_mode mode) +static int fotg210_gemini_init(struct fotg210 *fotg) { struct device *dev = fotg->dev; struct device_node *np = dev->of_node; - struct regmap *map; - bool wakeup; + bool wakeup = of_property_read_bool(np, "wakeup-source"); u32 mask, val; int ret; - map = syscon_regmap_lookup_by_phandle(np, "syscon"); - if (IS_ERR(map)) - return dev_err_probe(dev, PTR_ERR(map), "no syscon\n"); - fotg->map = map; - wakeup = of_property_read_bool(np, "wakeup-source"); + fotg->map = syscon_regmap_lookup_by_phandle(np, "syscon"); + if (IS_ERR(fotg->map)) + return dev_err_probe(dev, PTR_ERR(fotg->map), "no syscon\n"); - /* - * Figure out if this is USB0 or USB1 by simply checking the - * physical base address. - */ - mask = 0; - if (res->start == 0x69000000) { + if (fotg->res->start == 0x69000000) { fotg->port = GEMINI_PORT_1; mask = GEMINI_MISC_USB1_VBUS_ON | GEMINI_MISC_USB1_MINI_B | - GEMINI_MISC_USB1_WAKEUP; - if (mode == USB_DR_MODE_HOST) - val = GEMINI_MISC_USB1_VBUS_ON; - else - val = GEMINI_MISC_USB1_MINI_B; + GEMINI_MISC_USB1_WAKEUP; + val = fotg->mode == USB_DR_MODE_HOST ? + GEMINI_MISC_USB1_VBUS_ON : GEMINI_MISC_USB1_MINI_B; if (wakeup) val |= GEMINI_MISC_USB1_WAKEUP; } else { fotg->port = GEMINI_PORT_0; mask = GEMINI_MISC_USB0_VBUS_ON | GEMINI_MISC_USB0_MINI_B | - GEMINI_MISC_USB0_WAKEUP; - if (mode == USB_DR_MODE_HOST) - val = GEMINI_MISC_USB0_VBUS_ON; - else - val = GEMINI_MISC_USB0_MINI_B; + GEMINI_MISC_USB0_WAKEUP; + val = fotg->mode == USB_DR_MODE_HOST ? + GEMINI_MISC_USB0_VBUS_ON : GEMINI_MISC_USB0_MINI_B; if (wakeup) val |= GEMINI_MISC_USB0_WAKEUP; } - ret = regmap_update_bits(map, GEMINI_GLOBAL_MISC_CTRL, mask, val); - if (ret) { - dev_err(dev, "failed to initialize Gemini PHY\n"); - return ret; - } + ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL, + mask, val); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize Gemini PHY\n"); dev_info(dev, "initialized Gemini PHY in %s mode\n", - (mode == USB_DR_MODE_HOST) ? "host" : "gadget"); + fotg->mode == USB_DR_MODE_HOST ? "host" : "gadget"); return 0; } /** - * fotg210_vbus() - Called by gadget driver to enable/disable VBUS - * @fotg: pointer to a private fotg210 object - * @enable: true to enable VBUS, false to disable VBUS + * fotg210_vbus() - enable or disable the A-device VBUS supply + * @fotg: controller state + * @enable: whether to drive VBUS */ void fotg210_vbus(struct fotg210 *fotg, bool enable) { - u32 mask; + u32 mask = 0; u32 val; int ret; + val = readl(fotg->base + FOTG210_OTGCSR); + if (enable) { + val &= ~FOTG210_OTGCSR_A_BUS_DROP; + val |= FOTG210_OTGCSR_A_BUS_REQ; + } else { + val &= ~FOTG210_OTGCSR_A_BUS_REQ; + val |= FOTG210_OTGCSR_A_BUS_DROP; + } + writel(val, fotg->base + FOTG210_OTGCSR); + switch (fotg->port) { case GEMINI_PORT_0: mask = GEMINI_MISC_USB0_VBUS_ON; - val = enable ? GEMINI_MISC_USB0_VBUS_ON : 0; break; case GEMINI_PORT_1: mask = GEMINI_MISC_USB1_VBUS_ON; - val = enable ? GEMINI_MISC_USB1_VBUS_ON : 0; break; - default: - return; + case GEMINI_PORT_NONE: + break; } - ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL, mask, val); + + if (mask) { + ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL, + mask, enable ? mask : 0); + if (ret) { + dev_err(fotg->dev, "failed to %s VBUS\n", + str_enable_disable(enable)); + return; + } + } + + ret = readl_poll_timeout(fotg->base + FOTG210_OTGCSR, val, + enable == !!(val & FOTG210_OTGCSR_A_VBUS_VLD), + 1000, 500000); if (ret) - dev_err(fotg->dev, "failed to %s VBUS\n", - str_enable_disable(enable)); - dev_info(fotg->dev, "%s: %s VBUS\n", __func__, str_enable_disable(enable)); + dev_warn(fotg->dev, "timeout waiting for VBUS to %s\n", + str_enable_disable(enable)); +} + +static void fotg210_init_host(struct fotg210 *fotg) +{ + u32 val; + + /* This driver keeps a fixed A-host role and does not negotiate HNP. */ + val = readl(fotg->base + FOTG210_OTGCSR); + val &= ~FOTG210_OTGCSR_B_HNP_EN; + writel(val, fotg->base + FOTG210_OTGCSR); + + /* Mask peripheral and OTG sources, enable the host source, active high. */ + writel(FOTG210_GINTM_MDEV_INT | FOTG210_GINTM_MOTG_INT | + FOTG210_GINTM_INT_POLARITY, fotg->base + FOTG210_GINTM); + writel(0, fotg->base + FOTG210_OTGIEN); + writel(readl(fotg->base + FOTG210_OTGISR), + fotg->base + FOTG210_OTGISR); + + /* Vendor trees cycle the A bus before starting the host controller. */ + fotg210_vbus(fotg, false); + usleep_range(10000, 12000); + fotg210_vbus(fotg, true); + usleep_range(10000, 12000); +} + +static void fotg210_init_peripheral(struct fotg210 *fotg) +{ + /* Mask host and OTG sources, enable the device source, active high. */ + writel(FOTG210_GINTM_MHC_INT | FOTG210_GINTM_MOTG_INT | + FOTG210_GINTM_INT_POLARITY, fotg->base + FOTG210_GINTM); + writel(0, fotg->base + FOTG210_OTGIEN); + writel(readl(fotg->base + FOTG210_OTGISR), + fotg->base + FOTG210_OTGISR); } static int fotg210_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - enum usb_dr_mode mode; + struct reset_control *rst; struct fotg210 *fotg; u32 val; int ret; @@ -141,62 +193,110 @@ static int fotg210_probe(struct platform_device *pdev) if (IS_ERR(fotg->base)) return PTR_ERR(fotg->base); - fotg->pclk = devm_clk_get_optional_enabled(dev, "PCLK"); + fotg->pclk = devm_clk_get_enabled(dev, "PCLK"); if (IS_ERR(fotg->pclk)) - return PTR_ERR(fotg->pclk); + return dev_err_probe(dev, PTR_ERR(fotg->pclk), + "failed to enable PCLK\n"); - mode = usb_get_dr_mode(dev); + rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get reset\n"); + ret = reset_control_reset(rst); + if (ret) + return dev_err_probe(dev, ret, + "failed to reset controller\n"); + + val = readl(fotg->base + FOTG210_OTGCSR); + fotg->mode = usb_get_dr_mode(dev); + /* Keep the historical fixed-host default for old device trees. */ + if (fotg->mode == USB_DR_MODE_UNKNOWN) + fotg->mode = USB_DR_MODE_HOST; + if (fotg->mode != USB_DR_MODE_HOST && + fotg->mode != USB_DR_MODE_PERIPHERAL) + return dev_err_probe(dev, -EINVAL, + "dr_mode must select host or peripheral\n"); if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) { - ret = fotg210_gemini_init(fotg, fotg->res, mode); + fotg->is_fotg210 = true; + ret = fotg210_gemini_init(fotg); if (ret) return ret; + } else if (of_device_is_compatible(dev->of_node, "faraday,fotg210")) { + fotg->is_fotg210 = true; } - val = readl(fotg->base + FOTG210_RR); - if (mode == USB_DR_MODE_PERIPHERAL) { - if (!(val & FOTG210_RR_CROLE)) - dev_err(dev, "block not in device role\n"); - ret = fotg210_udc_probe(pdev, fotg); - } else { - if (val & FOTG210_RR_CROLE) - dev_err(dev, "block not in host role\n"); - ret = fotg210_hcd_probe(pdev, fotg); + if (fotg->mode == USB_DR_MODE_PERIPHERAL) { + if (!(val & FOTG210_OTGCSR_CROLE)) + dev_warn(dev, "controller does not report device role\n"); + if (!(val & FOTG210_OTGCSR_ID)) + dev_warn(dev, "controller does not report B-device role\n"); + fotg210_init_peripheral(fotg); + return fotg210_udc_probe(pdev, fotg); } + if (val & FOTG210_OTGCSR_CROLE) + dev_warn(dev, "controller does not report host role\n"); + if (val & FOTG210_OTGCSR_ID) + dev_warn(dev, "controller does not report A-device role\n"); + fotg210_init_host(fotg); + ret = fotg210_hcd_probe(pdev, fotg); + if (ret) + fotg210_vbus(fotg, false); + return ret; } static void fotg210_remove(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - enum usb_dr_mode mode; - - mode = usb_get_dr_mode(dev); - - if (mode == USB_DR_MODE_PERIPHERAL) + /* The subdrivers own drvdata, so recover the fixed role from firmware. */ + if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) fotg210_udc_remove(pdev); else fotg210_hcd_remove(pdev); } -#ifdef CONFIG_OF +static void fotg210_shutdown(struct platform_device *pdev) +{ + if (usb_get_dr_mode(&pdev->dev) != USB_DR_MODE_PERIPHERAL) + usb_hcd_platform_shutdown(pdev); +} + +static int fotg210_suspend(struct device *dev) +{ + if (usb_get_dr_mode(dev) == USB_DR_MODE_PERIPHERAL) + return 0; + + return fotg210_hcd_suspend(dev); +} + +static int fotg210_resume(struct device *dev) +{ + if (usb_get_dr_mode(dev) == USB_DR_MODE_PERIPHERAL) + return 0; + + return fotg210_hcd_resume(dev); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(fotg210_pm_ops, fotg210_suspend, + fotg210_resume); + static const struct of_device_id fotg210_of_match[] = { { .compatible = "faraday,fotg200" }, { .compatible = "faraday,fotg210" }, - /* TODO: can we also handle FUSB220? */ {}, }; MODULE_DEVICE_TABLE(of, fotg210_of_match); -#endif static struct platform_driver fotg210_driver = { .driver = { - .name = "fotg210", - .of_match_table = of_match_ptr(fotg210_of_match), + .name = "fotg210", + .of_match_table = fotg210_of_match, + .pm = pm_sleep_ptr(&fotg210_pm_ops), }, - .probe = fotg210_probe, + .probe = fotg210_probe, .remove = fotg210_remove, + .shutdown = fotg210_shutdown, }; static int __init fotg210_init(void) diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 956be5b56510..0a36820f189b 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -1,5547 +1,127 @@ // SPDX-License-Identifier: GPL-2.0+ -/* Faraday FOTG210 EHCI-like driver - * - * Copyright (c) 2013 Faraday Technology Corporation - * - * Author: Yuan-Hsin Chen - * Feng-Hsin Chiang - * Po-Yu Chuang - * - * Most of code borrowed from the Linux-3.7 EHCI driver - */ -#include -#include -#include -#include -#include +/* Faraday FOTG210 EHCI driver */ +#include #include -#include -#include -#include +#include #include #include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include +#include "../host/ehci.h" #include "fotg210.h" -static const char hcd_name[] = "fotg210_hcd"; +#define FOTG210_PORTSC 0x30 +#define FOTG210_MISC 0x40 +#define FOTG200_MISC_SPEED_MASK GENMASK(10, 9) +#define FOTG200_MISC_SPEED_SHIFT 9 +#define FOTG210_MISC_EOF1_MASK GENMASK(3, 2) +#define FOTG210_MISC_EOF1_720_21000_4000 (3 << 2) +#define FOTG210_MISC_AS_SLP_MASK GENMASK(1, 0) +#define FOTG210_MISC_AS_SLP_10US BIT(0) +#define FOTG210_OTGCSR 0x80 +#define FOTG210_OTGCSR_SPEED_MASK GENMASK(23, 22) +#define FOTG210_OTGCSR_SPEED_SHIFT 22 -#undef FOTG210_URB_TRACE -#define FOTG210_STATS - -/* magic numbers that can affect system performance */ -#define FOTG210_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ -#define FOTG210_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ -#define FOTG210_TUNE_RL_TT 0 -#define FOTG210_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ -#define FOTG210_TUNE_MULT_TT 1 - -/* Some drivers think it's safe to schedule isochronous transfers more than 256 - * ms into the future (partly as a result of an old bug in the scheduling - * code). In an attempt to avoid trouble, we will use a minimum scheduling - * length of 512 frames instead of 256. - */ -#define FOTG210_TUNE_FLS 1 /* (medium) 512-frame schedule */ - -/* Initial IRQ latency: faster than hw default */ -static int log2_irq_thresh; /* 0 to 6 */ -module_param(log2_irq_thresh, int, S_IRUGO); -MODULE_PARM_DESC(log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); - -/* initial park setting: slower than hw default */ -static unsigned park; -module_param(park, uint, S_IRUGO); -MODULE_PARM_DESC(park, "park setting; 1-3 back-to-back async packets"); - -/* for link power management(LPM) feature */ -static unsigned int hird; -module_param(hird, int, S_IRUGO); -MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); - -#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) - -#include "fotg210-hcd.h" - -#define fotg210_dbg(fotg210, fmt, args...) \ - dev_dbg(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) -#define fotg210_err(fotg210, fmt, args...) \ - dev_err(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) -#define fotg210_info(fotg210, fmt, args...) \ - dev_info(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) -#define fotg210_warn(fotg210, fmt, args...) \ - dev_warn(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) - -/* check the values in the HCSPARAMS register (host controller _Structural_ - * parameters) see EHCI spec, Table 2-4 for each value - */ -static void dbg_hcs_params(struct fotg210_hcd *fotg210, char *label) -{ - u32 params = fotg210_readl(fotg210, &fotg210->caps->hcs_params); - - fotg210_dbg(fotg210, "%s hcs_params 0x%x ports=%d\n", label, params, - HCS_N_PORTS(params)); -} - -/* check the values in the HCCPARAMS register (host controller _Capability_ - * parameters) see EHCI Spec, Table 2-5 for each value - */ -static void dbg_hcc_params(struct fotg210_hcd *fotg210, char *label) -{ - u32 params = fotg210_readl(fotg210, &fotg210->caps->hcc_params); - - fotg210_dbg(fotg210, "%s hcc_params %04x uframes %s%s\n", label, - params, - HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", - HCC_CANPARK(params) ? " park" : ""); -} - -static void __maybe_unused -dbg_qtd(const char *label, struct fotg210_hcd *fotg210, struct fotg210_qtd *qtd) -{ - fotg210_dbg(fotg210, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, - hc32_to_cpup(fotg210, &qtd->hw_next), - hc32_to_cpup(fotg210, &qtd->hw_alt_next), - hc32_to_cpup(fotg210, &qtd->hw_token), - hc32_to_cpup(fotg210, &qtd->hw_buf[0])); - if (qtd->hw_buf[1]) - fotg210_dbg(fotg210, " p1=%08x p2=%08x p3=%08x p4=%08x\n", - hc32_to_cpup(fotg210, &qtd->hw_buf[1]), - hc32_to_cpup(fotg210, &qtd->hw_buf[2]), - hc32_to_cpup(fotg210, &qtd->hw_buf[3]), - hc32_to_cpup(fotg210, &qtd->hw_buf[4])); -} - -static void __maybe_unused -dbg_qh(const char *label, struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - struct fotg210_qh_hw *hw = qh->hw; - - fotg210_dbg(fotg210, "%s qh %p n%08x info %x %x qtd %x\n", label, qh, - hw->hw_next, hw->hw_info1, hw->hw_info2, - hw->hw_current); - - dbg_qtd("overlay", fotg210, (struct fotg210_qtd *) &hw->hw_qtd_next); -} - -static void __maybe_unused -dbg_itd(const char *label, struct fotg210_hcd *fotg210, struct fotg210_itd *itd) -{ - fotg210_dbg(fotg210, "%s[%d] itd %p, next %08x, urb %p\n", label, - itd->frame, itd, hc32_to_cpu(fotg210, itd->hw_next), - itd->urb); - - fotg210_dbg(fotg210, - " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", - hc32_to_cpu(fotg210, itd->hw_transaction[0]), - hc32_to_cpu(fotg210, itd->hw_transaction[1]), - hc32_to_cpu(fotg210, itd->hw_transaction[2]), - hc32_to_cpu(fotg210, itd->hw_transaction[3]), - hc32_to_cpu(fotg210, itd->hw_transaction[4]), - hc32_to_cpu(fotg210, itd->hw_transaction[5]), - hc32_to_cpu(fotg210, itd->hw_transaction[6]), - hc32_to_cpu(fotg210, itd->hw_transaction[7])); - - fotg210_dbg(fotg210, - " buf: %08x %08x %08x %08x %08x %08x %08x\n", - hc32_to_cpu(fotg210, itd->hw_bufp[0]), - hc32_to_cpu(fotg210, itd->hw_bufp[1]), - hc32_to_cpu(fotg210, itd->hw_bufp[2]), - hc32_to_cpu(fotg210, itd->hw_bufp[3]), - hc32_to_cpu(fotg210, itd->hw_bufp[4]), - hc32_to_cpu(fotg210, itd->hw_bufp[5]), - hc32_to_cpu(fotg210, itd->hw_bufp[6])); - - fotg210_dbg(fotg210, " index: %d %d %d %d %d %d %d %d\n", - itd->index[0], itd->index[1], itd->index[2], - itd->index[3], itd->index[4], itd->index[5], - itd->index[6], itd->index[7]); -} - -static int __maybe_unused -dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) -{ - return scnprintf(buf, len, "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", - label, label[0] ? " " : "", status, - (status & STS_ASS) ? " Async" : "", - (status & STS_PSS) ? " Periodic" : "", - (status & STS_RECL) ? " Recl" : "", - (status & STS_HALT) ? " Halt" : "", - (status & STS_IAA) ? " IAA" : "", - (status & STS_FATAL) ? " FATAL" : "", - (status & STS_FLR) ? " FLR" : "", - (status & STS_PCD) ? " PCD" : "", - (status & STS_ERR) ? " ERR" : "", - (status & STS_INT) ? " INT" : ""); -} - -static int __maybe_unused -dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) -{ - return scnprintf(buf, len, "%s%sintrenable %02x%s%s%s%s%s%s", - label, label[0] ? " " : "", enable, - (enable & STS_IAA) ? " IAA" : "", - (enable & STS_FATAL) ? " FATAL" : "", - (enable & STS_FLR) ? " FLR" : "", - (enable & STS_PCD) ? " PCD" : "", - (enable & STS_ERR) ? " ERR" : "", - (enable & STS_INT) ? " INT" : ""); -} - -static const char *const fls_strings[] = { "1024", "512", "256", "??" }; - -static int dbg_command_buf(char *buf, unsigned len, const char *label, - u32 command) -{ - return scnprintf(buf, len, - "%s%scommand %07x %s=%d ithresh=%d%s%s%s period=%s%s %s", - label, label[0] ? " " : "", command, - (command & CMD_PARK) ? " park" : "(park)", - CMD_PARK_CNT(command), - (command >> 16) & 0x3f, - (command & CMD_IAAD) ? " IAAD" : "", - (command & CMD_ASE) ? " Async" : "", - (command & CMD_PSE) ? " Periodic" : "", - fls_strings[(command >> 2) & 0x3], - (command & CMD_RESET) ? " Reset" : "", - (command & CMD_RUN) ? "RUN" : "HALT"); -} - -static char *dbg_port_buf(char *buf, unsigned len, const char *label, int port, - u32 status) -{ - char *sig; - - /* signaling state */ - switch (status & (3 << 10)) { - case 0 << 10: - sig = "se0"; - break; - case 1 << 10: - sig = "k"; - break; /* low speed */ - case 2 << 10: - sig = "j"; - break; - default: - sig = "?"; - break; - } - - scnprintf(buf, len, "%s%sport:%d status %06x %d sig=%s%s%s%s%s%s%s%s", - label, label[0] ? " " : "", port, status, - status >> 25, /*device address */ - sig, - (status & PORT_RESET) ? " RESET" : "", - (status & PORT_SUSPEND) ? " SUSPEND" : "", - (status & PORT_RESUME) ? " RESUME" : "", - (status & PORT_PEC) ? " PEC" : "", - (status & PORT_PE) ? " PE" : "", - (status & PORT_CSC) ? " CSC" : "", - (status & PORT_CONNECT) ? " CONNECT" : ""); - - return buf; -} - -/* functions have the "wrong" filename when they're output... */ -#define dbg_status(fotg210, label, status) { \ - char _buf[80]; \ - dbg_status_buf(_buf, sizeof(_buf), label, status); \ - fotg210_dbg(fotg210, "%s\n", _buf); \ -} - -#define dbg_cmd(fotg210, label, command) { \ - char _buf[80]; \ - dbg_command_buf(_buf, sizeof(_buf), label, command); \ - fotg210_dbg(fotg210, "%s\n", _buf); \ -} - -#define dbg_port(fotg210, label, port, status) { \ - char _buf[80]; \ - fotg210_dbg(fotg210, "%s\n", \ - dbg_port_buf(_buf, sizeof(_buf), label, port, status));\ -} - -/* troubleshooting help: expose state in debugfs */ -static int debug_async_open(struct inode *, struct file *); -static int debug_periodic_open(struct inode *, struct file *); -static int debug_registers_open(struct inode *, struct file *); -static int debug_async_open(struct inode *, struct file *); - -static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); -static int debug_close(struct inode *, struct file *); - -static const struct file_operations debug_async_fops = { - .owner = THIS_MODULE, - .open = debug_async_open, - .read = debug_output, - .release = debug_close, - .llseek = default_llseek, -}; -static const struct file_operations debug_periodic_fops = { - .owner = THIS_MODULE, - .open = debug_periodic_open, - .read = debug_output, - .release = debug_close, - .llseek = default_llseek, -}; -static const struct file_operations debug_registers_fops = { - .owner = THIS_MODULE, - .open = debug_registers_open, - .read = debug_output, - .release = debug_close, - .llseek = default_llseek, +struct fotg210_hcd { + struct fotg210 *fotg; }; -static struct dentry *fotg210_debug_root; +#define hcd_to_fotg210_priv(hcd) \ + ((struct fotg210_hcd *)hcd_to_ehci(hcd)->priv) -struct debug_buffer { - ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ - struct usb_bus *bus; - struct mutex mutex; /* protect filling of buffer */ - size_t count; /* number of characters filled into buffer */ - char *output_buf; - size_t alloc_size; -}; +static struct hc_driver __read_mostly fotg210_hc_driver; -static inline char speed_char(u32 scratch) +static unsigned int fotg210_get_port_speed(struct ehci_hcd *ehci, + unsigned int port) { - switch (scratch & (3 << 12)) { - case QH_FULL_SPEED: - return 'f'; + struct fotg210 *fotg = hcd_to_fotg210_priv(ehci_to_hcd(ehci))->fotg; + u32 speed; - case QH_LOW_SPEED: - return 'l'; - - case QH_HIGH_SPEED: - return 'h'; - - default: - return '?'; - } -} - -static inline char token_mark(struct fotg210_hcd *fotg210, __hc32 token) -{ - __u32 v = hc32_to_cpu(fotg210, token); - - if (v & QTD_STS_ACTIVE) - return '*'; - if (v & QTD_STS_HALT) - return '-'; - if (!IS_SHORT_READ(v)) - return ' '; - /* tries to advance through hw_alt_next */ - return '/'; -} - -static void qh_lines(struct fotg210_hcd *fotg210, struct fotg210_qh *qh, - char **nextp, unsigned *sizep) -{ - u32 scratch; - u32 hw_curr; - struct fotg210_qtd *td; - unsigned temp; - unsigned size = *sizep; - char *next = *nextp; - char mark; - __le32 list_end = FOTG210_LIST_END(fotg210); - struct fotg210_qh_hw *hw = qh->hw; - - if (hw->hw_qtd_next == list_end) /* NEC does this */ - mark = '@'; + if (fotg->is_fotg210) + speed = (readl(fotg->base + FOTG210_OTGCSR) & + FOTG210_OTGCSR_SPEED_MASK) >> FOTG210_OTGCSR_SPEED_SHIFT; else - mark = token_mark(fotg210, hw->hw_token); - if (mark == '/') { /* qh_alt_next controls qh advance? */ - if ((hw->hw_alt_next & QTD_MASK(fotg210)) == - fotg210->async->hw->hw_alt_next) - mark = '#'; /* blocked */ - else if (hw->hw_alt_next == list_end) - mark = '.'; /* use hw_qtd_next */ - /* else alt_next points to some other qtd */ - } - scratch = hc32_to_cpup(fotg210, &hw->hw_info1); - hw_curr = (mark == '*') ? hc32_to_cpup(fotg210, &hw->hw_current) : 0; - temp = scnprintf(next, size, - "qh/%p dev%d %cs ep%d %08x %08x(%08x%c %s nak%d)", - qh, scratch & 0x007f, - speed_char(scratch), - (scratch >> 8) & 0x000f, - scratch, hc32_to_cpup(fotg210, &hw->hw_info2), - hc32_to_cpup(fotg210, &hw->hw_token), mark, - (cpu_to_hc32(fotg210, QTD_TOGGLE) & hw->hw_token) - ? "data1" : "data0", - (hc32_to_cpup(fotg210, &hw->hw_alt_next) >> 1) & 0x0f); - size -= temp; - next += temp; + speed = (readl(fotg->base + FOTG210_MISC) & + FOTG200_MISC_SPEED_MASK) >> FOTG200_MISC_SPEED_SHIFT; - /* hc may be modifying the list as we read it ... */ - list_for_each_entry(td, &qh->qtd_list, qtd_list) { - scratch = hc32_to_cpup(fotg210, &td->hw_token); - mark = ' '; - if (hw_curr == td->qtd_dma) - mark = '*'; - else if (hw->hw_qtd_next == cpu_to_hc32(fotg210, td->qtd_dma)) - mark = '+'; - else if (QTD_LENGTH(scratch)) { - if (td->hw_alt_next == fotg210->async->hw->hw_alt_next) - mark = '#'; - else if (td->hw_alt_next != list_end) - mark = '/'; - } - temp = scnprintf(next, size, - "\n\t%p%c%s len=%d %08x urb %p", - td, mark, ({ char *tmp; - switch ((scratch>>8)&0x03) { - case 0: - tmp = "out"; - break; - case 1: - tmp = "in"; - break; - case 2: - tmp = "setup"; - break; - default: - tmp = "?"; - break; - } tmp; }), - (scratch >> 16) & 0x7fff, - scratch, - td->urb); - size -= temp; - next += temp; - } - - temp = scnprintf(next, size, "\n"); - - size -= temp; - next += temp; - - *sizep = size; - *nextp = next; -} - -static ssize_t fill_async_buffer(struct debug_buffer *buf) -{ - struct usb_hcd *hcd; - struct fotg210_hcd *fotg210; - unsigned long flags; - unsigned temp, size; - char *next; - struct fotg210_qh *qh; - - hcd = bus_to_hcd(buf->bus); - fotg210 = hcd_to_fotg210(hcd); - next = buf->output_buf; - size = buf->alloc_size; - - *next = 0; - - /* dumps a snapshot of the async schedule. - * usually empty except for long-term bulk reads, or head. - * one QH per line, and TDs we know about - */ - spin_lock_irqsave(&fotg210->lock, flags); - for (qh = fotg210->async->qh_next.qh; size > 0 && qh; - qh = qh->qh_next.qh) - qh_lines(fotg210, qh, &next, &size); - if (fotg210->async_unlink && size > 0) { - temp = scnprintf(next, size, "\nunlink =\n"); - size -= temp; - next += temp; - - for (qh = fotg210->async_unlink; size > 0 && qh; - qh = qh->unlink_next) - qh_lines(fotg210, qh, &next, &size); - } - spin_unlock_irqrestore(&fotg210->lock, flags); - - return strlen(buf->output_buf); -} - -/* count tds, get ep direction */ -static unsigned output_buf_tds_dir(char *buf, struct fotg210_hcd *fotg210, - struct fotg210_qh_hw *hw, struct fotg210_qh *qh, unsigned size) -{ - u32 scratch = hc32_to_cpup(fotg210, &hw->hw_info1); - struct fotg210_qtd *qtd; - char *type = ""; - unsigned temp = 0; - - /* count tds, get ep direction */ - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { - temp++; - switch ((hc32_to_cpu(fotg210, qtd->hw_token) >> 8) & 0x03) { - case 0: - type = "out"; - continue; - case 1: - type = "in"; - continue; - } - } - - return scnprintf(buf, size, "(%c%d ep%d%s [%d/%d] q%d p%d)", - speed_char(scratch), scratch & 0x007f, - (scratch >> 8) & 0x000f, type, qh->usecs, - qh->c_usecs, temp, (scratch >> 16) & 0x7ff); -} - -#define DBG_SCHED_LIMIT 64 -static ssize_t fill_periodic_buffer(struct debug_buffer *buf) -{ - struct usb_hcd *hcd; - struct fotg210_hcd *fotg210; - unsigned long flags; - union fotg210_shadow p, *seen; - unsigned temp, size, seen_count; - char *next; - unsigned i; - __hc32 tag; - - seen = kmalloc_objs(*seen, DBG_SCHED_LIMIT, GFP_ATOMIC); - if (!seen) + switch (speed) { + case 0: return 0; - - seen_count = 0; - - hcd = bus_to_hcd(buf->bus); - fotg210 = hcd_to_fotg210(hcd); - next = buf->output_buf; - size = buf->alloc_size; - - temp = scnprintf(next, size, "size = %d\n", fotg210->periodic_size); - size -= temp; - next += temp; - - /* dump a snapshot of the periodic schedule. - * iso changes, interrupt usually doesn't. - */ - spin_lock_irqsave(&fotg210->lock, flags); - for (i = 0; i < fotg210->periodic_size; i++) { - p = fotg210->pshadow[i]; - if (likely(!p.ptr)) - continue; - - tag = Q_NEXT_TYPE(fotg210, fotg210->periodic[i]); - - temp = scnprintf(next, size, "%4d: ", i); - size -= temp; - next += temp; - - do { - struct fotg210_qh_hw *hw; - - switch (hc32_to_cpu(fotg210, tag)) { - case Q_TYPE_QH: - hw = p.qh->hw; - temp = scnprintf(next, size, " qh%d-%04x/%p", - p.qh->period, - hc32_to_cpup(fotg210, - &hw->hw_info2) - /* uframe masks */ - & (QH_CMASK | QH_SMASK), - p.qh); - size -= temp; - next += temp; - /* don't repeat what follows this qh */ - for (temp = 0; temp < seen_count; temp++) { - if (seen[temp].ptr != p.ptr) - continue; - if (p.qh->qh_next.ptr) { - temp = scnprintf(next, size, - " ..."); - size -= temp; - next += temp; - } - break; - } - /* show more info the first time around */ - if (temp == seen_count) { - temp = output_buf_tds_dir(next, - fotg210, hw, - p.qh, size); - - if (seen_count < DBG_SCHED_LIMIT) - seen[seen_count++].qh = p.qh; - } else - temp = 0; - tag = Q_NEXT_TYPE(fotg210, hw->hw_next); - p = p.qh->qh_next; - break; - case Q_TYPE_FSTN: - temp = scnprintf(next, size, - " fstn-%8x/%p", - p.fstn->hw_prev, p.fstn); - tag = Q_NEXT_TYPE(fotg210, p.fstn->hw_next); - p = p.fstn->fstn_next; - break; - case Q_TYPE_ITD: - temp = scnprintf(next, size, - " itd/%p", p.itd); - tag = Q_NEXT_TYPE(fotg210, p.itd->hw_next); - p = p.itd->itd_next; - break; - } - size -= temp; - next += temp; - } while (p.ptr); - - temp = scnprintf(next, size, "\n"); - size -= temp; - next += temp; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; } - spin_unlock_irqrestore(&fotg210->lock, flags); - kfree(seen); - - return buf->alloc_size - size; -} -#undef DBG_SCHED_LIMIT - -static const char *rh_state_string(struct fotg210_hcd *fotg210) -{ - switch (fotg210->rh_state) { - case FOTG210_RH_HALTED: - return "halted"; - case FOTG210_RH_SUSPENDED: - return "suspended"; - case FOTG210_RH_RUNNING: - return "running"; - case FOTG210_RH_STOPPING: - return "stopping"; - } - return "?"; } -static ssize_t fill_registers_buffer(struct debug_buffer *buf) +static int fotg210_pre_port_reset(struct ehci_hcd *ehci, unsigned int port) { - struct usb_hcd *hcd; - struct fotg210_hcd *fotg210; - unsigned long flags; - unsigned temp, size, i; - char *next, scratch[80]; - static const char fmt[] = "%*s\n"; - static const char label[] = ""; + u32 command; - hcd = bus_to_hcd(buf->bus); - fotg210 = hcd_to_fotg210(hcd); - next = buf->output_buf; - size = buf->alloc_size; + /* FOTG210 requires Run/Stop to be clear while PORTSC.Reset is set. */ + command = ehci_readl(ehci, &ehci->regs->command); + ehci_writel(ehci, command & ~CMD_RUN, &ehci->regs->command); - spin_lock_irqsave(&fotg210->lock, flags); - - if (!HCD_HW_ACCESSIBLE(hcd)) { - size = scnprintf(next, size, - "bus %s, device %s\n" - "%s\n" - "SUSPENDED(no register access)\n", - hcd->self.controller->bus->name, - dev_name(hcd->self.controller), - hcd->product_desc); - goto done; - } - - /* Capability Registers */ - i = HC_VERSION(fotg210, fotg210_readl(fotg210, - &fotg210->caps->hc_capbase)); - temp = scnprintf(next, size, - "bus %s, device %s\n" - "%s\n" - "EHCI %x.%02x, rh state %s\n", - hcd->self.controller->bus->name, - dev_name(hcd->self.controller), - hcd->product_desc, - i >> 8, i & 0x0ff, rh_state_string(fotg210)); - size -= temp; - next += temp; - - /* FIXME interpret both types of params */ - i = fotg210_readl(fotg210, &fotg210->caps->hcs_params); - temp = scnprintf(next, size, "structural params 0x%08x\n", i); - size -= temp; - next += temp; - - i = fotg210_readl(fotg210, &fotg210->caps->hcc_params); - temp = scnprintf(next, size, "capability params 0x%08x\n", i); - size -= temp; - next += temp; - - /* Operational Registers */ - temp = dbg_status_buf(scratch, sizeof(scratch), label, - fotg210_readl(fotg210, &fotg210->regs->status)); - temp = scnprintf(next, size, fmt, temp, scratch); - size -= temp; - next += temp; - - temp = dbg_command_buf(scratch, sizeof(scratch), label, - fotg210_readl(fotg210, &fotg210->regs->command)); - temp = scnprintf(next, size, fmt, temp, scratch); - size -= temp; - next += temp; - - temp = dbg_intr_buf(scratch, sizeof(scratch), label, - fotg210_readl(fotg210, &fotg210->regs->intr_enable)); - temp = scnprintf(next, size, fmt, temp, scratch); - size -= temp; - next += temp; - - temp = scnprintf(next, size, "uframe %04x\n", - fotg210_read_frame_index(fotg210)); - size -= temp; - next += temp; - - if (fotg210->async_unlink) { - temp = scnprintf(next, size, "async unlink qh %p\n", - fotg210->async_unlink); - size -= temp; - next += temp; - } - -#ifdef FOTG210_STATS - temp = scnprintf(next, size, - "irq normal %ld err %ld iaa %ld(lost %ld)\n", - fotg210->stats.normal, fotg210->stats.error, - fotg210->stats.iaa, fotg210->stats.lost_iaa); - size -= temp; - next += temp; - - temp = scnprintf(next, size, "complete %ld unlink %ld\n", - fotg210->stats.complete, fotg210->stats.unlink); - size -= temp; - next += temp; -#endif - -done: - spin_unlock_irqrestore(&fotg210->lock, flags); - - return buf->alloc_size - size; + return ehci_handshake(ehci, &ehci->regs->status, + STS_HALT, STS_HALT, 16 * 125); } -static struct debug_buffer -*alloc_buffer(struct usb_bus *bus, ssize_t (*fill_func)(struct debug_buffer *)) +static int fotg210_post_port_reset(struct ehci_hcd *ehci, unsigned int port) { - struct debug_buffer *buf; + ehci_writel(ehci, ehci->command | CMD_RUN, &ehci->regs->command); - buf = kzalloc_obj(struct debug_buffer); - - if (buf) { - buf->bus = bus; - buf->fill_func = fill_func; - mutex_init(&buf->mutex); - buf->alloc_size = PAGE_SIZE; - } - - return buf; + return ehci_handshake(ehci, &ehci->regs->status, STS_HALT, 0, + 16 * 125); } -static int fill_buffer(struct debug_buffer *buf) +static int fotg210_hcd_reset(struct usb_hcd *hcd) { - int ret = 0; - - if (!buf->output_buf) - buf->output_buf = vmalloc(buf->alloc_size); - - if (!buf->output_buf) { - ret = -ENOMEM; - goto out; - } - - ret = buf->fill_func(buf); - - if (ret >= 0) { - buf->count = ret; - ret = 0; - } - -out: - return ret; -} - -static ssize_t debug_output(struct file *file, char __user *user_buf, - size_t len, loff_t *offset) -{ - struct debug_buffer *buf = file->private_data; - int ret = 0; - - mutex_lock(&buf->mutex); - if (buf->count == 0) { - ret = fill_buffer(buf); - if (ret != 0) { - mutex_unlock(&buf->mutex); - goto out; - } - } - mutex_unlock(&buf->mutex); - - ret = simple_read_from_buffer(user_buf, len, offset, - buf->output_buf, buf->count); - -out: - return ret; - -} - -static int debug_close(struct inode *inode, struct file *file) -{ - struct debug_buffer *buf = file->private_data; - - if (buf) { - vfree(buf->output_buf); - kfree(buf); - } - - return 0; -} -static int debug_async_open(struct inode *inode, struct file *file) -{ - file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); - - return file->private_data ? 0 : -ENOMEM; -} - -static int debug_periodic_open(struct inode *inode, struct file *file) -{ - struct debug_buffer *buf; - - buf = alloc_buffer(inode->i_private, fill_periodic_buffer); - if (!buf) - return -ENOMEM; - - buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; - file->private_data = buf; - return 0; -} - -static int debug_registers_open(struct inode *inode, struct file *file) -{ - file->private_data = alloc_buffer(inode->i_private, - fill_registers_buffer); - - return file->private_data ? 0 : -ENOMEM; -} - -static inline void create_debug_files(struct fotg210_hcd *fotg210) -{ - struct usb_bus *bus = &fotg210_to_hcd(fotg210)->self; - struct dentry *root; - - root = debugfs_create_dir(bus->bus_name, fotg210_debug_root); - - debugfs_create_file("async", S_IRUGO, root, bus, &debug_async_fops); - debugfs_create_file("periodic", S_IRUGO, root, bus, - &debug_periodic_fops); - debugfs_create_file("registers", S_IRUGO, root, bus, - &debug_registers_fops); -} - -static inline void remove_debug_files(struct fotg210_hcd *fotg210) -{ - struct usb_bus *bus = &fotg210_to_hcd(fotg210)->self; - - debugfs_lookup_and_remove(bus->bus_name, fotg210_debug_root); -} - -/* handshake - spin reading hc until handshake completes or fails - * @ptr: address of hc register to be read - * @mask: bits to look at in result of read - * @done: value of those bits when handshake succeeds - * @usec: timeout in microseconds - * - * Returns negative errno, or zero on success - * - * Success happens when the "mask" bits have the specified value (hardware - * handshake done). There are two failure modes: "usec" have passed (major - * hardware flakeout), or the register reads as all-ones (hardware removed). - * - * That last failure should_only happen in cases like physical cardbus eject - * before driver shutdown. But it also seems to be caused by bugs in cardbus - * bridge shutdown: shutting down the bridge before the devices using it. - */ -static int handshake(struct fotg210_hcd *fotg210, void __iomem *ptr, - u32 mask, u32 done, int usec) -{ - u32 result; + struct fotg210 *fotg = hcd_to_fotg210_priv(hcd)->fotg; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 val; int ret; - ret = readl_poll_timeout_atomic(ptr, result, - ((result & mask) == done || - result == U32_MAX), 1, usec); - if (result == U32_MAX) /* card removed */ - return -ENODEV; - - return ret; -} - -/* Force HC to halt state from unknown (EHCI spec section 2.3). - * Must be called with interrupts enabled and the lock not held. - */ -static int fotg210_halt(struct fotg210_hcd *fotg210) -{ - u32 temp; - - spin_lock_irq(&fotg210->lock); - - /* disable any irqs left enabled by previous code */ - fotg210_writel(fotg210, 0, &fotg210->regs->intr_enable); - - /* - * This routine gets called during probe before fotg210->command - * has been initialized, so we can't rely on its value. - */ - fotg210->command &= ~CMD_RUN; - temp = fotg210_readl(fotg210, &fotg210->regs->command); - temp &= ~(CMD_RUN | CMD_IAAD); - fotg210_writel(fotg210, temp, &fotg210->regs->command); - - spin_unlock_irq(&fotg210->lock); - synchronize_irq(fotg210_to_hcd(fotg210)->irq); - - return handshake(fotg210, &fotg210->regs->status, - STS_HALT, STS_HALT, 16 * 125); -} - -/* Reset a non-running (STS_HALT == 1) controller. - * Must be called with interrupts enabled and the lock not held. - */ -static int fotg210_reset(struct fotg210_hcd *fotg210) -{ - int retval; - u32 command = fotg210_readl(fotg210, &fotg210->regs->command); - - /* If the EHCI debug controller is active, special care must be - * taken before and after a host controller reset - */ - if (fotg210->debug && !dbgp_reset_prep(fotg210_to_hcd(fotg210))) - fotg210->debug = NULL; - - command |= CMD_RESET; - dbg_cmd(fotg210, "reset", command); - fotg210_writel(fotg210, command, &fotg210->regs->command); - fotg210->rh_state = FOTG210_RH_HALTED; - fotg210->next_statechange = jiffies; - retval = handshake(fotg210, &fotg210->regs->command, - CMD_RESET, 0, 250 * 1000); - - if (retval) - return retval; - - if (fotg210->debug) - dbgp_external_startup(fotg210_to_hcd(fotg210)); - - fotg210->port_c_suspend = fotg210->suspended_ports = - fotg210->resuming_ports = 0; - return retval; -} - -/* Idle the controller (turn off the schedules). - * Must be called with interrupts enabled and the lock not held. - */ -static void fotg210_quiesce(struct fotg210_hcd *fotg210) -{ - u32 temp; - - if (fotg210->rh_state != FOTG210_RH_RUNNING) - return; - - /* wait for any schedule enables/disables to take effect */ - temp = (fotg210->command << 10) & (STS_ASS | STS_PSS); - handshake(fotg210, &fotg210->regs->status, STS_ASS | STS_PSS, temp, - 16 * 125); - - /* then disable anything that's still active */ - spin_lock_irq(&fotg210->lock); - fotg210->command &= ~(CMD_ASE | CMD_PSE); - fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); - spin_unlock_irq(&fotg210->lock); - - /* hardware can take 16 microframes to turn off ... */ - handshake(fotg210, &fotg210->regs->status, STS_ASS | STS_PSS, 0, - 16 * 125); -} - -static void end_unlink_async(struct fotg210_hcd *fotg210); -static void unlink_empty_async(struct fotg210_hcd *fotg210); -static void fotg210_work(struct fotg210_hcd *fotg210); -static void start_unlink_intr(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh); -static void end_unlink_intr(struct fotg210_hcd *fotg210, struct fotg210_qh *qh); - -/* Set a bit in the USBCMD register */ -static void fotg210_set_command_bit(struct fotg210_hcd *fotg210, u32 bit) -{ - fotg210->command |= bit; - fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); - - /* unblock posted write */ - fotg210_readl(fotg210, &fotg210->regs->command); -} - -/* Clear a bit in the USBCMD register */ -static void fotg210_clear_command_bit(struct fotg210_hcd *fotg210, u32 bit) -{ - fotg210->command &= ~bit; - fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); - - /* unblock posted write */ - fotg210_readl(fotg210, &fotg210->regs->command); -} - -/* EHCI timer support... Now using hrtimers. - * - * Lots of different events are triggered from fotg210->hrtimer. Whenever - * the timer routine runs, it checks each possible event; events that are - * currently enabled and whose expiration time has passed get handled. - * The set of enabled events is stored as a collection of bitflags in - * fotg210->enabled_hrtimer_events, and they are numbered in order of - * increasing delay values (ranging between 1 ms and 100 ms). - * - * Rather than implementing a sorted list or tree of all pending events, - * we keep track only of the lowest-numbered pending event, in - * fotg210->next_hrtimer_event. Whenever fotg210->hrtimer gets restarted, its - * expiration time is set to the timeout value for this event. - * - * As a result, events might not get handled right away; the actual delay - * could be anywhere up to twice the requested delay. This doesn't - * matter, because none of the events are especially time-critical. The - * ones that matter most all have a delay of 1 ms, so they will be - * handled after 2 ms at most, which is okay. In addition to this, we - * allow for an expiration range of 1 ms. - */ - -/* Delay lengths for the hrtimer event types. - * Keep this list sorted by delay length, in the same order as - * the event types indexed by enum fotg210_hrtimer_event in fotg210.h. - */ -static unsigned event_delays_ns[] = { - 1 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_POLL_ASS */ - 1 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_POLL_PSS */ - 1 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_POLL_DEAD */ - 1125 * NSEC_PER_USEC, /* FOTG210_HRTIMER_UNLINK_INTR */ - 2 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_FREE_ITDS */ - 6 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_ASYNC_UNLINKS */ - 10 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_IAA_WATCHDOG */ - 10 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_DISABLE_PERIODIC */ - 15 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_DISABLE_ASYNC */ - 100 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_IO_WATCHDOG */ -}; - -/* Enable a pending hrtimer event */ -static void fotg210_enable_event(struct fotg210_hcd *fotg210, unsigned event, - bool resched) -{ - ktime_t *timeout = &fotg210->hr_timeouts[event]; - - if (resched) - *timeout = ktime_add(ktime_get(), event_delays_ns[event]); - fotg210->enabled_hrtimer_events |= (1 << event); - - /* Track only the lowest-numbered pending event */ - if (event < fotg210->next_hrtimer_event) { - fotg210->next_hrtimer_event = event; - hrtimer_start_range_ns(&fotg210->hrtimer, *timeout, - NSEC_PER_MSEC, HRTIMER_MODE_ABS); - } -} - - -/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ -static void fotg210_poll_ASS(struct fotg210_hcd *fotg210) -{ - unsigned actual, want; - - /* Don't enable anything if the controller isn't running (e.g., died) */ - if (fotg210->rh_state != FOTG210_RH_RUNNING) - return; - - want = (fotg210->command & CMD_ASE) ? STS_ASS : 0; - actual = fotg210_readl(fotg210, &fotg210->regs->status) & STS_ASS; - - if (want != actual) { - - /* Poll again later, but give up after about 20 ms */ - if (fotg210->ASS_poll_count++ < 20) { - fotg210_enable_event(fotg210, FOTG210_HRTIMER_POLL_ASS, - true); - return; - } - fotg210_dbg(fotg210, "Waited too long for the async schedule status (%x/%x), giving up\n", - want, actual); - } - fotg210->ASS_poll_count = 0; - - /* The status is up-to-date; restart or stop the schedule as needed */ - if (want == 0) { /* Stopped */ - if (fotg210->async_count > 0) - fotg210_set_command_bit(fotg210, CMD_ASE); - - } else { /* Running */ - if (fotg210->async_count == 0) { - - /* Turn off the schedule after a while */ - fotg210_enable_event(fotg210, - FOTG210_HRTIMER_DISABLE_ASYNC, - true); - } - } -} - -/* Turn off the async schedule after a brief delay */ -static void fotg210_disable_ASE(struct fotg210_hcd *fotg210) -{ - fotg210_clear_command_bit(fotg210, CMD_ASE); -} - - -/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ -static void fotg210_poll_PSS(struct fotg210_hcd *fotg210) -{ - unsigned actual, want; - - /* Don't do anything if the controller isn't running (e.g., died) */ - if (fotg210->rh_state != FOTG210_RH_RUNNING) - return; - - want = (fotg210->command & CMD_PSE) ? STS_PSS : 0; - actual = fotg210_readl(fotg210, &fotg210->regs->status) & STS_PSS; - - if (want != actual) { - - /* Poll again later, but give up after about 20 ms */ - if (fotg210->PSS_poll_count++ < 20) { - fotg210_enable_event(fotg210, FOTG210_HRTIMER_POLL_PSS, - true); - return; - } - fotg210_dbg(fotg210, "Waited too long for the periodic schedule status (%x/%x), giving up\n", - want, actual); - } - fotg210->PSS_poll_count = 0; - - /* The status is up-to-date; restart or stop the schedule as needed */ - if (want == 0) { /* Stopped */ - if (fotg210->periodic_count > 0) - fotg210_set_command_bit(fotg210, CMD_PSE); - - } else { /* Running */ - if (fotg210->periodic_count == 0) { - - /* Turn off the schedule after a while */ - fotg210_enable_event(fotg210, - FOTG210_HRTIMER_DISABLE_PERIODIC, - true); - } - } -} - -/* Turn off the periodic schedule after a brief delay */ -static void fotg210_disable_PSE(struct fotg210_hcd *fotg210) -{ - fotg210_clear_command_bit(fotg210, CMD_PSE); -} - - -/* Poll the STS_HALT status bit; see when a dead controller stops */ -static void fotg210_handle_controller_death(struct fotg210_hcd *fotg210) -{ - if (!(fotg210_readl(fotg210, &fotg210->regs->status) & STS_HALT)) { - - /* Give up after a few milliseconds */ - if (fotg210->died_poll_count++ < 5) { - /* Try again later */ - fotg210_enable_event(fotg210, - FOTG210_HRTIMER_POLL_DEAD, true); - return; - } - fotg210_warn(fotg210, "Waited too long for the controller to stop, giving up\n"); - } - - /* Clean up the mess */ - fotg210->rh_state = FOTG210_RH_HALTED; - fotg210_writel(fotg210, 0, &fotg210->regs->intr_enable); - fotg210_work(fotg210); - end_unlink_async(fotg210); - - /* Not in process context, so don't try to reset the controller */ -} - - -/* Handle unlinked interrupt QHs once they are gone from the hardware */ -static void fotg210_handle_intr_unlinks(struct fotg210_hcd *fotg210) -{ - bool stopped = (fotg210->rh_state < FOTG210_RH_RUNNING); - - /* - * Process all the QHs on the intr_unlink list that were added - * before the current unlink cycle began. The list is in - * temporal order, so stop when we reach the first entry in the - * current cycle. But if the root hub isn't running then - * process all the QHs on the list. - */ - fotg210->intr_unlinking = true; - while (fotg210->intr_unlink) { - struct fotg210_qh *qh = fotg210->intr_unlink; - - if (!stopped && qh->unlink_cycle == fotg210->intr_unlink_cycle) - break; - fotg210->intr_unlink = qh->unlink_next; - qh->unlink_next = NULL; - end_unlink_intr(fotg210, qh); - } - - /* Handle remaining entries later */ - if (fotg210->intr_unlink) { - fotg210_enable_event(fotg210, FOTG210_HRTIMER_UNLINK_INTR, - true); - ++fotg210->intr_unlink_cycle; - } - fotg210->intr_unlinking = false; -} - - -/* Start another free-iTDs/siTDs cycle */ -static void start_free_itds(struct fotg210_hcd *fotg210) -{ - if (!(fotg210->enabled_hrtimer_events & - BIT(FOTG210_HRTIMER_FREE_ITDS))) { - fotg210->last_itd_to_free = list_entry( - fotg210->cached_itd_list.prev, - struct fotg210_itd, itd_list); - fotg210_enable_event(fotg210, FOTG210_HRTIMER_FREE_ITDS, true); - } -} - -/* Wait for controller to stop using old iTDs and siTDs */ -static void end_free_itds(struct fotg210_hcd *fotg210) -{ - struct fotg210_itd *itd, *n; - - if (fotg210->rh_state < FOTG210_RH_RUNNING) - fotg210->last_itd_to_free = NULL; - - list_for_each_entry_safe(itd, n, &fotg210->cached_itd_list, itd_list) { - list_del(&itd->itd_list); - dma_pool_free(fotg210->itd_pool, itd, itd->itd_dma); - if (itd == fotg210->last_itd_to_free) - break; - } - - if (!list_empty(&fotg210->cached_itd_list)) - start_free_itds(fotg210); -} - - -/* Handle lost (or very late) IAA interrupts */ -static void fotg210_iaa_watchdog(struct fotg210_hcd *fotg210) -{ - if (fotg210->rh_state != FOTG210_RH_RUNNING) - return; - - /* - * Lost IAA irqs wedge things badly; seen first with a vt8235. - * So we need this watchdog, but must protect it against both - * (a) SMP races against real IAA firing and retriggering, and - * (b) clean HC shutdown, when IAA watchdog was pending. - */ - if (fotg210->async_iaa) { - u32 cmd, status; - - /* If we get here, IAA is *REALLY* late. It's barely - * conceivable that the system is so busy that CMD_IAAD - * is still legitimately set, so let's be sure it's - * clear before we read STS_IAA. (The HC should clear - * CMD_IAAD when it sets STS_IAA.) - */ - cmd = fotg210_readl(fotg210, &fotg210->regs->command); - - /* - * If IAA is set here it either legitimately triggered - * after the watchdog timer expired (_way_ late, so we'll - * still count it as lost) ... or a silicon erratum: - * - VIA seems to set IAA without triggering the IRQ; - * - IAAD potentially cleared without setting IAA. - */ - status = fotg210_readl(fotg210, &fotg210->regs->status); - if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { - INCR(fotg210->stats.lost_iaa); - fotg210_writel(fotg210, STS_IAA, - &fotg210->regs->status); - } - - fotg210_dbg(fotg210, "IAA watchdog: status %x cmd %x\n", - status, cmd); - end_unlink_async(fotg210); - } -} - - -/* Enable the I/O watchdog, if appropriate */ -static void turn_on_io_watchdog(struct fotg210_hcd *fotg210) -{ - /* Not needed if the controller isn't running or it's already enabled */ - if (fotg210->rh_state != FOTG210_RH_RUNNING || - (fotg210->enabled_hrtimer_events & - BIT(FOTG210_HRTIMER_IO_WATCHDOG))) - return; - - /* - * Isochronous transfers always need the watchdog. - * For other sorts we use it only if the flag is set. - */ - if (fotg210->isoc_count > 0 || (fotg210->need_io_watchdog && - fotg210->async_count + fotg210->intr_count > 0)) - fotg210_enable_event(fotg210, FOTG210_HRTIMER_IO_WATCHDOG, - true); -} - - -/* Handler functions for the hrtimer event types. - * Keep this array in the same order as the event types indexed by - * enum fotg210_hrtimer_event in fotg210.h. - */ -static void (*event_handlers[])(struct fotg210_hcd *) = { - fotg210_poll_ASS, /* FOTG210_HRTIMER_POLL_ASS */ - fotg210_poll_PSS, /* FOTG210_HRTIMER_POLL_PSS */ - fotg210_handle_controller_death, /* FOTG210_HRTIMER_POLL_DEAD */ - fotg210_handle_intr_unlinks, /* FOTG210_HRTIMER_UNLINK_INTR */ - end_free_itds, /* FOTG210_HRTIMER_FREE_ITDS */ - unlink_empty_async, /* FOTG210_HRTIMER_ASYNC_UNLINKS */ - fotg210_iaa_watchdog, /* FOTG210_HRTIMER_IAA_WATCHDOG */ - fotg210_disable_PSE, /* FOTG210_HRTIMER_DISABLE_PERIODIC */ - fotg210_disable_ASE, /* FOTG210_HRTIMER_DISABLE_ASYNC */ - fotg210_work, /* FOTG210_HRTIMER_IO_WATCHDOG */ -}; - -static enum hrtimer_restart fotg210_hrtimer_func(struct hrtimer *t) -{ - struct fotg210_hcd *fotg210 = - container_of(t, struct fotg210_hcd, hrtimer); - ktime_t now; - unsigned long events; - unsigned long flags; - unsigned e; - - spin_lock_irqsave(&fotg210->lock, flags); - - events = fotg210->enabled_hrtimer_events; - fotg210->enabled_hrtimer_events = 0; - fotg210->next_hrtimer_event = FOTG210_HRTIMER_NO_EVENT; - - /* - * Check each pending event. If its time has expired, handle - * the event; otherwise re-enable it. - */ - now = ktime_get(); - for_each_set_bit(e, &events, FOTG210_HRTIMER_NUM_EVENTS) { - if (ktime_compare(now, fotg210->hr_timeouts[e]) >= 0) - event_handlers[e](fotg210); - else - fotg210_enable_event(fotg210, e, false); - } - - spin_unlock_irqrestore(&fotg210->lock, flags); - return HRTIMER_NORESTART; -} - -#define fotg210_bus_suspend NULL -#define fotg210_bus_resume NULL - -static int check_reset_complete(struct fotg210_hcd *fotg210, int index, - u32 __iomem *status_reg, int port_status) -{ - if (!(port_status & PORT_CONNECT)) - return port_status; - - /* if reset finished and it's still not enabled -- handoff */ - if (!(port_status & PORT_PE)) - /* with integrated TT, there's nobody to hand it to! */ - fotg210_dbg(fotg210, "Failed to enable port %d on root hub TT\n", - index + 1); - else - fotg210_dbg(fotg210, "port %d reset complete, port enabled\n", - index + 1); - - return port_status; -} - - -/* build "status change" packet (one or two bytes) from HC registers */ - -static int fotg210_hub_status_data(struct usb_hcd *hcd, char *buf) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - u32 temp, status; - u32 mask; - int retval = 1; - unsigned long flags; - - /* init status to no-changes */ - buf[0] = 0; - - /* Inform the core about resumes-in-progress by returning - * a non-zero value even if there are no status changes. - */ - status = fotg210->resuming_ports; - - mask = PORT_CSC | PORT_PEC; - /* PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND */ - - /* no hub change reports (bit 0) for now (power, ...) */ - - /* port N changes (bit N)? */ - spin_lock_irqsave(&fotg210->lock, flags); - - temp = fotg210_readl(fotg210, &fotg210->regs->port_status); - - /* - * Return status information even for ports with OWNER set. - * Otherwise hub_wq wouldn't see the disconnect event when a - * high-speed device is switched over to the companion - * controller by the user. - */ - - if ((temp & mask) != 0 || test_bit(0, &fotg210->port_c_suspend) || - (fotg210->reset_done[0] && - time_after_eq(jiffies, fotg210->reset_done[0]))) { - buf[0] |= 1 << 1; - status = STS_PCD; - } - /* FIXME autosuspend idle root hubs */ - spin_unlock_irqrestore(&fotg210->lock, flags); - return status ? retval : 0; -} - -static void fotg210_hub_descriptor(struct fotg210_hcd *fotg210, - struct usb_hub_descriptor *desc) -{ - int ports = HCS_N_PORTS(fotg210->hcs_params); - u16 temp; - - desc->bDescriptorType = USB_DT_HUB; - desc->bPwrOn2PwrGood = 10; /* fotg210 1.0, 2.3.9 says 20ms max */ - desc->bHubContrCurrent = 0; - - desc->bNbrPorts = ports; - temp = 1 + (ports / 8); - desc->bDescLength = 7 + 2 * temp; - - /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ - memset(&desc->u.hs.DeviceRemovable[0], 0, temp); - memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - - temp = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ - temp |= HUB_CHAR_NO_LPSM; /* no power switching */ - desc->wHubCharacteristics = cpu_to_le16(temp); -} - -static int fotg210_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, - u16 wIndex, char *buf, u16 wLength) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - int ports = HCS_N_PORTS(fotg210->hcs_params); - u32 __iomem *status_reg = &fotg210->regs->port_status; - u32 temp, temp1, status; - unsigned long flags; - int retval = 0; - unsigned selector; - - /* - * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. - * HCS_INDICATOR may say we can change LEDs to off/amber/green. - * (track current state ourselves) ... blink for diagnostics, - * power, "this is the one", etc. EHCI spec supports this. - */ - - spin_lock_irqsave(&fotg210->lock, flags); - switch (typeReq) { - case ClearHubFeature: - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* no hub-wide feature/status flags */ - break; - default: - goto error; - } - break; - case ClearPortFeature: - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - temp = fotg210_readl(fotg210, status_reg); - temp &= ~PORT_RWC_BITS; - - /* - * Even if OWNER is set, so the port is owned by the - * companion controller, hub_wq needs to be able to clear - * the port-change status bits (especially - * USB_PORT_STAT_C_CONNECTION). - */ - - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - fotg210_writel(fotg210, temp & ~PORT_PE, status_reg); - break; - case USB_PORT_FEAT_C_ENABLE: - fotg210_writel(fotg210, temp | PORT_PEC, status_reg); - break; - case USB_PORT_FEAT_SUSPEND: - if (temp & PORT_RESET) - goto error; - if (!(temp & PORT_SUSPEND)) - break; - if ((temp & PORT_PE) == 0) - goto error; - - /* resume signaling for 20 msec */ - fotg210_writel(fotg210, temp | PORT_RESUME, status_reg); - fotg210->reset_done[wIndex] = jiffies - + msecs_to_jiffies(USB_RESUME_TIMEOUT); - break; - case USB_PORT_FEAT_C_SUSPEND: - clear_bit(wIndex, &fotg210->port_c_suspend); - break; - case USB_PORT_FEAT_C_CONNECTION: - fotg210_writel(fotg210, temp | PORT_CSC, status_reg); - break; - case USB_PORT_FEAT_C_OVER_CURRENT: - fotg210_writel(fotg210, temp | OTGISR_OVC, - &fotg210->regs->otgisr); - break; - case USB_PORT_FEAT_C_RESET: - /* GetPortStatus clears reset */ - break; - default: - goto error; - } - fotg210_readl(fotg210, &fotg210->regs->command); - break; - case GetHubDescriptor: - fotg210_hub_descriptor(fotg210, (struct usb_hub_descriptor *) - buf); - break; - case GetHubStatus: - /* no hub-wide feature/status flags */ - memset(buf, 0, 4); - /*cpu_to_le32s ((u32 *) buf); */ - break; - case GetPortStatus: - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - status = 0; - temp = fotg210_readl(fotg210, status_reg); - - /* wPortChange bits */ - if (temp & PORT_CSC) - status |= USB_PORT_STAT_C_CONNECTION << 16; - if (temp & PORT_PEC) - status |= USB_PORT_STAT_C_ENABLE << 16; - - temp1 = fotg210_readl(fotg210, &fotg210->regs->otgisr); - if (temp1 & OTGISR_OVC) - status |= USB_PORT_STAT_C_OVERCURRENT << 16; - - /* whoever resumes must GetPortStatus to complete it!! */ - if (temp & PORT_RESUME) { - - /* Remote Wakeup received? */ - if (!fotg210->reset_done[wIndex]) { - /* resume signaling for 20 msec */ - fotg210->reset_done[wIndex] = jiffies - + msecs_to_jiffies(20); - /* check the port again */ - mod_timer(&fotg210_to_hcd(fotg210)->rh_timer, - fotg210->reset_done[wIndex]); - } - - /* resume completed? */ - else if (time_after_eq(jiffies, - fotg210->reset_done[wIndex])) { - clear_bit(wIndex, &fotg210->suspended_ports); - set_bit(wIndex, &fotg210->port_c_suspend); - fotg210->reset_done[wIndex] = 0; - - /* stop resume signaling */ - temp = fotg210_readl(fotg210, status_reg); - fotg210_writel(fotg210, temp & - ~(PORT_RWC_BITS | PORT_RESUME), - status_reg); - clear_bit(wIndex, &fotg210->resuming_ports); - retval = handshake(fotg210, status_reg, - PORT_RESUME, 0, 2000);/* 2ms */ - if (retval != 0) { - fotg210_err(fotg210, - "port %d resume error %d\n", - wIndex + 1, retval); - goto error; - } - temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); - } - } - - /* whoever resets must GetPortStatus to complete it!! */ - if ((temp & PORT_RESET) && time_after_eq(jiffies, - fotg210->reset_done[wIndex])) { - status |= USB_PORT_STAT_C_RESET << 16; - fotg210->reset_done[wIndex] = 0; - clear_bit(wIndex, &fotg210->resuming_ports); - - /* force reset to complete */ - fotg210_writel(fotg210, - temp & ~(PORT_RWC_BITS | PORT_RESET), - status_reg); - /* REVISIT: some hardware needs 550+ usec to clear - * this bit; seems too long to spin routinely... - */ - retval = handshake(fotg210, status_reg, - PORT_RESET, 0, 1000); - if (retval != 0) { - fotg210_err(fotg210, "port %d reset error %d\n", - wIndex + 1, retval); - goto error; - } - - /* see what we found out */ - temp = check_reset_complete(fotg210, wIndex, status_reg, - fotg210_readl(fotg210, status_reg)); - - /* restart schedule */ - fotg210->command |= CMD_RUN; - fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); - } - - if (!(temp & (PORT_RESUME|PORT_RESET))) { - fotg210->reset_done[wIndex] = 0; - clear_bit(wIndex, &fotg210->resuming_ports); - } - - /* transfer dedicated ports to the companion hc */ - if ((temp & PORT_CONNECT) && - test_bit(wIndex, &fotg210->companion_ports)) { - temp &= ~PORT_RWC_BITS; - fotg210_writel(fotg210, temp, status_reg); - fotg210_dbg(fotg210, "port %d --> companion\n", - wIndex + 1); - temp = fotg210_readl(fotg210, status_reg); - } - - /* - * Even if OWNER is set, there's no harm letting hub_wq - * see the wPortStatus values (they should all be 0 except - * for PORT_POWER anyway). - */ - - if (temp & PORT_CONNECT) { - status |= USB_PORT_STAT_CONNECTION; - status |= fotg210_port_speed(fotg210, temp); - } - if (temp & PORT_PE) - status |= USB_PORT_STAT_ENABLE; - - /* maybe the port was unsuspended without our knowledge */ - if (temp & (PORT_SUSPEND|PORT_RESUME)) { - status |= USB_PORT_STAT_SUSPEND; - } else if (test_bit(wIndex, &fotg210->suspended_ports)) { - clear_bit(wIndex, &fotg210->suspended_ports); - clear_bit(wIndex, &fotg210->resuming_ports); - fotg210->reset_done[wIndex] = 0; - if (temp & PORT_PE) - set_bit(wIndex, &fotg210->port_c_suspend); - } - - temp1 = fotg210_readl(fotg210, &fotg210->regs->otgisr); - if (temp1 & OTGISR_OVC) - status |= USB_PORT_STAT_OVERCURRENT; - if (temp & PORT_RESET) - status |= USB_PORT_STAT_RESET; - if (test_bit(wIndex, &fotg210->port_c_suspend)) - status |= USB_PORT_STAT_C_SUSPEND << 16; - - if (status & ~0xffff) /* only if wPortChange is interesting */ - dbg_port(fotg210, "GetStatus", wIndex + 1, temp); - put_unaligned_le32(status, buf); - break; - case SetHubFeature: - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* no hub-wide feature/status flags */ - break; - default: - goto error; - } - break; - case SetPortFeature: - selector = wIndex >> 8; - wIndex &= 0xff; - - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - temp = fotg210_readl(fotg210, status_reg); - temp &= ~PORT_RWC_BITS; - switch (wValue) { - case USB_PORT_FEAT_SUSPEND: - if ((temp & PORT_PE) == 0 - || (temp & PORT_RESET) != 0) - goto error; - - /* After above check the port must be connected. - * Set appropriate bit thus could put phy into low power - * mode if we have hostpc feature - */ - fotg210_writel(fotg210, temp | PORT_SUSPEND, - status_reg); - set_bit(wIndex, &fotg210->suspended_ports); - break; - case USB_PORT_FEAT_RESET: - if (temp & PORT_RESUME) - goto error; - /* line status bits may report this as low speed, - * which can be fine if this root hub has a - * transaction translator built in. - */ - fotg210_dbg(fotg210, "port %d reset\n", wIndex + 1); - temp |= PORT_RESET; - temp &= ~PORT_PE; - - /* - * caller must wait, then call GetPortStatus - * usb 2.0 spec says 50 ms resets on root - */ - fotg210->reset_done[wIndex] = jiffies - + msecs_to_jiffies(50); - fotg210_writel(fotg210, temp, status_reg); - break; - - /* For downstream facing ports (these): one hub port is put - * into test mode according to USB2 11.24.2.13, then the hub - * must be reset (which for root hub now means rmmod+modprobe, - * or else system reboot). See EHCI 2.3.9 and 4.14 for info - * about the EHCI-specific stuff. - */ - case USB_PORT_FEAT_TEST: - if (!selector || selector > 5) - goto error; - spin_unlock_irqrestore(&fotg210->lock, flags); - fotg210_quiesce(fotg210); - spin_lock_irqsave(&fotg210->lock, flags); - - /* Put all enabled ports into suspend */ - temp = fotg210_readl(fotg210, status_reg) & - ~PORT_RWC_BITS; - if (temp & PORT_PE) - fotg210_writel(fotg210, temp | PORT_SUSPEND, - status_reg); - - spin_unlock_irqrestore(&fotg210->lock, flags); - fotg210_halt(fotg210); - spin_lock_irqsave(&fotg210->lock, flags); - - temp = fotg210_readl(fotg210, status_reg); - temp |= selector << 16; - fotg210_writel(fotg210, temp, status_reg); - break; - - default: - goto error; - } - fotg210_readl(fotg210, &fotg210->regs->command); - break; - - default: -error: - /* "stall" on error */ - retval = -EPIPE; - } - spin_unlock_irqrestore(&fotg210->lock, flags); - return retval; -} - -static void __maybe_unused fotg210_relinquish_port(struct usb_hcd *hcd, - int portnum) -{ - return; -} - -static int __maybe_unused fotg210_port_handed_over(struct usb_hcd *hcd, - int portnum) -{ - return 0; -} - -/* There's basically three types of memory: - * - data used only by the HCD ... kmalloc is fine - * - async and periodic schedules, shared by HC and HCD ... these - * need to use dma_pool or dma_alloc_coherent - * - driver buffers, read/written by HC ... single shot DMA mapped - * - * There's also "register" data (e.g. PCI or SOC), which is memory mapped. - * No memory seen by this driver is pageable. - */ - -/* Allocate the key transfer structures from the previously allocated pool */ -static inline void fotg210_qtd_init(struct fotg210_hcd *fotg210, - struct fotg210_qtd *qtd, dma_addr_t dma) -{ - memset(qtd, 0, sizeof(*qtd)); - qtd->qtd_dma = dma; - qtd->hw_token = cpu_to_hc32(fotg210, QTD_STS_HALT); - qtd->hw_next = FOTG210_LIST_END(fotg210); - qtd->hw_alt_next = FOTG210_LIST_END(fotg210); - INIT_LIST_HEAD(&qtd->qtd_list); -} - -static struct fotg210_qtd *fotg210_qtd_alloc(struct fotg210_hcd *fotg210, - gfp_t flags) -{ - struct fotg210_qtd *qtd; - dma_addr_t dma; - - qtd = dma_pool_alloc(fotg210->qtd_pool, flags, &dma); - if (qtd != NULL) - fotg210_qtd_init(fotg210, qtd, dma); - - return qtd; -} - -static inline void fotg210_qtd_free(struct fotg210_hcd *fotg210, - struct fotg210_qtd *qtd) -{ - dma_pool_free(fotg210->qtd_pool, qtd, qtd->qtd_dma); -} - - -static void qh_destroy(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - /* clean qtds first, and know this is not linked */ - if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) { - fotg210_dbg(fotg210, "unused qh not empty!\n"); - BUG(); - } - if (qh->dummy) - fotg210_qtd_free(fotg210, qh->dummy); - dma_pool_free(fotg210->qh_pool, qh->hw, qh->qh_dma); - kfree(qh); -} - -static struct fotg210_qh *fotg210_qh_alloc(struct fotg210_hcd *fotg210, - gfp_t flags) -{ - struct fotg210_qh *qh; - dma_addr_t dma; - - qh = kzalloc_obj(*qh, GFP_ATOMIC); - if (!qh) - goto done; - qh->hw = (struct fotg210_qh_hw *) - dma_pool_zalloc(fotg210->qh_pool, flags, &dma); - if (!qh->hw) - goto fail; - qh->qh_dma = dma; - INIT_LIST_HEAD(&qh->qtd_list); - - /* dummy td enables safe urb queuing */ - qh->dummy = fotg210_qtd_alloc(fotg210, flags); - if (qh->dummy == NULL) { - fotg210_dbg(fotg210, "no dummy td\n"); - goto fail1; - } -done: - return qh; -fail1: - dma_pool_free(fotg210->qh_pool, qh->hw, qh->qh_dma); -fail: - kfree(qh); - return NULL; -} - -/* The queue heads and transfer descriptors are managed from pools tied - * to each of the "per device" structures. - * This is the initialisation and cleanup code. - */ - -static void fotg210_mem_cleanup(struct fotg210_hcd *fotg210) -{ - if (fotg210->async) - qh_destroy(fotg210, fotg210->async); - fotg210->async = NULL; - - if (fotg210->dummy) - qh_destroy(fotg210, fotg210->dummy); - fotg210->dummy = NULL; - - /* DMA consistent memory and pools */ - dma_pool_destroy(fotg210->qtd_pool); - fotg210->qtd_pool = NULL; - - dma_pool_destroy(fotg210->qh_pool); - fotg210->qh_pool = NULL; - - dma_pool_destroy(fotg210->itd_pool); - fotg210->itd_pool = NULL; - - if (fotg210->periodic) - dma_free_coherent(fotg210_to_hcd(fotg210)->self.controller, - fotg210->periodic_size * sizeof(u32), - fotg210->periodic, fotg210->periodic_dma); - fotg210->periodic = NULL; - - /* shadow periodic table */ - kfree(fotg210->pshadow); - fotg210->pshadow = NULL; -} - -/* remember to add cleanup code (above) if you add anything here */ -static int fotg210_mem_init(struct fotg210_hcd *fotg210, gfp_t flags) -{ - int i; - - /* QTDs for control/bulk/intr transfers */ - fotg210->qtd_pool = dma_pool_create("fotg210_qtd", - fotg210_to_hcd(fotg210)->self.controller, - sizeof(struct fotg210_qtd), - 32 /* byte alignment (for hw parts) */, - 4096 /* can't cross 4K */); - if (!fotg210->qtd_pool) - goto fail; - - /* QHs for control/bulk/intr transfers */ - fotg210->qh_pool = dma_pool_create("fotg210_qh", - fotg210_to_hcd(fotg210)->self.controller, - sizeof(struct fotg210_qh_hw), - 32 /* byte alignment (for hw parts) */, - 4096 /* can't cross 4K */); - if (!fotg210->qh_pool) - goto fail; - - fotg210->async = fotg210_qh_alloc(fotg210, flags); - if (!fotg210->async) - goto fail; - - /* ITD for high speed ISO transfers */ - fotg210->itd_pool = dma_pool_create("fotg210_itd", - fotg210_to_hcd(fotg210)->self.controller, - sizeof(struct fotg210_itd), - 64 /* byte alignment (for hw parts) */, - 4096 /* can't cross 4K */); - if (!fotg210->itd_pool) - goto fail; - - /* Hardware periodic table */ - fotg210->periodic = - dma_alloc_coherent(fotg210_to_hcd(fotg210)->self.controller, - fotg210->periodic_size * sizeof(__le32), - &fotg210->periodic_dma, 0); - if (fotg210->periodic == NULL) - goto fail; - - for (i = 0; i < fotg210->periodic_size; i++) - fotg210->periodic[i] = FOTG210_LIST_END(fotg210); - - /* software shadow of hardware table */ - fotg210->pshadow = kcalloc(fotg210->periodic_size, sizeof(void *), - flags); - if (fotg210->pshadow != NULL) - return 0; - -fail: - fotg210_dbg(fotg210, "couldn't init memory\n"); - fotg210_mem_cleanup(fotg210); - return -ENOMEM; -} -/* EHCI hardware queue manipulation ... the core. QH/QTD manipulation. - * - * Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" - * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned - * buffers needed for the larger number). We use one QH per endpoint, queue - * multiple urbs (all three types) per endpoint. URBs may need several qtds. - * - * ISO traffic uses "ISO TD" (itd) records, and (along with - * interrupts) needs careful scheduling. Performance improvements can be - * an ongoing challenge. That's in "ehci-sched.c". - * - * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, - * or otherwise through transaction translators (TTs) in USB 2.0 hubs using - * (b) special fields in qh entries or (c) split iso entries. TTs will - * buffer low/full speed data so the host collects it at high speed. - */ - -/* fill a qtd, returning how much of the buffer we were able to queue up */ -static int qtd_fill(struct fotg210_hcd *fotg210, struct fotg210_qtd *qtd, - dma_addr_t buf, size_t len, int token, int maxpacket) -{ - int i, count; - u64 addr = buf; - - /* one buffer entry per 4K ... first might be short or unaligned */ - qtd->hw_buf[0] = cpu_to_hc32(fotg210, (u32)addr); - qtd->hw_buf_hi[0] = cpu_to_hc32(fotg210, (u32)(addr >> 32)); - count = 0x1000 - (buf & 0x0fff); /* rest of that page */ - if (likely(len < count)) /* ... iff needed */ - count = len; - else { - buf += 0x1000; - buf &= ~0x0fff; - - /* per-qtd limit: from 16K to 20K (best alignment) */ - for (i = 1; count < len && i < 5; i++) { - addr = buf; - qtd->hw_buf[i] = cpu_to_hc32(fotg210, (u32)addr); - qtd->hw_buf_hi[i] = cpu_to_hc32(fotg210, - (u32)(addr >> 32)); - buf += 0x1000; - if ((count + 0x1000) < len) - count += 0x1000; - else - count = len; - } - - /* short packets may only terminate transfers */ - if (count != len) - count -= (count % maxpacket); - } - qtd->hw_token = cpu_to_hc32(fotg210, (count << 16) | token); - qtd->length = count; - - return count; -} - -static inline void qh_update(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh, struct fotg210_qtd *qtd) -{ - struct fotg210_qh_hw *hw = qh->hw; - - /* writes to an active overlay are unsafe */ - BUG_ON(qh->qh_state != QH_STATE_IDLE); - - hw->hw_qtd_next = QTD_NEXT(fotg210, qtd->qtd_dma); - hw->hw_alt_next = FOTG210_LIST_END(fotg210); - - /* Except for control endpoints, we make hardware maintain data - * toggle (like OHCI) ... here (re)initialize the toggle in the QH, - * and set the pseudo-toggle in udev. Only usb_clear_halt() will - * ever clear it. - */ - if (!(hw->hw_info1 & cpu_to_hc32(fotg210, QH_TOGGLE_CTL))) { - unsigned is_out, epnum; - - is_out = qh->is_out; - epnum = (hc32_to_cpup(fotg210, &hw->hw_info1) >> 8) & 0x0f; - if (unlikely(!usb_gettoggle(qh->dev, epnum, is_out))) { - hw->hw_token &= ~cpu_to_hc32(fotg210, QTD_TOGGLE); - usb_settoggle(qh->dev, epnum, is_out, 1); - } - } - - hw->hw_token &= cpu_to_hc32(fotg210, QTD_TOGGLE | QTD_STS_PING); -} - -/* if it weren't for a common silicon quirk (writing the dummy into the qh - * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault - * recovery (including urb dequeue) would need software changes to a QH... - */ -static void qh_refresh(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - struct fotg210_qtd *qtd; - - if (list_empty(&qh->qtd_list)) - qtd = qh->dummy; - else { - qtd = list_entry(qh->qtd_list.next, - struct fotg210_qtd, qtd_list); - /* - * first qtd may already be partially processed. - * If we come here during unlink, the QH overlay region - * might have reference to the just unlinked qtd. The - * qtd is updated in qh_completions(). Update the QH - * overlay here. - */ - if (cpu_to_hc32(fotg210, qtd->qtd_dma) == qh->hw->hw_current) { - qh->hw->hw_qtd_next = qtd->hw_next; - qtd = NULL; - } - } - - if (qtd) - qh_update(fotg210, qh, qtd); -} - -static void qh_link_async(struct fotg210_hcd *fotg210, struct fotg210_qh *qh); - -static void fotg210_clear_tt_buffer_complete(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - struct fotg210_qh *qh = ep->hcpriv; - unsigned long flags; - - spin_lock_irqsave(&fotg210->lock, flags); - qh->clearing_tt = 0; - if (qh->qh_state == QH_STATE_IDLE && !list_empty(&qh->qtd_list) - && fotg210->rh_state == FOTG210_RH_RUNNING) - qh_link_async(fotg210, qh); - spin_unlock_irqrestore(&fotg210->lock, flags); -} - -static void fotg210_clear_tt_buffer(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh, struct urb *urb, u32 token) -{ - - /* If an async split transaction gets an error or is unlinked, - * the TT buffer may be left in an indeterminate state. We - * have to clear the TT buffer. - * - * Note: this routine is never called for Isochronous transfers. - */ - if (urb->dev->tt && !usb_pipeint(urb->pipe) && !qh->clearing_tt) { - struct usb_device *tt = urb->dev->tt->hub; - - dev_dbg(&tt->dev, - "clear tt buffer port %d, a%d ep%d t%08x\n", - urb->dev->ttport, urb->dev->devnum, - usb_pipeendpoint(urb->pipe), token); - - if (urb->dev->tt->hub != - fotg210_to_hcd(fotg210)->self.root_hub) { - if (usb_hub_clear_tt_buffer(urb) == 0) - qh->clearing_tt = 1; - } - } -} - -static int qtd_copy_status(struct fotg210_hcd *fotg210, struct urb *urb, - size_t length, u32 token) -{ - int status = -EINPROGRESS; - - /* count IN/OUT bytes, not SETUP (even short packets) */ - if (likely(QTD_PID(token) != 2)) - urb->actual_length += length - QTD_LENGTH(token); - - /* don't modify error codes */ - if (unlikely(urb->unlinked)) - return status; - - /* force cleanup after short read; not always an error */ - if (unlikely(IS_SHORT_READ(token))) - status = -EREMOTEIO; - - /* serious "can't proceed" faults reported by the hardware */ - if (token & QTD_STS_HALT) { - if (token & QTD_STS_BABBLE) { - /* FIXME "must" disable babbling device's port too */ - status = -EOVERFLOW; - /* CERR nonzero + halt --> stall */ - } else if (QTD_CERR(token)) { - status = -EPIPE; - - /* In theory, more than one of the following bits can be set - * since they are sticky and the transaction is retried. - * Which to test first is rather arbitrary. - */ - } else if (token & QTD_STS_MMF) { - /* fs/ls interrupt xfer missed the complete-split */ - status = -EPROTO; - } else if (token & QTD_STS_DBE) { - status = (QTD_PID(token) == 1) /* IN ? */ - ? -ENOSR /* hc couldn't read data */ - : -ECOMM; /* hc couldn't write data */ - } else if (token & QTD_STS_XACT) { - /* timeout, bad CRC, wrong PID, etc */ - fotg210_dbg(fotg210, "devpath %s ep%d%s 3strikes\n", - urb->dev->devpath, - usb_pipeendpoint(urb->pipe), - usb_pipein(urb->pipe) ? "in" : "out"); - status = -EPROTO; - } else { /* unknown */ - status = -EPROTO; - } - - fotg210_dbg(fotg210, - "dev%d ep%d%s qtd token %08x --> status %d\n", - usb_pipedevice(urb->pipe), - usb_pipeendpoint(urb->pipe), - usb_pipein(urb->pipe) ? "in" : "out", - token, status); - } - - return status; -} - -static void fotg210_urb_done(struct fotg210_hcd *fotg210, struct urb *urb, - int status) -__releases(fotg210->lock) -__acquires(fotg210->lock) -{ - if (likely(urb->hcpriv != NULL)) { - struct fotg210_qh *qh = (struct fotg210_qh *) urb->hcpriv; - - /* S-mask in a QH means it's an interrupt urb */ - if ((qh->hw->hw_info2 & cpu_to_hc32(fotg210, QH_SMASK)) != 0) { - - /* ... update hc-wide periodic stats (for usbfs) */ - fotg210_to_hcd(fotg210)->self.bandwidth_int_reqs--; - } - } - - if (unlikely(urb->unlinked)) { - INCR(fotg210->stats.unlink); - } else { - /* report non-error and short read status as zero */ - if (status == -EINPROGRESS || status == -EREMOTEIO) - status = 0; - INCR(fotg210->stats.complete); - } - -#ifdef FOTG210_URB_TRACE - fotg210_dbg(fotg210, - "%s %s urb %p ep%d%s status %d len %d/%d\n", - __func__, urb->dev->devpath, urb, - usb_pipeendpoint(urb->pipe), - usb_pipein(urb->pipe) ? "in" : "out", - status, - urb->actual_length, urb->transfer_buffer_length); -#endif - - /* complete() can reenter this HCD */ - usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), urb); - spin_unlock(&fotg210->lock); - usb_hcd_giveback_urb(fotg210_to_hcd(fotg210), urb, status); - spin_lock(&fotg210->lock); -} - -static int qh_schedule(struct fotg210_hcd *fotg210, struct fotg210_qh *qh); - -/* Process and free completed qtds for a qh, returning URBs to drivers. - * Chases up to qh->hw_current. Returns number of completions called, - * indicating how much "real" work we did. - */ -static unsigned qh_completions(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh) -{ - struct fotg210_qtd *last, *end = qh->dummy; - struct fotg210_qtd *qtd, *tmp; - int last_status; - int stopped; - unsigned count = 0; - u8 state; - struct fotg210_qh_hw *hw = qh->hw; - - if (unlikely(list_empty(&qh->qtd_list))) - return count; - - /* completions (or tasks on other cpus) must never clobber HALT - * till we've gone through and cleaned everything up, even when - * they add urbs to this qh's queue or mark them for unlinking. - * - * NOTE: unlinking expects to be done in queue order. - * - * It's a bug for qh->qh_state to be anything other than - * QH_STATE_IDLE, unless our caller is scan_async() or - * scan_intr(). - */ - state = qh->qh_state; - qh->qh_state = QH_STATE_COMPLETING; - stopped = (state == QH_STATE_IDLE); - -rescan: - last = NULL; - last_status = -EINPROGRESS; - qh->needs_rescan = 0; - - /* remove de-activated QTDs from front of queue. - * after faults (including short reads), cleanup this urb - * then let the queue advance. - * if queue is stopped, handles unlinks. - */ - list_for_each_entry_safe(qtd, tmp, &qh->qtd_list, qtd_list) { - struct urb *urb; - u32 token = 0; - - urb = qtd->urb; - - /* clean up any state from previous QTD ...*/ - if (last) { - if (likely(last->urb != urb)) { - fotg210_urb_done(fotg210, last->urb, - last_status); - count++; - last_status = -EINPROGRESS; - } - fotg210_qtd_free(fotg210, last); - last = NULL; - } - - /* ignore urbs submitted during completions we reported */ - if (qtd == end) - break; - - /* hardware copies qtd out of qh overlay */ - rmb(); - token = hc32_to_cpu(fotg210, qtd->hw_token); - - /* always clean up qtds the hc de-activated */ -retry_xacterr: - if ((token & QTD_STS_ACTIVE) == 0) { - - /* Report Data Buffer Error: non-fatal but useful */ - if (token & QTD_STS_DBE) - fotg210_dbg(fotg210, - "detected DataBufferErr for urb %p ep%d%s len %d, qtd %p [qh %p]\n", - urb, usb_endpoint_num(&urb->ep->desc), - usb_endpoint_dir_in(&urb->ep->desc) - ? "in" : "out", - urb->transfer_buffer_length, qtd, qh); - - /* on STALL, error, and short reads this urb must - * complete and all its qtds must be recycled. - */ - if ((token & QTD_STS_HALT) != 0) { - - /* retry transaction errors until we - * reach the software xacterr limit - */ - if ((token & QTD_STS_XACT) && - QTD_CERR(token) == 0 && - ++qh->xacterrs < QH_XACTERR_MAX && - !urb->unlinked) { - fotg210_dbg(fotg210, - "detected XactErr len %zu/%zu retry %d\n", - qtd->length - QTD_LENGTH(token), - qtd->length, - qh->xacterrs); - - /* reset the token in the qtd and the - * qh overlay (which still contains - * the qtd) so that we pick up from - * where we left off - */ - token &= ~QTD_STS_HALT; - token |= QTD_STS_ACTIVE | - (FOTG210_TUNE_CERR << 10); - qtd->hw_token = cpu_to_hc32(fotg210, - token); - wmb(); - hw->hw_token = cpu_to_hc32(fotg210, - token); - goto retry_xacterr; - } - stopped = 1; - - /* magic dummy for some short reads; qh won't advance. - * that silicon quirk can kick in with this dummy too. - * - * other short reads won't stop the queue, including - * control transfers (status stage handles that) or - * most other single-qtd reads ... the queue stops if - * URB_SHORT_NOT_OK was set so the driver submitting - * the urbs could clean it up. - */ - } else if (IS_SHORT_READ(token) && - !(qtd->hw_alt_next & - FOTG210_LIST_END(fotg210))) { - stopped = 1; - } - - /* stop scanning when we reach qtds the hc is using */ - } else if (likely(!stopped - && fotg210->rh_state >= FOTG210_RH_RUNNING)) { - break; - - /* scan the whole queue for unlinks whenever it stops */ - } else { - stopped = 1; - - /* cancel everything if we halt, suspend, etc */ - if (fotg210->rh_state < FOTG210_RH_RUNNING) - last_status = -ESHUTDOWN; - - /* this qtd is active; skip it unless a previous qtd - * for its urb faulted, or its urb was canceled. - */ - else if (last_status == -EINPROGRESS && !urb->unlinked) - continue; - - /* qh unlinked; token in overlay may be most current */ - if (state == QH_STATE_IDLE && - cpu_to_hc32(fotg210, qtd->qtd_dma) - == hw->hw_current) { - token = hc32_to_cpu(fotg210, hw->hw_token); - - /* An unlink may leave an incomplete - * async transaction in the TT buffer. - * We have to clear it. - */ - fotg210_clear_tt_buffer(fotg210, qh, urb, - token); - } - } - - /* unless we already know the urb's status, collect qtd status - * and update count of bytes transferred. in common short read - * cases with only one data qtd (including control transfers), - * queue processing won't halt. but with two or more qtds (for - * example, with a 32 KB transfer), when the first qtd gets a - * short read the second must be removed by hand. - */ - if (last_status == -EINPROGRESS) { - last_status = qtd_copy_status(fotg210, urb, - qtd->length, token); - if (last_status == -EREMOTEIO && - (qtd->hw_alt_next & - FOTG210_LIST_END(fotg210))) - last_status = -EINPROGRESS; - - /* As part of low/full-speed endpoint-halt processing - * we must clear the TT buffer (11.17.5). - */ - if (unlikely(last_status != -EINPROGRESS && - last_status != -EREMOTEIO)) { - /* The TT's in some hubs malfunction when they - * receive this request following a STALL (they - * stop sending isochronous packets). Since a - * STALL can't leave the TT buffer in a busy - * state (if you believe Figures 11-48 - 11-51 - * in the USB 2.0 spec), we won't clear the TT - * buffer in this case. Strictly speaking this - * is a violation of the spec. - */ - if (last_status != -EPIPE) - fotg210_clear_tt_buffer(fotg210, qh, - urb, token); - } - } - - /* if we're removing something not at the queue head, - * patch the hardware queue pointer. - */ - if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { - last = list_entry(qtd->qtd_list.prev, - struct fotg210_qtd, qtd_list); - last->hw_next = qtd->hw_next; - } - - /* remove qtd; it's recycled after possible urb completion */ - list_del(&qtd->qtd_list); - last = qtd; - - /* reinit the xacterr counter for the next qtd */ - qh->xacterrs = 0; - } - - /* last urb's completion might still need calling */ - if (likely(last != NULL)) { - fotg210_urb_done(fotg210, last->urb, last_status); - count++; - fotg210_qtd_free(fotg210, last); - } - - /* Do we need to rescan for URBs dequeued during a giveback? */ - if (unlikely(qh->needs_rescan)) { - /* If the QH is already unlinked, do the rescan now. */ - if (state == QH_STATE_IDLE) - goto rescan; - - /* Otherwise we have to wait until the QH is fully unlinked. - * Our caller will start an unlink if qh->needs_rescan is - * set. But if an unlink has already started, nothing needs - * to be done. - */ - if (state != QH_STATE_LINKED) - qh->needs_rescan = 0; - } - - /* restore original state; caller must unlink or relink */ - qh->qh_state = state; - - /* be sure the hardware's done with the qh before refreshing - * it after fault cleanup, or recovering from silicon wrongly - * overlaying the dummy qtd (which reduces DMA chatter). - */ - if (stopped != 0 || hw->hw_qtd_next == FOTG210_LIST_END(fotg210)) { - switch (state) { - case QH_STATE_IDLE: - qh_refresh(fotg210, qh); - break; - case QH_STATE_LINKED: - /* We won't refresh a QH that's linked (after the HC - * stopped the queue). That avoids a race: - * - HC reads first part of QH; - * - CPU updates that first part and the token; - * - HC reads rest of that QH, including token - * Result: HC gets an inconsistent image, and then - * DMAs to/from the wrong memory (corrupting it). - * - * That should be rare for interrupt transfers, - * except maybe high bandwidth ... - */ - - /* Tell the caller to start an unlink */ - qh->needs_rescan = 1; - break; - /* otherwise, unlink already started */ - } - } - - return count; -} - -/* reverse of qh_urb_transaction: free a list of TDs. - * used for cleanup after errors, before HC sees an URB's TDs. - */ -static void qtd_list_free(struct fotg210_hcd *fotg210, struct urb *urb, - struct list_head *head) -{ - struct fotg210_qtd *qtd, *temp; - - list_for_each_entry_safe(qtd, temp, head, qtd_list) { - list_del(&qtd->qtd_list); - fotg210_qtd_free(fotg210, qtd); - } -} - -/* create a list of filled qtds for this URB; won't link into qh. - */ -static struct list_head *qh_urb_transaction(struct fotg210_hcd *fotg210, - struct urb *urb, struct list_head *head, gfp_t flags) -{ - struct fotg210_qtd *qtd, *qtd_prev; - dma_addr_t buf; - int len, this_sg_len, maxpacket; - int is_input; - u32 token; - int i; - struct scatterlist *sg; - - /* - * URBs map to sequences of QTDs: one logical transaction - */ - qtd = fotg210_qtd_alloc(fotg210, flags); - if (unlikely(!qtd)) - return NULL; - list_add_tail(&qtd->qtd_list, head); - qtd->urb = urb; - - token = QTD_STS_ACTIVE; - token |= (FOTG210_TUNE_CERR << 10); - /* for split transactions, SplitXState initialized to zero */ - - len = urb->transfer_buffer_length; - is_input = usb_pipein(urb->pipe); - if (usb_pipecontrol(urb->pipe)) { - /* SETUP pid */ - qtd_fill(fotg210, qtd, urb->setup_dma, - sizeof(struct usb_ctrlrequest), - token | (2 /* "setup" */ << 8), 8); - - /* ... and always at least one more pid */ - token ^= QTD_TOGGLE; - qtd_prev = qtd; - qtd = fotg210_qtd_alloc(fotg210, flags); - if (unlikely(!qtd)) - goto cleanup; - qtd->urb = urb; - qtd_prev->hw_next = QTD_NEXT(fotg210, qtd->qtd_dma); - list_add_tail(&qtd->qtd_list, head); - - /* for zero length DATA stages, STATUS is always IN */ - if (len == 0) - token |= (1 /* "in" */ << 8); - } - - /* - * data transfer stage: buffer setup - */ - i = urb->num_mapped_sgs; - if (len > 0 && i > 0) { - sg = urb->sg; - buf = sg_dma_address(sg); - - /* urb->transfer_buffer_length may be smaller than the - * size of the scatterlist (or vice versa) - */ - this_sg_len = min_t(int, sg_dma_len(sg), len); - } else { - sg = NULL; - buf = urb->transfer_dma; - this_sg_len = len; - } - - if (is_input) - token |= (1 /* "in" */ << 8); - /* else it's already initted to "out" pid (0 << 8) */ - - maxpacket = usb_maxpacket(urb->dev, urb->pipe); - - /* - * buffer gets wrapped in one or more qtds; - * last one may be "short" (including zero len) - * and may serve as a control status ack - */ - for (;;) { - int this_qtd_len; - - this_qtd_len = qtd_fill(fotg210, qtd, buf, this_sg_len, token, - maxpacket); - this_sg_len -= this_qtd_len; - len -= this_qtd_len; - buf += this_qtd_len; - - /* - * short reads advance to a "magic" dummy instead of the next - * qtd ... that forces the queue to stop, for manual cleanup. - * (this will usually be overridden later.) - */ - if (is_input) - qtd->hw_alt_next = fotg210->async->hw->hw_alt_next; - - /* qh makes control packets use qtd toggle; maybe switch it */ - if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) - token ^= QTD_TOGGLE; - - if (likely(this_sg_len <= 0)) { - if (--i <= 0 || len <= 0) - break; - sg = sg_next(sg); - buf = sg_dma_address(sg); - this_sg_len = min_t(int, sg_dma_len(sg), len); - } - - qtd_prev = qtd; - qtd = fotg210_qtd_alloc(fotg210, flags); - if (unlikely(!qtd)) - goto cleanup; - qtd->urb = urb; - qtd_prev->hw_next = QTD_NEXT(fotg210, qtd->qtd_dma); - list_add_tail(&qtd->qtd_list, head); - } - - /* - * unless the caller requires manual cleanup after short reads, - * have the alt_next mechanism keep the queue running after the - * last data qtd (the only one, for control and most other cases). - */ - if (likely((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 || - usb_pipecontrol(urb->pipe))) - qtd->hw_alt_next = FOTG210_LIST_END(fotg210); - - /* - * control requests may need a terminating data "status" ack; - * other OUT ones may need a terminating short packet - * (zero length). - */ - if (likely(urb->transfer_buffer_length != 0)) { - int one_more = 0; - - if (usb_pipecontrol(urb->pipe)) { - one_more = 1; - token ^= 0x0100; /* "in" <--> "out" */ - token |= QTD_TOGGLE; /* force DATA1 */ - } else if (usb_pipeout(urb->pipe) - && (urb->transfer_flags & URB_ZERO_PACKET) - && !(urb->transfer_buffer_length % maxpacket)) { - one_more = 1; - } - if (one_more) { - qtd_prev = qtd; - qtd = fotg210_qtd_alloc(fotg210, flags); - if (unlikely(!qtd)) - goto cleanup; - qtd->urb = urb; - qtd_prev->hw_next = QTD_NEXT(fotg210, qtd->qtd_dma); - list_add_tail(&qtd->qtd_list, head); - - /* never any data in such packets */ - qtd_fill(fotg210, qtd, 0, 0, token, 0); - } - } - - /* by default, enable interrupt on urb completion */ - if (likely(!(urb->transfer_flags & URB_NO_INTERRUPT))) - qtd->hw_token |= cpu_to_hc32(fotg210, QTD_IOC); - return head; - -cleanup: - qtd_list_free(fotg210, urb, head); - return NULL; -} - -/* Would be best to create all qh's from config descriptors, - * when each interface/altsetting is established. Unlink - * any previous qh and cancel its urbs first; endpoints are - * implicitly reset then (data toggle too). - * That'd mean updating how usbcore talks to HCDs. (2.7?) - */ - - -/* Each QH holds a qtd list; a QH is used for everything except iso. - * - * For interrupt urbs, the scheduler must set the microframe scheduling - * mask(s) each time the QH gets scheduled. For highspeed, that's - * just one microframe in the s-mask. For split interrupt transactions - * there are additional complications: c-mask, maybe FSTNs. - */ -static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, - gfp_t flags) -{ - struct fotg210_qh *qh = fotg210_qh_alloc(fotg210, flags); - struct usb_host_endpoint *ep; - u32 info1 = 0, info2 = 0; - int is_input, type; - int maxp = 0; - int mult; - struct usb_tt *tt = urb->dev->tt; - struct fotg210_qh_hw *hw; - - if (!qh) - return qh; - - /* - * init endpoint/device data for this QH - */ - info1 |= usb_pipeendpoint(urb->pipe) << 8; - info1 |= usb_pipedevice(urb->pipe) << 0; - - is_input = usb_pipein(urb->pipe); - type = usb_pipetype(urb->pipe); - ep = usb_pipe_endpoint(urb->dev, urb->pipe); - maxp = usb_endpoint_maxp(&ep->desc); - mult = usb_endpoint_maxp_mult(&ep->desc); - - /* 1024 byte maxpacket is a hardware ceiling. High bandwidth - * acts like up to 3KB, but is built from smaller packets. - */ - if (maxp > 1024) { - fotg210_dbg(fotg210, "bogus qh maxpacket %d\n", maxp); - goto done; - } - - /* Compute interrupt scheduling parameters just once, and save. - * - allowing for high bandwidth, how many nsec/uframe are used? - * - split transactions need a second CSPLIT uframe; same question - * - splits also need a schedule gap (for full/low speed I/O) - * - qh has a polling interval - * - * For control/bulk requests, the HC or TT handles these. - */ - if (type == PIPE_INTERRUPT) { - qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, - is_input, 0, mult * maxp)); - qh->start = NO_FRAME; - - if (urb->dev->speed == USB_SPEED_HIGH) { - qh->c_usecs = 0; - qh->gap_uf = 0; - - qh->period = urb->interval >> 3; - if (qh->period == 0 && urb->interval != 1) { - /* NOTE interval 2 or 4 uframes could work. - * But interval 1 scheduling is simpler, and - * includes high bandwidth. - */ - urb->interval = 1; - } else if (qh->period > fotg210->periodic_size) { - qh->period = fotg210->periodic_size; - urb->interval = qh->period << 3; - } - } else { - int think_time; - - /* gap is f(FS/LS transfer times) */ - qh->gap_uf = 1 + usb_calc_bus_time(urb->dev->speed, - is_input, 0, maxp) / (125 * 1000); - - /* FIXME this just approximates SPLIT/CSPLIT times */ - if (is_input) { /* SPLIT, gap, CSPLIT+DATA */ - qh->c_usecs = qh->usecs + HS_USECS(0); - qh->usecs = HS_USECS(1); - } else { /* SPLIT+DATA, gap, CSPLIT */ - qh->usecs += HS_USECS(1); - qh->c_usecs = HS_USECS(0); - } - - think_time = tt ? tt->think_time : 0; - qh->tt_usecs = NS_TO_US(think_time + - usb_calc_bus_time(urb->dev->speed, - is_input, 0, maxp)); - qh->period = urb->interval; - if (qh->period > fotg210->periodic_size) { - qh->period = fotg210->periodic_size; - urb->interval = qh->period; - } - } - } - - /* support for tt scheduling, and access to toggles */ - qh->dev = urb->dev; - - /* using TT? */ - switch (urb->dev->speed) { - case USB_SPEED_LOW: - info1 |= QH_LOW_SPEED; - fallthrough; - - case USB_SPEED_FULL: - /* EPS 0 means "full" */ - if (type != PIPE_INTERRUPT) - info1 |= (FOTG210_TUNE_RL_TT << 28); - if (type == PIPE_CONTROL) { - info1 |= QH_CONTROL_EP; /* for TT */ - info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ - } - info1 |= maxp << 16; - - info2 |= (FOTG210_TUNE_MULT_TT << 30); - - /* Some Freescale processors have an erratum in which the - * port number in the queue head was 0..N-1 instead of 1..N. - */ - if (fotg210_has_fsl_portno_bug(fotg210)) - info2 |= (urb->dev->ttport-1) << 23; - else - info2 |= urb->dev->ttport << 23; - - /* set the address of the TT; for TDI's integrated - * root hub tt, leave it zeroed. - */ - if (tt && tt->hub != fotg210_to_hcd(fotg210)->self.root_hub) - info2 |= tt->hub->devnum << 16; - - /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ - - break; - - case USB_SPEED_HIGH: /* no TT involved */ - info1 |= QH_HIGH_SPEED; - if (type == PIPE_CONTROL) { - info1 |= (FOTG210_TUNE_RL_HS << 28); - info1 |= 64 << 16; /* usb2 fixed maxpacket */ - info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ - info2 |= (FOTG210_TUNE_MULT_HS << 30); - } else if (type == PIPE_BULK) { - info1 |= (FOTG210_TUNE_RL_HS << 28); - /* The USB spec says that high speed bulk endpoints - * always use 512 byte maxpacket. But some device - * vendors decided to ignore that, and MSFT is happy - * to help them do so. So now people expect to use - * such nonconformant devices with Linux too; sigh. - */ - info1 |= maxp << 16; - info2 |= (FOTG210_TUNE_MULT_HS << 30); - } else { /* PIPE_INTERRUPT */ - info1 |= maxp << 16; - info2 |= mult << 30; - } - break; - default: - fotg210_dbg(fotg210, "bogus dev %p speed %d\n", urb->dev, - urb->dev->speed); -done: - qh_destroy(fotg210, qh); - return NULL; - } - - /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ - - /* init as live, toggle clear, advance to dummy */ - qh->qh_state = QH_STATE_IDLE; - hw = qh->hw; - hw->hw_info1 = cpu_to_hc32(fotg210, info1); - hw->hw_info2 = cpu_to_hc32(fotg210, info2); - qh->is_out = !is_input; - usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, 1); - qh_refresh(fotg210, qh); - return qh; -} - -static void enable_async(struct fotg210_hcd *fotg210) -{ - if (fotg210->async_count++) - return; - - /* Stop waiting to turn off the async schedule */ - fotg210->enabled_hrtimer_events &= ~BIT(FOTG210_HRTIMER_DISABLE_ASYNC); - - /* Don't start the schedule until ASS is 0 */ - fotg210_poll_ASS(fotg210); - turn_on_io_watchdog(fotg210); -} - -static void disable_async(struct fotg210_hcd *fotg210) -{ - if (--fotg210->async_count) - return; - - /* The async schedule and async_unlink list are supposed to be empty */ - WARN_ON(fotg210->async->qh_next.qh || fotg210->async_unlink); - - /* Don't turn off the schedule until ASS is 1 */ - fotg210_poll_ASS(fotg210); -} - -/* move qh (and its qtds) onto async queue; maybe enable queue. */ - -static void qh_link_async(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - __hc32 dma = QH_NEXT(fotg210, qh->qh_dma); - struct fotg210_qh *head; - - /* Don't link a QH if there's a Clear-TT-Buffer pending */ - if (unlikely(qh->clearing_tt)) - return; - - WARN_ON(qh->qh_state != QH_STATE_IDLE); - - /* clear halt and/or toggle; and maybe recover from silicon quirk */ - qh_refresh(fotg210, qh); - - /* splice right after start */ - head = fotg210->async; - qh->qh_next = head->qh_next; - qh->hw->hw_next = head->hw->hw_next; - wmb(); - - head->qh_next.qh = qh; - head->hw->hw_next = dma; - - qh->xacterrs = 0; - qh->qh_state = QH_STATE_LINKED; - /* qtd completions reported later by interrupt */ - - enable_async(fotg210); -} - -/* For control/bulk/interrupt, return QH with these TDs appended. - * Allocates and initializes the QH if necessary. - * Returns null if it can't allocate a QH it needs to. - * If the QH has TDs (urbs) already, that's great. - */ -static struct fotg210_qh *qh_append_tds(struct fotg210_hcd *fotg210, - struct urb *urb, struct list_head *qtd_list, - int epnum, void **ptr) -{ - struct fotg210_qh *qh = NULL; - __hc32 qh_addr_mask = cpu_to_hc32(fotg210, 0x7f); - - qh = (struct fotg210_qh *) *ptr; - if (unlikely(qh == NULL)) { - /* can't sleep here, we have fotg210->lock... */ - qh = qh_make(fotg210, urb, GFP_ATOMIC); - *ptr = qh; - } - if (likely(qh != NULL)) { - struct fotg210_qtd *qtd; - - if (unlikely(list_empty(qtd_list))) - qtd = NULL; - else - qtd = list_entry(qtd_list->next, struct fotg210_qtd, - qtd_list); - - /* control qh may need patching ... */ - if (unlikely(epnum == 0)) { - /* usb_reset_device() briefly reverts to address 0 */ - if (usb_pipedevice(urb->pipe) == 0) - qh->hw->hw_info1 &= ~qh_addr_mask; - } - - /* just one way to queue requests: swap with the dummy qtd. - * only hc or qh_refresh() ever modify the overlay. - */ - if (likely(qtd != NULL)) { - struct fotg210_qtd *dummy; - dma_addr_t dma; - __hc32 token; - - /* to avoid racing the HC, use the dummy td instead of - * the first td of our list (becomes new dummy). both - * tds stay deactivated until we're done, when the - * HC is allowed to fetch the old dummy (4.10.2). - */ - token = qtd->hw_token; - qtd->hw_token = HALT_BIT(fotg210); - - dummy = qh->dummy; - - dma = dummy->qtd_dma; - *dummy = *qtd; - dummy->qtd_dma = dma; - - list_del(&qtd->qtd_list); - list_add(&dummy->qtd_list, qtd_list); - list_splice_tail(qtd_list, &qh->qtd_list); - - fotg210_qtd_init(fotg210, qtd, qtd->qtd_dma); - qh->dummy = qtd; - - /* hc must see the new dummy at list end */ - dma = qtd->qtd_dma; - qtd = list_entry(qh->qtd_list.prev, - struct fotg210_qtd, qtd_list); - qtd->hw_next = QTD_NEXT(fotg210, dma); - - /* let the hc process these next qtds */ - wmb(); - dummy->hw_token = token; - - urb->hcpriv = qh; - } - } - return qh; -} - -static int submit_async(struct fotg210_hcd *fotg210, struct urb *urb, - struct list_head *qtd_list, gfp_t mem_flags) -{ - int epnum; - unsigned long flags; - struct fotg210_qh *qh = NULL; - int rc; - - epnum = urb->ep->desc.bEndpointAddress; - -#ifdef FOTG210_URB_TRACE - { - struct fotg210_qtd *qtd; - - qtd = list_entry(qtd_list->next, struct fotg210_qtd, qtd_list); - fotg210_dbg(fotg210, - "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", - __func__, urb->dev->devpath, urb, - epnum & 0x0f, (epnum & USB_DIR_IN) - ? "in" : "out", - urb->transfer_buffer_length, - qtd, urb->ep->hcpriv); - } -#endif - - spin_lock_irqsave(&fotg210->lock, flags); - if (unlikely(!HCD_HW_ACCESSIBLE(fotg210_to_hcd(fotg210)))) { - rc = -ESHUTDOWN; - goto done; - } - rc = usb_hcd_link_urb_to_ep(fotg210_to_hcd(fotg210), urb); - if (unlikely(rc)) - goto done; - - qh = qh_append_tds(fotg210, urb, qtd_list, epnum, &urb->ep->hcpriv); - if (unlikely(qh == NULL)) { - usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), urb); - rc = -ENOMEM; - goto done; - } - - /* Control/bulk operations through TTs don't need scheduling, - * the HC and TT handle it when the TT has a buffer ready. - */ - if (likely(qh->qh_state == QH_STATE_IDLE)) - qh_link_async(fotg210, qh); -done: - spin_unlock_irqrestore(&fotg210->lock, flags); - if (unlikely(qh == NULL)) - qtd_list_free(fotg210, urb, qtd_list); - return rc; -} - -static void single_unlink_async(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh) -{ - struct fotg210_qh *prev; - - /* Add to the end of the list of QHs waiting for the next IAAD */ - qh->qh_state = QH_STATE_UNLINK; - if (fotg210->async_unlink) - fotg210->async_unlink_last->unlink_next = qh; - else - fotg210->async_unlink = qh; - fotg210->async_unlink_last = qh; - - /* Unlink it from the schedule */ - prev = fotg210->async; - while (prev->qh_next.qh != qh) - prev = prev->qh_next.qh; - - prev->hw->hw_next = qh->hw->hw_next; - prev->qh_next = qh->qh_next; - if (fotg210->qh_scan_next == qh) - fotg210->qh_scan_next = qh->qh_next.qh; -} - -static void start_iaa_cycle(struct fotg210_hcd *fotg210, bool nested) -{ - /* - * Do nothing if an IAA cycle is already running or - * if one will be started shortly. - */ - if (fotg210->async_iaa || fotg210->async_unlinking) - return; - - /* Do all the waiting QHs at once */ - fotg210->async_iaa = fotg210->async_unlink; - fotg210->async_unlink = NULL; - - /* If the controller isn't running, we don't have to wait for it */ - if (unlikely(fotg210->rh_state < FOTG210_RH_RUNNING)) { - if (!nested) /* Avoid recursion */ - end_unlink_async(fotg210); - - /* Otherwise start a new IAA cycle */ - } else if (likely(fotg210->rh_state == FOTG210_RH_RUNNING)) { - /* Make sure the unlinks are all visible to the hardware */ - wmb(); - - fotg210_writel(fotg210, fotg210->command | CMD_IAAD, - &fotg210->regs->command); - fotg210_readl(fotg210, &fotg210->regs->command); - fotg210_enable_event(fotg210, FOTG210_HRTIMER_IAA_WATCHDOG, - true); - } -} - -/* the async qh for the qtds being unlinked are now gone from the HC */ - -static void end_unlink_async(struct fotg210_hcd *fotg210) -{ - struct fotg210_qh *qh; - - /* Process the idle QHs */ -restart: - fotg210->async_unlinking = true; - while (fotg210->async_iaa) { - qh = fotg210->async_iaa; - fotg210->async_iaa = qh->unlink_next; - qh->unlink_next = NULL; - - qh->qh_state = QH_STATE_IDLE; - qh->qh_next.qh = NULL; - - qh_completions(fotg210, qh); - if (!list_empty(&qh->qtd_list) && - fotg210->rh_state == FOTG210_RH_RUNNING) - qh_link_async(fotg210, qh); - disable_async(fotg210); - } - fotg210->async_unlinking = false; - - /* Start a new IAA cycle if any QHs are waiting for it */ - if (fotg210->async_unlink) { - start_iaa_cycle(fotg210, true); - if (unlikely(fotg210->rh_state < FOTG210_RH_RUNNING)) - goto restart; - } -} - -static void unlink_empty_async(struct fotg210_hcd *fotg210) -{ - struct fotg210_qh *qh, *next; - bool stopped = (fotg210->rh_state < FOTG210_RH_RUNNING); - bool check_unlinks_later = false; - - /* Unlink all the async QHs that have been empty for a timer cycle */ - next = fotg210->async->qh_next.qh; - while (next) { - qh = next; - next = qh->qh_next.qh; - - if (list_empty(&qh->qtd_list) && - qh->qh_state == QH_STATE_LINKED) { - if (!stopped && qh->unlink_cycle == - fotg210->async_unlink_cycle) - check_unlinks_later = true; - else - single_unlink_async(fotg210, qh); - } - } - - /* Start a new IAA cycle if any QHs are waiting for it */ - if (fotg210->async_unlink) - start_iaa_cycle(fotg210, false); - - /* QHs that haven't been empty for long enough will be handled later */ - if (check_unlinks_later) { - fotg210_enable_event(fotg210, FOTG210_HRTIMER_ASYNC_UNLINKS, - true); - ++fotg210->async_unlink_cycle; - } -} - -/* makes sure the async qh will become idle */ -/* caller must own fotg210->lock */ - -static void start_unlink_async(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh) -{ - /* - * If the QH isn't linked then there's nothing we can do - * unless we were called during a giveback, in which case - * qh_completions() has to deal with it. - */ - if (qh->qh_state != QH_STATE_LINKED) { - if (qh->qh_state == QH_STATE_COMPLETING) - qh->needs_rescan = 1; - return; - } - - single_unlink_async(fotg210, qh); - start_iaa_cycle(fotg210, false); -} - -static void scan_async(struct fotg210_hcd *fotg210) -{ - struct fotg210_qh *qh; - bool check_unlinks_later = false; - - fotg210->qh_scan_next = fotg210->async->qh_next.qh; - while (fotg210->qh_scan_next) { - qh = fotg210->qh_scan_next; - fotg210->qh_scan_next = qh->qh_next.qh; -rescan: - /* clean any finished work for this qh */ - if (!list_empty(&qh->qtd_list)) { - int temp; - - /* - * Unlinks could happen here; completion reporting - * drops the lock. That's why fotg210->qh_scan_next - * always holds the next qh to scan; if the next qh - * gets unlinked then fotg210->qh_scan_next is adjusted - * in single_unlink_async(). - */ - temp = qh_completions(fotg210, qh); - if (qh->needs_rescan) { - start_unlink_async(fotg210, qh); - } else if (list_empty(&qh->qtd_list) - && qh->qh_state == QH_STATE_LINKED) { - qh->unlink_cycle = fotg210->async_unlink_cycle; - check_unlinks_later = true; - } else if (temp != 0) - goto rescan; - } - } - - /* - * Unlink empty entries, reducing DMA usage as well - * as HCD schedule-scanning costs. Delay for any qh - * we just scanned, there's a not-unusual case that it - * doesn't stay idle for long. - */ - if (check_unlinks_later && fotg210->rh_state == FOTG210_RH_RUNNING && - !(fotg210->enabled_hrtimer_events & - BIT(FOTG210_HRTIMER_ASYNC_UNLINKS))) { - fotg210_enable_event(fotg210, - FOTG210_HRTIMER_ASYNC_UNLINKS, true); - ++fotg210->async_unlink_cycle; - } -} -/* EHCI scheduled transaction support: interrupt, iso, split iso - * These are called "periodic" transactions in the EHCI spec. - * - * Note that for interrupt transfers, the QH/QTD manipulation is shared - * with the "asynchronous" transaction support (control/bulk transfers). - * The only real difference is in how interrupt transfers are scheduled. - * - * For ISO, we make an "iso_stream" head to serve the same role as a QH. - * It keeps track of every ITD (or SITD) that's linked, and holds enough - * pre-calculated schedule data to make appending to the queue be quick. - */ -static int fotg210_get_frame(struct usb_hcd *hcd); - -/* periodic_next_shadow - return "next" pointer on shadow list - * @periodic: host pointer to qh/itd - * @tag: hardware tag for type of this record - */ -static union fotg210_shadow *periodic_next_shadow(struct fotg210_hcd *fotg210, - union fotg210_shadow *periodic, __hc32 tag) -{ - switch (hc32_to_cpu(fotg210, tag)) { - case Q_TYPE_QH: - return &periodic->qh->qh_next; - case Q_TYPE_FSTN: - return &periodic->fstn->fstn_next; - default: - return &periodic->itd->itd_next; - } -} - -static __hc32 *shadow_next_periodic(struct fotg210_hcd *fotg210, - union fotg210_shadow *periodic, __hc32 tag) -{ - switch (hc32_to_cpu(fotg210, tag)) { - /* our fotg210_shadow.qh is actually software part */ - case Q_TYPE_QH: - return &periodic->qh->hw->hw_next; - /* others are hw parts */ - default: - return periodic->hw_next; - } -} - -/* caller must hold fotg210->lock */ -static void periodic_unlink(struct fotg210_hcd *fotg210, unsigned frame, - void *ptr) -{ - union fotg210_shadow *prev_p = &fotg210->pshadow[frame]; - __hc32 *hw_p = &fotg210->periodic[frame]; - union fotg210_shadow here = *prev_p; - - /* find predecessor of "ptr"; hw and shadow lists are in sync */ - while (here.ptr && here.ptr != ptr) { - prev_p = periodic_next_shadow(fotg210, prev_p, - Q_NEXT_TYPE(fotg210, *hw_p)); - hw_p = shadow_next_periodic(fotg210, &here, - Q_NEXT_TYPE(fotg210, *hw_p)); - here = *prev_p; - } - /* an interrupt entry (at list end) could have been shared */ - if (!here.ptr) - return; - - /* update shadow and hardware lists ... the old "next" pointers - * from ptr may still be in use, the caller updates them. - */ - *prev_p = *periodic_next_shadow(fotg210, &here, - Q_NEXT_TYPE(fotg210, *hw_p)); - - *hw_p = *shadow_next_periodic(fotg210, &here, - Q_NEXT_TYPE(fotg210, *hw_p)); -} - -/* how many of the uframe's 125 usecs are allocated? */ -static unsigned short periodic_usecs(struct fotg210_hcd *fotg210, - unsigned frame, unsigned uframe) -{ - __hc32 *hw_p = &fotg210->periodic[frame]; - union fotg210_shadow *q = &fotg210->pshadow[frame]; - unsigned usecs = 0; - struct fotg210_qh_hw *hw; - - while (q->ptr) { - switch (hc32_to_cpu(fotg210, Q_NEXT_TYPE(fotg210, *hw_p))) { - case Q_TYPE_QH: - hw = q->qh->hw; - /* is it in the S-mask? */ - if (hw->hw_info2 & cpu_to_hc32(fotg210, 1 << uframe)) - usecs += q->qh->usecs; - /* ... or C-mask? */ - if (hw->hw_info2 & cpu_to_hc32(fotg210, - 1 << (8 + uframe))) - usecs += q->qh->c_usecs; - hw_p = &hw->hw_next; - q = &q->qh->qh_next; - break; - /* case Q_TYPE_FSTN: */ - default: - /* for "save place" FSTNs, count the relevant INTR - * bandwidth from the previous frame - */ - if (q->fstn->hw_prev != FOTG210_LIST_END(fotg210)) - fotg210_dbg(fotg210, "ignoring FSTN cost ...\n"); - - hw_p = &q->fstn->hw_next; - q = &q->fstn->fstn_next; - break; - case Q_TYPE_ITD: - if (q->itd->hw_transaction[uframe]) - usecs += q->itd->stream->usecs; - hw_p = &q->itd->hw_next; - q = &q->itd->itd_next; - break; - } - } - if (usecs > fotg210->uframe_periodic_max) - fotg210_err(fotg210, "uframe %d sched overrun: %d usecs\n", - frame * 8 + uframe, usecs); - return usecs; -} - -static int same_tt(struct usb_device *dev1, struct usb_device *dev2) -{ - if (!dev1->tt || !dev2->tt) - return 0; - if (dev1->tt != dev2->tt) - return 0; - if (dev1->tt->multi) - return dev1->ttport == dev2->ttport; - else - return 1; -} - -/* return true iff the device's transaction translator is available - * for a periodic transfer starting at the specified frame, using - * all the uframes in the mask. - */ -static int tt_no_collision(struct fotg210_hcd *fotg210, unsigned period, - struct usb_device *dev, unsigned frame, u32 uf_mask) -{ - if (period == 0) /* error */ - return 0; - - /* note bandwidth wastage: split never follows csplit - * (different dev or endpoint) until the next uframe. - * calling convention doesn't make that distinction. - */ - for (; frame < fotg210->periodic_size; frame += period) { - union fotg210_shadow here; - __hc32 type; - struct fotg210_qh_hw *hw; - - here = fotg210->pshadow[frame]; - type = Q_NEXT_TYPE(fotg210, fotg210->periodic[frame]); - while (here.ptr) { - switch (hc32_to_cpu(fotg210, type)) { - case Q_TYPE_ITD: - type = Q_NEXT_TYPE(fotg210, here.itd->hw_next); - here = here.itd->itd_next; - continue; - case Q_TYPE_QH: - hw = here.qh->hw; - if (same_tt(dev, here.qh->dev)) { - u32 mask; - - mask = hc32_to_cpu(fotg210, - hw->hw_info2); - /* "knows" no gap is needed */ - mask |= mask >> 8; - if (mask & uf_mask) - break; - } - type = Q_NEXT_TYPE(fotg210, hw->hw_next); - here = here.qh->qh_next; - continue; - /* case Q_TYPE_FSTN: */ - default: - fotg210_dbg(fotg210, - "periodic frame %d bogus type %d\n", - frame, type); - } - - /* collision or error */ - return 0; - } - } - - /* no collision */ - return 1; -} - -static void enable_periodic(struct fotg210_hcd *fotg210) -{ - if (fotg210->periodic_count++) - return; - - /* Stop waiting to turn off the periodic schedule */ - fotg210->enabled_hrtimer_events &= - ~BIT(FOTG210_HRTIMER_DISABLE_PERIODIC); - - /* Don't start the schedule until PSS is 0 */ - fotg210_poll_PSS(fotg210); - turn_on_io_watchdog(fotg210); -} - -static void disable_periodic(struct fotg210_hcd *fotg210) -{ - if (--fotg210->periodic_count) - return; - - /* Don't turn off the schedule until PSS is 1 */ - fotg210_poll_PSS(fotg210); -} - -/* periodic schedule slots have iso tds (normal or split) first, then a - * sparse tree for active interrupt transfers. - * - * this just links in a qh; caller guarantees uframe masks are set right. - * no FSTN support (yet; fotg210 0.96+) - */ -static void qh_link_periodic(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - unsigned i; - unsigned period = qh->period; - - dev_dbg(&qh->dev->dev, - "link qh%d-%04x/%p start %d [%d/%d us]\n", period, - hc32_to_cpup(fotg210, &qh->hw->hw_info2) & - (QH_CMASK | QH_SMASK), qh, qh->start, qh->usecs, - qh->c_usecs); - - /* high bandwidth, or otherwise every microframe */ - if (period == 0) - period = 1; - - for (i = qh->start; i < fotg210->periodic_size; i += period) { - union fotg210_shadow *prev = &fotg210->pshadow[i]; - __hc32 *hw_p = &fotg210->periodic[i]; - union fotg210_shadow here = *prev; - __hc32 type = 0; - - /* skip the iso nodes at list head */ - while (here.ptr) { - type = Q_NEXT_TYPE(fotg210, *hw_p); - if (type == cpu_to_hc32(fotg210, Q_TYPE_QH)) - break; - prev = periodic_next_shadow(fotg210, prev, type); - hw_p = shadow_next_periodic(fotg210, &here, type); - here = *prev; - } - - /* sorting each branch by period (slow-->fast) - * enables sharing interior tree nodes - */ - while (here.ptr && qh != here.qh) { - if (qh->period > here.qh->period) - break; - prev = &here.qh->qh_next; - hw_p = &here.qh->hw->hw_next; - here = *prev; - } - /* link in this qh, unless some earlier pass did that */ - if (qh != here.qh) { - qh->qh_next = here; - if (here.qh) - qh->hw->hw_next = *hw_p; - wmb(); - prev->qh = qh; - *hw_p = QH_NEXT(fotg210, qh->qh_dma); - } - } - qh->qh_state = QH_STATE_LINKED; - qh->xacterrs = 0; - - /* update per-qh bandwidth for usbfs */ - fotg210_to_hcd(fotg210)->self.bandwidth_allocated += qh->period - ? ((qh->usecs + qh->c_usecs) / qh->period) - : (qh->usecs * 8); - - list_add(&qh->intr_node, &fotg210->intr_qh_list); - - /* maybe enable periodic schedule processing */ - ++fotg210->intr_count; - enable_periodic(fotg210); -} - -static void qh_unlink_periodic(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh) -{ - unsigned i; - unsigned period; - - /* - * If qh is for a low/full-speed device, simply unlinking it - * could interfere with an ongoing split transaction. To unlink - * it safely would require setting the QH_INACTIVATE bit and - * waiting at least one frame, as described in EHCI 4.12.2.5. - * - * We won't bother with any of this. Instead, we assume that the - * only reason for unlinking an interrupt QH while the current URB - * is still active is to dequeue all the URBs (flush the whole - * endpoint queue). - * - * If rebalancing the periodic schedule is ever implemented, this - * approach will no longer be valid. - */ - - /* high bandwidth, or otherwise part of every microframe */ - period = qh->period; - if (!period) - period = 1; - - for (i = qh->start; i < fotg210->periodic_size; i += period) - periodic_unlink(fotg210, i, qh); - - /* update per-qh bandwidth for usbfs */ - fotg210_to_hcd(fotg210)->self.bandwidth_allocated -= qh->period - ? ((qh->usecs + qh->c_usecs) / qh->period) - : (qh->usecs * 8); - - dev_dbg(&qh->dev->dev, - "unlink qh%d-%04x/%p start %d [%d/%d us]\n", - qh->period, hc32_to_cpup(fotg210, &qh->hw->hw_info2) & - (QH_CMASK | QH_SMASK), qh, qh->start, qh->usecs, - qh->c_usecs); - - /* qh->qh_next still "live" to HC */ - qh->qh_state = QH_STATE_UNLINK; - qh->qh_next.ptr = NULL; - - if (fotg210->qh_scan_next == qh) - fotg210->qh_scan_next = list_entry(qh->intr_node.next, - struct fotg210_qh, intr_node); - list_del(&qh->intr_node); -} - -static void start_unlink_intr(struct fotg210_hcd *fotg210, - struct fotg210_qh *qh) -{ - /* If the QH isn't linked then there's nothing we can do - * unless we were called during a giveback, in which case - * qh_completions() has to deal with it. - */ - if (qh->qh_state != QH_STATE_LINKED) { - if (qh->qh_state == QH_STATE_COMPLETING) - qh->needs_rescan = 1; - return; - } - - qh_unlink_periodic(fotg210, qh); - - /* Make sure the unlinks are visible before starting the timer */ - wmb(); - - /* - * The EHCI spec doesn't say how long it takes the controller to - * stop accessing an unlinked interrupt QH. The timer delay is - * 9 uframes; presumably that will be long enough. - */ - qh->unlink_cycle = fotg210->intr_unlink_cycle; - - /* New entries go at the end of the intr_unlink list */ - if (fotg210->intr_unlink) - fotg210->intr_unlink_last->unlink_next = qh; - else - fotg210->intr_unlink = qh; - fotg210->intr_unlink_last = qh; - - if (fotg210->intr_unlinking) - ; /* Avoid recursive calls */ - else if (fotg210->rh_state < FOTG210_RH_RUNNING) - fotg210_handle_intr_unlinks(fotg210); - else if (fotg210->intr_unlink == qh) { - fotg210_enable_event(fotg210, FOTG210_HRTIMER_UNLINK_INTR, - true); - ++fotg210->intr_unlink_cycle; - } -} - -static void end_unlink_intr(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - struct fotg210_qh_hw *hw = qh->hw; - int rc; - - qh->qh_state = QH_STATE_IDLE; - hw->hw_next = FOTG210_LIST_END(fotg210); - - qh_completions(fotg210, qh); - - /* reschedule QH iff another request is queued */ - if (!list_empty(&qh->qtd_list) && - fotg210->rh_state == FOTG210_RH_RUNNING) { - rc = qh_schedule(fotg210, qh); - - /* An error here likely indicates handshake failure - * or no space left in the schedule. Neither fault - * should happen often ... - * - * FIXME kill the now-dysfunctional queued urbs - */ - if (rc != 0) - fotg210_err(fotg210, "can't reschedule qh %p, err %d\n", - qh, rc); - } - - /* maybe turn off periodic schedule */ - --fotg210->intr_count; - disable_periodic(fotg210); -} - -static int check_period(struct fotg210_hcd *fotg210, unsigned frame, - unsigned uframe, unsigned period, unsigned usecs) -{ - int claimed; - - /* complete split running into next frame? - * given FSTN support, we could sometimes check... - */ - if (uframe >= 8) - return 0; - - /* convert "usecs we need" to "max already claimed" */ - usecs = fotg210->uframe_periodic_max - usecs; - - /* we "know" 2 and 4 uframe intervals were rejected; so - * for period 0, check _every_ microframe in the schedule. - */ - if (unlikely(period == 0)) { - do { - for (uframe = 0; uframe < 7; uframe++) { - claimed = periodic_usecs(fotg210, frame, - uframe); - if (claimed > usecs) - return 0; - } - } while ((frame += 1) < fotg210->periodic_size); - - /* just check the specified uframe, at that period */ - } else { - do { - claimed = periodic_usecs(fotg210, frame, uframe); - if (claimed > usecs) - return 0; - } while ((frame += period) < fotg210->periodic_size); - } - - /* success! */ - return 1; -} - -static int check_intr_schedule(struct fotg210_hcd *fotg210, unsigned frame, - unsigned uframe, const struct fotg210_qh *qh, __hc32 *c_maskp) -{ - int retval = -ENOSPC; - u8 mask = 0; - - if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ - goto done; - - if (!check_period(fotg210, frame, uframe, qh->period, qh->usecs)) - goto done; - if (!qh->c_usecs) { - retval = 0; - *c_maskp = 0; - goto done; - } - - /* Make sure this tt's buffer is also available for CSPLITs. - * We pessimize a bit; probably the typical full speed case - * doesn't need the second CSPLIT. - * - * NOTE: both SPLIT and CSPLIT could be checked in just - * one smart pass... - */ - mask = 0x03 << (uframe + qh->gap_uf); - *c_maskp = cpu_to_hc32(fotg210, mask << 8); - - mask |= 1 << uframe; - if (tt_no_collision(fotg210, qh->period, qh->dev, frame, mask)) { - if (!check_period(fotg210, frame, uframe + qh->gap_uf + 1, - qh->period, qh->c_usecs)) - goto done; - if (!check_period(fotg210, frame, uframe + qh->gap_uf, - qh->period, qh->c_usecs)) - goto done; - retval = 0; - } -done: - return retval; -} - -/* "first fit" scheduling policy used the first time through, - * or when the previous schedule slot can't be re-used. - */ -static int qh_schedule(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) -{ - int status; - unsigned uframe; - __hc32 c_mask; - unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ - struct fotg210_qh_hw *hw = qh->hw; - - qh_refresh(fotg210, qh); - hw->hw_next = FOTG210_LIST_END(fotg210); - frame = qh->start; - - /* reuse the previous schedule slots, if we can */ - if (frame < qh->period) { - uframe = ffs(hc32_to_cpup(fotg210, &hw->hw_info2) & QH_SMASK); - status = check_intr_schedule(fotg210, frame, --uframe, - qh, &c_mask); - } else { - uframe = 0; - c_mask = 0; - status = -ENOSPC; - } - - /* else scan the schedule to find a group of slots such that all - * uframes have enough periodic bandwidth available. - */ - if (status) { - /* "normal" case, uframing flexible except with splits */ - if (qh->period) { - int i; - - for (i = qh->period; status && i > 0; --i) { - frame = ++fotg210->random_frame % qh->period; - for (uframe = 0; uframe < 8; uframe++) { - status = check_intr_schedule(fotg210, - frame, uframe, qh, - &c_mask); - if (status == 0) - break; - } - } - - /* qh->period == 0 means every uframe */ - } else { - frame = 0; - status = check_intr_schedule(fotg210, 0, 0, qh, - &c_mask); - } - if (status) - goto done; - qh->start = frame; - - /* reset S-frame and (maybe) C-frame masks */ - hw->hw_info2 &= cpu_to_hc32(fotg210, ~(QH_CMASK | QH_SMASK)); - hw->hw_info2 |= qh->period - ? cpu_to_hc32(fotg210, 1 << uframe) - : cpu_to_hc32(fotg210, QH_SMASK); - hw->hw_info2 |= c_mask; - } else - fotg210_dbg(fotg210, "reused qh %p schedule\n", qh); - - /* stuff into the periodic schedule */ - qh_link_periodic(fotg210, qh); -done: - return status; -} - -static int intr_submit(struct fotg210_hcd *fotg210, struct urb *urb, - struct list_head *qtd_list, gfp_t mem_flags) -{ - unsigned epnum; - unsigned long flags; - struct fotg210_qh *qh; - int status; - struct list_head empty; - - /* get endpoint and transfer/schedule data */ - epnum = urb->ep->desc.bEndpointAddress; - - spin_lock_irqsave(&fotg210->lock, flags); - - if (unlikely(!HCD_HW_ACCESSIBLE(fotg210_to_hcd(fotg210)))) { - status = -ESHUTDOWN; - goto done_not_linked; - } - status = usb_hcd_link_urb_to_ep(fotg210_to_hcd(fotg210), urb); - if (unlikely(status)) - goto done_not_linked; - - /* get qh and force any scheduling errors */ - INIT_LIST_HEAD(&empty); - qh = qh_append_tds(fotg210, urb, &empty, epnum, &urb->ep->hcpriv); - if (qh == NULL) { - status = -ENOMEM; - goto done; - } - if (qh->qh_state == QH_STATE_IDLE) { - status = qh_schedule(fotg210, qh); - if (status) - goto done; - } - - /* then queue the urb's tds to the qh */ - qh = qh_append_tds(fotg210, urb, qtd_list, epnum, &urb->ep->hcpriv); - BUG_ON(qh == NULL); - - /* ... update usbfs periodic stats */ - fotg210_to_hcd(fotg210)->self.bandwidth_int_reqs++; - -done: - if (unlikely(status)) - usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), urb); -done_not_linked: - spin_unlock_irqrestore(&fotg210->lock, flags); - if (status) - qtd_list_free(fotg210, urb, qtd_list); - - return status; -} - -static void scan_intr(struct fotg210_hcd *fotg210) -{ - struct fotg210_qh *qh; - - list_for_each_entry_safe(qh, fotg210->qh_scan_next, - &fotg210->intr_qh_list, intr_node) { -rescan: - /* clean any finished work for this qh */ - if (!list_empty(&qh->qtd_list)) { - int temp; - - /* - * Unlinks could happen here; completion reporting - * drops the lock. That's why fotg210->qh_scan_next - * always holds the next qh to scan; if the next qh - * gets unlinked then fotg210->qh_scan_next is adjusted - * in qh_unlink_periodic(). - */ - temp = qh_completions(fotg210, qh); - if (unlikely(qh->needs_rescan || - (list_empty(&qh->qtd_list) && - qh->qh_state == QH_STATE_LINKED))) - start_unlink_intr(fotg210, qh); - else if (temp != 0) - goto rescan; - } - } -} - -/* fotg210_iso_stream ops work with both ITD and SITD */ - -static struct fotg210_iso_stream *iso_stream_alloc(gfp_t mem_flags) -{ - struct fotg210_iso_stream *stream; - - stream = kzalloc_obj(*stream, mem_flags); - if (likely(stream != NULL)) { - INIT_LIST_HEAD(&stream->td_list); - INIT_LIST_HEAD(&stream->free_list); - stream->next_uframe = -1; - } - return stream; -} - -static void iso_stream_init(struct fotg210_hcd *fotg210, - struct fotg210_iso_stream *stream, struct usb_device *dev, - int pipe, unsigned interval) -{ - u32 buf1; - unsigned epnum, maxp; - int is_input; - long bandwidth; - unsigned multi; - struct usb_host_endpoint *ep; - - /* - * this might be a "high bandwidth" highspeed endpoint, - * as encoded in the ep descriptor's wMaxPacket field - */ - epnum = usb_pipeendpoint(pipe); - is_input = usb_pipein(pipe) ? USB_DIR_IN : 0; - ep = usb_pipe_endpoint(dev, pipe); - maxp = usb_endpoint_maxp(&ep->desc); - if (is_input) - buf1 = (1 << 11); - else - buf1 = 0; - - multi = usb_endpoint_maxp_mult(&ep->desc); - buf1 |= maxp; - maxp *= multi; - - stream->buf0 = cpu_to_hc32(fotg210, (epnum << 8) | dev->devnum); - stream->buf1 = cpu_to_hc32(fotg210, buf1); - stream->buf2 = cpu_to_hc32(fotg210, multi); - - /* usbfs wants to report the average usecs per frame tied up - * when transfers on this endpoint are scheduled ... - */ - if (dev->speed == USB_SPEED_FULL) { - interval <<= 3; - stream->usecs = NS_TO_US(usb_calc_bus_time(dev->speed, - is_input, 1, maxp)); - stream->usecs /= 8; - } else { - stream->highspeed = 1; - stream->usecs = HS_USECS_ISO(maxp); - } - bandwidth = stream->usecs * 8; - bandwidth /= interval; - - stream->bandwidth = bandwidth; - stream->udev = dev; - stream->bEndpointAddress = is_input | epnum; - stream->interval = interval; - stream->maxp = maxp; -} - -static struct fotg210_iso_stream *iso_stream_find(struct fotg210_hcd *fotg210, - struct urb *urb) -{ - unsigned epnum; - struct fotg210_iso_stream *stream; - struct usb_host_endpoint *ep; - unsigned long flags; - - epnum = usb_pipeendpoint(urb->pipe); - if (usb_pipein(urb->pipe)) - ep = urb->dev->ep_in[epnum]; - else - ep = urb->dev->ep_out[epnum]; - - spin_lock_irqsave(&fotg210->lock, flags); - stream = ep->hcpriv; - - if (unlikely(stream == NULL)) { - stream = iso_stream_alloc(GFP_ATOMIC); - if (likely(stream != NULL)) { - ep->hcpriv = stream; - stream->ep = ep; - iso_stream_init(fotg210, stream, urb->dev, urb->pipe, - urb->interval); - } - - /* if dev->ep[epnum] is a QH, hw is set */ - } else if (unlikely(stream->hw != NULL)) { - fotg210_dbg(fotg210, "dev %s ep%d%s, not iso??\n", - urb->dev->devpath, epnum, - usb_pipein(urb->pipe) ? "in" : "out"); - stream = NULL; - } - - spin_unlock_irqrestore(&fotg210->lock, flags); - return stream; -} - -/* fotg210_iso_sched ops can be ITD-only or SITD-only */ - -static struct fotg210_iso_sched *iso_sched_alloc(unsigned packets, - gfp_t mem_flags) -{ - struct fotg210_iso_sched *iso_sched; - - iso_sched = kzalloc_flex(*iso_sched, packet, packets, mem_flags); - if (likely(iso_sched != NULL)) - INIT_LIST_HEAD(&iso_sched->td_list); - - return iso_sched; -} - -static inline void itd_sched_init(struct fotg210_hcd *fotg210, - struct fotg210_iso_sched *iso_sched, - struct fotg210_iso_stream *stream, struct urb *urb) -{ - unsigned i; - dma_addr_t dma = urb->transfer_dma; - - /* how many uframes are needed for these transfers */ - iso_sched->span = urb->number_of_packets * stream->interval; - - /* figure out per-uframe itd fields that we'll need later - * when we fit new itds into the schedule. - */ - for (i = 0; i < urb->number_of_packets; i++) { - struct fotg210_iso_packet *uframe = &iso_sched->packet[i]; - unsigned length; - dma_addr_t buf; - u32 trans; - - length = urb->iso_frame_desc[i].length; - buf = dma + urb->iso_frame_desc[i].offset; - - trans = FOTG210_ISOC_ACTIVE; - trans |= buf & 0x0fff; - if (unlikely(((i + 1) == urb->number_of_packets)) - && !(urb->transfer_flags & URB_NO_INTERRUPT)) - trans |= FOTG210_ITD_IOC; - trans |= length << 16; - uframe->transaction = cpu_to_hc32(fotg210, trans); - - /* might need to cross a buffer page within a uframe */ - uframe->bufp = (buf & ~(u64)0x0fff); - buf += length; - if (unlikely((uframe->bufp != (buf & ~(u64)0x0fff)))) - uframe->cross = 1; - } -} - -static void iso_sched_free(struct fotg210_iso_stream *stream, - struct fotg210_iso_sched *iso_sched) -{ - if (!iso_sched) - return; - /* caller must hold fotg210->lock!*/ - list_splice(&iso_sched->td_list, &stream->free_list); - kfree(iso_sched); -} - -static int itd_urb_transaction(struct fotg210_iso_stream *stream, - struct fotg210_hcd *fotg210, struct urb *urb, gfp_t mem_flags) -{ - struct fotg210_itd *itd; - dma_addr_t itd_dma; - int i; - unsigned num_itds; - struct fotg210_iso_sched *sched; - unsigned long flags; - - sched = iso_sched_alloc(urb->number_of_packets, mem_flags); - if (unlikely(sched == NULL)) - return -ENOMEM; - - itd_sched_init(fotg210, sched, stream, urb); - - if (urb->interval < 8) - num_itds = 1 + (sched->span + 7) / 8; - else - num_itds = urb->number_of_packets; - - /* allocate/init ITDs */ - spin_lock_irqsave(&fotg210->lock, flags); - for (i = 0; i < num_itds; i++) { - - /* - * Use iTDs from the free list, but not iTDs that may - * still be in use by the hardware. - */ - if (likely(!list_empty(&stream->free_list))) { - itd = list_first_entry(&stream->free_list, - struct fotg210_itd, itd_list); - if (itd->frame == fotg210->now_frame) - goto alloc_itd; - list_del(&itd->itd_list); - itd_dma = itd->itd_dma; - } else { -alloc_itd: - spin_unlock_irqrestore(&fotg210->lock, flags); - itd = dma_pool_alloc(fotg210->itd_pool, mem_flags, - &itd_dma); - spin_lock_irqsave(&fotg210->lock, flags); - if (!itd) { - iso_sched_free(stream, sched); - spin_unlock_irqrestore(&fotg210->lock, flags); - return -ENOMEM; - } - } - - memset(itd, 0, sizeof(*itd)); - itd->itd_dma = itd_dma; - list_add(&itd->itd_list, &sched->td_list); - } - spin_unlock_irqrestore(&fotg210->lock, flags); - - /* temporarily store schedule info in hcpriv */ - urb->hcpriv = sched; - urb->error_count = 0; - return 0; -} - -static inline int itd_slot_ok(struct fotg210_hcd *fotg210, u32 mod, u32 uframe, - u8 usecs, u32 period) -{ - uframe %= period; - do { - /* can't commit more than uframe_periodic_max usec */ - if (periodic_usecs(fotg210, uframe >> 3, uframe & 0x7) - > (fotg210->uframe_periodic_max - usecs)) - return 0; - - /* we know urb->interval is 2^N uframes */ - uframe += period; - } while (uframe < mod); - return 1; -} - -/* This scheduler plans almost as far into the future as it has actual - * periodic schedule slots. (Affected by TUNE_FLS, which defaults to - * "as small as possible" to be cache-friendlier.) That limits the size - * transfers you can stream reliably; avoid more than 64 msec per urb. - * Also avoid queue depths of less than fotg210's worst irq latency (affected - * by the per-urb URB_NO_INTERRUPT hint, the log2_irq_thresh module parameter, - * and other factors); or more than about 230 msec total (for portability, - * given FOTG210_TUNE_FLS and the slop). Or, write a smarter scheduler! - */ - -#define SCHEDULE_SLOP 80 /* microframes */ - -static int iso_stream_schedule(struct fotg210_hcd *fotg210, struct urb *urb, - struct fotg210_iso_stream *stream) -{ - u32 now, next, start, period, span; - int status; - unsigned mod = fotg210->periodic_size << 3; - struct fotg210_iso_sched *sched = urb->hcpriv; - - period = urb->interval; - span = sched->span; - - if (span > mod - SCHEDULE_SLOP) { - fotg210_dbg(fotg210, "iso request %p too long\n", urb); - status = -EFBIG; - goto fail; - } - - now = fotg210_read_frame_index(fotg210) & (mod - 1); - - /* Typical case: reuse current schedule, stream is still active. - * Hopefully there are no gaps from the host falling behind - * (irq delays etc), but if there are we'll take the next - * slot in the schedule, implicitly assuming URB_ISO_ASAP. - */ - if (likely(!list_empty(&stream->td_list))) { - u32 excess; - - /* For high speed devices, allow scheduling within the - * isochronous scheduling threshold. For full speed devices - * and Intel PCI-based controllers, don't (work around for - * Intel ICH9 bug). - */ - if (!stream->highspeed && fotg210->fs_i_thresh) - next = now + fotg210->i_thresh; - else - next = now; - - /* Fell behind (by up to twice the slop amount)? - * We decide based on the time of the last currently-scheduled - * slot, not the time of the next available slot. - */ - excess = (stream->next_uframe - period - next) & (mod - 1); - if (excess >= mod - 2 * SCHEDULE_SLOP) - start = next + excess - mod + period * - DIV_ROUND_UP(mod - excess, period); - else - start = next + excess + period; - if (start - now >= mod) { - fotg210_dbg(fotg210, "request %p would overflow (%d+%d >= %d)\n", - urb, start - now - period, period, - mod); - status = -EFBIG; - goto fail; - } - } - - /* need to schedule; when's the next (u)frame we could start? - * this is bigger than fotg210->i_thresh allows; scheduling itself - * isn't free, the slop should handle reasonably slow cpus. it - * can also help high bandwidth if the dma and irq loads don't - * jump until after the queue is primed. - */ - else { - int done = 0; - - start = SCHEDULE_SLOP + (now & ~0x07); - - /* NOTE: assumes URB_ISO_ASAP, to limit complexity/bugs */ - - /* find a uframe slot with enough bandwidth. - * Early uframes are more precious because full-speed - * iso IN transfers can't use late uframes, - * and therefore they should be allocated last. - */ - next = start; - start += period; - do { - start--; - /* check schedule: enough space? */ - if (itd_slot_ok(fotg210, mod, start, - stream->usecs, period)) - done = 1; - } while (start > next && !done); - - /* no room in the schedule */ - if (!done) { - fotg210_dbg(fotg210, "iso resched full %p (now %d max %d)\n", - urb, now, now + mod); - status = -ENOSPC; - goto fail; - } - } - - /* Tried to schedule too far into the future? */ - if (unlikely(start - now + span - period >= - mod - 2 * SCHEDULE_SLOP)) { - fotg210_dbg(fotg210, "request %p would overflow (%d+%d >= %d)\n", - urb, start - now, span - period, - mod - 2 * SCHEDULE_SLOP); - status = -EFBIG; - goto fail; - } - - stream->next_uframe = start & (mod - 1); - - /* report high speed start in uframes; full speed, in frames */ - urb->start_frame = stream->next_uframe; - if (!stream->highspeed) - urb->start_frame >>= 3; - - /* Make sure scan_isoc() sees these */ - if (fotg210->isoc_count == 0) - fotg210->next_frame = now >> 3; - return 0; - -fail: - return status; -} - -static inline void itd_init(struct fotg210_hcd *fotg210, - struct fotg210_iso_stream *stream, struct fotg210_itd *itd) -{ - int i; - - /* it's been recently zeroed */ - itd->hw_next = FOTG210_LIST_END(fotg210); - itd->hw_bufp[0] = stream->buf0; - itd->hw_bufp[1] = stream->buf1; - itd->hw_bufp[2] = stream->buf2; - - for (i = 0; i < 8; i++) - itd->index[i] = -1; - - /* All other fields are filled when scheduling */ -} - -static inline void itd_patch(struct fotg210_hcd *fotg210, - struct fotg210_itd *itd, struct fotg210_iso_sched *iso_sched, - unsigned index, u16 uframe) -{ - struct fotg210_iso_packet *uf = &iso_sched->packet[index]; - unsigned pg = itd->pg; - - uframe &= 0x07; - itd->index[uframe] = index; - - itd->hw_transaction[uframe] = uf->transaction; - itd->hw_transaction[uframe] |= cpu_to_hc32(fotg210, pg << 12); - itd->hw_bufp[pg] |= cpu_to_hc32(fotg210, uf->bufp & ~(u32)0); - itd->hw_bufp_hi[pg] |= cpu_to_hc32(fotg210, (u32)(uf->bufp >> 32)); - - /* iso_frame_desc[].offset must be strictly increasing */ - if (unlikely(uf->cross)) { - u64 bufp = uf->bufp + 4096; - - itd->pg = ++pg; - itd->hw_bufp[pg] |= cpu_to_hc32(fotg210, bufp & ~(u32)0); - itd->hw_bufp_hi[pg] |= cpu_to_hc32(fotg210, (u32)(bufp >> 32)); - } -} - -static inline void itd_link(struct fotg210_hcd *fotg210, unsigned frame, - struct fotg210_itd *itd) -{ - union fotg210_shadow *prev = &fotg210->pshadow[frame]; - __hc32 *hw_p = &fotg210->periodic[frame]; - union fotg210_shadow here = *prev; - __hc32 type = 0; - - /* skip any iso nodes which might belong to previous microframes */ - while (here.ptr) { - type = Q_NEXT_TYPE(fotg210, *hw_p); - if (type == cpu_to_hc32(fotg210, Q_TYPE_QH)) - break; - prev = periodic_next_shadow(fotg210, prev, type); - hw_p = shadow_next_periodic(fotg210, &here, type); - here = *prev; - } - - itd->itd_next = here; - itd->hw_next = *hw_p; - prev->itd = itd; - itd->frame = frame; - wmb(); - *hw_p = cpu_to_hc32(fotg210, itd->itd_dma | Q_TYPE_ITD); -} - -/* fit urb's itds into the selected schedule slot; activate as needed */ -static void itd_link_urb(struct fotg210_hcd *fotg210, struct urb *urb, - unsigned mod, struct fotg210_iso_stream *stream) -{ - int packet; - unsigned next_uframe, uframe, frame; - struct fotg210_iso_sched *iso_sched = urb->hcpriv; - struct fotg210_itd *itd; - - next_uframe = stream->next_uframe & (mod - 1); - - if (unlikely(list_empty(&stream->td_list))) { - fotg210_to_hcd(fotg210)->self.bandwidth_allocated - += stream->bandwidth; - fotg210_dbg(fotg210, - "schedule devp %s ep%d%s-iso period %d start %d.%d\n", - urb->dev->devpath, stream->bEndpointAddress & 0x0f, - (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out", - urb->interval, - next_uframe >> 3, next_uframe & 0x7); - } - - /* fill iTDs uframe by uframe */ - for (packet = 0, itd = NULL; packet < urb->number_of_packets;) { - if (itd == NULL) { - /* ASSERT: we have all necessary itds */ - - /* ASSERT: no itds for this endpoint in this uframe */ - - itd = list_entry(iso_sched->td_list.next, - struct fotg210_itd, itd_list); - list_move_tail(&itd->itd_list, &stream->td_list); - itd->stream = stream; - itd->urb = urb; - itd_init(fotg210, stream, itd); - } - - uframe = next_uframe & 0x07; - frame = next_uframe >> 3; - - itd_patch(fotg210, itd, iso_sched, packet, uframe); - - next_uframe += stream->interval; - next_uframe &= mod - 1; - packet++; - - /* link completed itds into the schedule */ - if (((next_uframe >> 3) != frame) - || packet == urb->number_of_packets) { - itd_link(fotg210, frame & (fotg210->periodic_size - 1), - itd); - itd = NULL; - } - } - stream->next_uframe = next_uframe; - - /* don't need that schedule data any more */ - iso_sched_free(stream, iso_sched); - urb->hcpriv = NULL; - - ++fotg210->isoc_count; - enable_periodic(fotg210); -} - -#define ISO_ERRS (FOTG210_ISOC_BUF_ERR | FOTG210_ISOC_BABBLE |\ - FOTG210_ISOC_XACTERR) - -/* Process and recycle a completed ITD. Return true iff its urb completed, - * and hence its completion callback probably added things to the hardware - * schedule. - * - * Note that we carefully avoid recycling this descriptor until after any - * completion callback runs, so that it won't be reused quickly. That is, - * assuming (a) no more than two urbs per frame on this endpoint, and also - * (b) only this endpoint's completions submit URBs. It seems some silicon - * corrupts things if you reuse completed descriptors very quickly... - */ -static bool itd_complete(struct fotg210_hcd *fotg210, struct fotg210_itd *itd) -{ - struct urb *urb = itd->urb; - struct usb_iso_packet_descriptor *desc; - u32 t; - unsigned uframe; - int urb_index = -1; - struct fotg210_iso_stream *stream = itd->stream; - struct usb_device *dev; - bool retval = false; - - /* for each uframe with a packet */ - for (uframe = 0; uframe < 8; uframe++) { - if (likely(itd->index[uframe] == -1)) - continue; - urb_index = itd->index[uframe]; - desc = &urb->iso_frame_desc[urb_index]; - - t = hc32_to_cpup(fotg210, &itd->hw_transaction[uframe]); - itd->hw_transaction[uframe] = 0; - - /* report transfer status */ - if (unlikely(t & ISO_ERRS)) { - urb->error_count++; - if (t & FOTG210_ISOC_BUF_ERR) - desc->status = usb_pipein(urb->pipe) - ? -ENOSR /* hc couldn't read */ - : -ECOMM; /* hc couldn't write */ - else if (t & FOTG210_ISOC_BABBLE) - desc->status = -EOVERFLOW; - else /* (t & FOTG210_ISOC_XACTERR) */ - desc->status = -EPROTO; - - /* HC need not update length with this error */ - if (!(t & FOTG210_ISOC_BABBLE)) { - desc->actual_length = FOTG210_ITD_LENGTH(t); - urb->actual_length += desc->actual_length; - } - } else if (likely((t & FOTG210_ISOC_ACTIVE) == 0)) { - desc->status = 0; - desc->actual_length = FOTG210_ITD_LENGTH(t); - urb->actual_length += desc->actual_length; - } else { - /* URB was too late */ - desc->status = -EXDEV; - } - } - - /* handle completion now? */ - if (likely((urb_index + 1) != urb->number_of_packets)) - goto done; - - /* ASSERT: it's really the last itd for this urb - * list_for_each_entry (itd, &stream->td_list, itd_list) - * BUG_ON (itd->urb == urb); - */ - - /* give urb back to the driver; completion often (re)submits */ - dev = urb->dev; - fotg210_urb_done(fotg210, urb, 0); - retval = true; - urb = NULL; - - --fotg210->isoc_count; - disable_periodic(fotg210); - - if (unlikely(list_is_singular(&stream->td_list))) { - fotg210_to_hcd(fotg210)->self.bandwidth_allocated - -= stream->bandwidth; - fotg210_dbg(fotg210, - "deschedule devp %s ep%d%s-iso\n", - dev->devpath, stream->bEndpointAddress & 0x0f, - (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); - } - -done: - itd->urb = NULL; - - /* Add to the end of the free list for later reuse */ - list_move_tail(&itd->itd_list, &stream->free_list); - - /* Recycle the iTDs when the pipeline is empty (ep no longer in use) */ - if (list_empty(&stream->td_list)) { - list_splice_tail_init(&stream->free_list, - &fotg210->cached_itd_list); - start_free_itds(fotg210); - } - - return retval; -} - -static int itd_submit(struct fotg210_hcd *fotg210, struct urb *urb, - gfp_t mem_flags) -{ - int status = -EINVAL; - unsigned long flags; - struct fotg210_iso_stream *stream; - - /* Get iso_stream head */ - stream = iso_stream_find(fotg210, urb); - if (unlikely(stream == NULL)) { - fotg210_dbg(fotg210, "can't get iso stream\n"); - return -ENOMEM; - } - if (unlikely(urb->interval != stream->interval && - fotg210_port_speed(fotg210, 0) == - USB_PORT_STAT_HIGH_SPEED)) { - fotg210_dbg(fotg210, "can't change iso interval %d --> %d\n", - stream->interval, urb->interval); - goto done; - } - -#ifdef FOTG210_URB_TRACE - fotg210_dbg(fotg210, - "%s %s urb %p ep%d%s len %d, %d pkts %d uframes[%p]\n", - __func__, urb->dev->devpath, urb, - usb_pipeendpoint(urb->pipe), - usb_pipein(urb->pipe) ? "in" : "out", - urb->transfer_buffer_length, - urb->number_of_packets, urb->interval, - stream); -#endif - - /* allocate ITDs w/o locking anything */ - status = itd_urb_transaction(stream, fotg210, urb, mem_flags); - if (unlikely(status < 0)) { - fotg210_dbg(fotg210, "can't init itds\n"); - goto done; - } - - /* schedule ... need to lock */ - spin_lock_irqsave(&fotg210->lock, flags); - if (unlikely(!HCD_HW_ACCESSIBLE(fotg210_to_hcd(fotg210)))) { - status = -ESHUTDOWN; - goto done_not_linked; - } - status = usb_hcd_link_urb_to_ep(fotg210_to_hcd(fotg210), urb); - if (unlikely(status)) - goto done_not_linked; - status = iso_stream_schedule(fotg210, urb, stream); - if (likely(status == 0)) - itd_link_urb(fotg210, urb, fotg210->periodic_size << 3, stream); - else - usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), urb); -done_not_linked: - if (status < 0) { - iso_sched_free(stream, urb->hcpriv); - urb->hcpriv = NULL; - } - spin_unlock_irqrestore(&fotg210->lock, flags); -done: - return status; -} - -static inline int scan_frame_queue(struct fotg210_hcd *fotg210, unsigned frame, - unsigned now_frame, bool live) -{ - unsigned uf; - bool modified; - union fotg210_shadow q, *q_p; - __hc32 type, *hw_p; - - /* scan each element in frame's queue for completions */ - q_p = &fotg210->pshadow[frame]; - hw_p = &fotg210->periodic[frame]; - q.ptr = q_p->ptr; - type = Q_NEXT_TYPE(fotg210, *hw_p); - modified = false; - - while (q.ptr) { - switch (hc32_to_cpu(fotg210, type)) { - case Q_TYPE_ITD: - /* If this ITD is still active, leave it for - * later processing ... check the next entry. - * No need to check for activity unless the - * frame is current. - */ - if (frame == now_frame && live) { - rmb(); - for (uf = 0; uf < 8; uf++) { - if (q.itd->hw_transaction[uf] & - ITD_ACTIVE(fotg210)) - break; - } - if (uf < 8) { - q_p = &q.itd->itd_next; - hw_p = &q.itd->hw_next; - type = Q_NEXT_TYPE(fotg210, - q.itd->hw_next); - q = *q_p; - break; - } - } - - /* Take finished ITDs out of the schedule - * and process them: recycle, maybe report - * URB completion. HC won't cache the - * pointer for much longer, if at all. - */ - *q_p = q.itd->itd_next; - *hw_p = q.itd->hw_next; - type = Q_NEXT_TYPE(fotg210, q.itd->hw_next); - wmb(); - modified = itd_complete(fotg210, q.itd); - q = *q_p; - break; - default: - fotg210_dbg(fotg210, "corrupt type %d frame %d shadow %p\n", - type, frame, q.ptr); - fallthrough; - case Q_TYPE_QH: - case Q_TYPE_FSTN: - /* End of the iTDs and siTDs */ - q.ptr = NULL; - break; - } - - /* assume completion callbacks modify the queue */ - if (unlikely(modified && fotg210->isoc_count > 0)) - return -EINVAL; - } - return 0; -} - -static void scan_isoc(struct fotg210_hcd *fotg210) -{ - unsigned uf, now_frame, frame, ret; - unsigned fmask = fotg210->periodic_size - 1; - bool live; - - /* - * When running, scan from last scan point up to "now" - * else clean up by scanning everything that's left. - * Touches as few pages as possible: cache-friendly. - */ - if (fotg210->rh_state >= FOTG210_RH_RUNNING) { - uf = fotg210_read_frame_index(fotg210); - now_frame = (uf >> 3) & fmask; - live = true; - } else { - now_frame = (fotg210->next_frame - 1) & fmask; - live = false; - } - fotg210->now_frame = now_frame; - - frame = fotg210->next_frame; - for (;;) { - ret = 1; - while (ret != 0) - ret = scan_frame_queue(fotg210, frame, - now_frame, live); - - /* Stop when we have reached the current frame */ - if (frame == now_frame) - break; - frame = (frame + 1) & fmask; - } - fotg210->next_frame = now_frame; -} - -/* Display / Set uframe_periodic_max - */ -static ssize_t uframe_periodic_max_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct fotg210_hcd *fotg210; - - fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); - return sysfs_emit(buf, "%d\n", fotg210->uframe_periodic_max); -} - -static ssize_t uframe_periodic_max_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct fotg210_hcd *fotg210; - unsigned uframe_periodic_max; - unsigned frame, uframe; - unsigned short allocated_max; - unsigned long flags; - ssize_t ret; - - fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); - - ret = kstrtouint(buf, 0, &uframe_periodic_max); + /* FOTG210 embeds an EHCI core but moves and omits some registers. */ + ehci->port_status = hcd->regs + FOTG210_PORTSC; + ehci->get_port_speed = fotg210_get_port_speed; + ehci->pre_port_reset = fotg210_pre_port_reset; + ehci->post_port_reset = fotg210_post_port_reset; + ehci->no_configured_flag = 1; + ehci->no_tdi_mode = 1; + ehci->no_fsls_isoc = 1; + hcd->has_tt = 1; + + ret = ehci_setup(hcd); if (ret) return ret; - if (uframe_periodic_max < 100 || uframe_periodic_max >= 125) { - fotg210_info(fotg210, "rejecting invalid request for uframe_periodic_max=%u\n", - uframe_periodic_max); - return -EINVAL; + if (fotg->port != GEMINI_PORT_NONE) { + /* Work around the Gemini full-speed EOF1 timing erratum. */ + val = readl(fotg->base + FOTG210_MISC); + val &= ~(FOTG210_MISC_EOF1_MASK | FOTG210_MISC_AS_SLP_MASK); + val |= FOTG210_MISC_EOF1_720_21000_4000 | + FOTG210_MISC_AS_SLP_10US; + writel(val, fotg->base + FOTG210_MISC); } - ret = -EINVAL; - - /* - * lock, so that our checking does not race with possible periodic - * bandwidth allocation through submitting new urbs. - */ - spin_lock_irqsave(&fotg210->lock, flags); - - /* - * for request to decrease max periodic bandwidth, we have to check - * every microframe in the schedule to see whether the decrease is - * possible. - */ - if (uframe_periodic_max < fotg210->uframe_periodic_max) { - allocated_max = 0; - - for (frame = 0; frame < fotg210->periodic_size; ++frame) - for (uframe = 0; uframe < 7; ++uframe) - allocated_max = max(allocated_max, - periodic_usecs(fotg210, frame, - uframe)); - - if (allocated_max > uframe_periodic_max) { - fotg210_info(fotg210, - "cannot decrease uframe_periodic_max because periodic bandwidth is already allocated (%u > %u)\n", - allocated_max, uframe_periodic_max); - goto out_unlock; - } - } - - /* increasing is always ok */ - - fotg210_info(fotg210, - "setting max periodic bandwidth to %u%% (== %u usec/uframe)\n", - 100 * uframe_periodic_max/125, uframe_periodic_max); - - if (uframe_periodic_max != 100) - fotg210_warn(fotg210, "max periodic bandwidth set is non-standard\n"); - - fotg210->uframe_periodic_max = uframe_periodic_max; - ret = count; - -out_unlock: - spin_unlock_irqrestore(&fotg210->lock, flags); - return ret; -} - -static DEVICE_ATTR_RW(uframe_periodic_max); - -static inline int create_sysfs_files(struct fotg210_hcd *fotg210) -{ - struct device *controller = fotg210_to_hcd(fotg210)->self.controller; - - return device_create_file(controller, &dev_attr_uframe_periodic_max); -} - -static inline void remove_sysfs_files(struct fotg210_hcd *fotg210) -{ - struct device *controller = fotg210_to_hcd(fotg210)->self.controller; - - device_remove_file(controller, &dev_attr_uframe_periodic_max); -} -/* On some systems, leaving remote wakeup enabled prevents system shutdown. - * The firmware seems to think that powering off is a wakeup event! - * This routine turns off remote wakeup and everything else, on all ports. - */ -static void fotg210_turn_off_all_ports(struct fotg210_hcd *fotg210) -{ - u32 __iomem *status_reg = &fotg210->regs->port_status; - - fotg210_writel(fotg210, PORT_RWC_BITS, status_reg); -} - -/* Halt HC, turn off all ports, and let the BIOS use the companion controllers. - * Must be called with interrupts enabled and the lock not held. - */ -static void fotg210_silence_controller(struct fotg210_hcd *fotg210) -{ - fotg210_halt(fotg210); - - spin_lock_irq(&fotg210->lock); - fotg210->rh_state = FOTG210_RH_HALTED; - fotg210_turn_off_all_ports(fotg210); - spin_unlock_irq(&fotg210->lock); -} - -/* fotg210_shutdown kick in for silicon on any bus (not just pci, etc). - * This forcibly disables dma and IRQs, helping kexec and other cases - * where the next system software may expect clean state. - */ -static void fotg210_shutdown(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - - spin_lock_irq(&fotg210->lock); - fotg210->shutdown = true; - fotg210->rh_state = FOTG210_RH_STOPPING; - fotg210->enabled_hrtimer_events = 0; - spin_unlock_irq(&fotg210->lock); - - fotg210_silence_controller(fotg210); - - hrtimer_cancel(&fotg210->hrtimer); -} - -/* fotg210_work is called from some interrupts, timers, and so on. - * it calls driver completion functions, after dropping fotg210->lock. - */ -static void fotg210_work(struct fotg210_hcd *fotg210) -{ - /* another CPU may drop fotg210->lock during a schedule scan while - * it reports urb completions. this flag guards against bogus - * attempts at re-entrant schedule scanning. - */ - if (fotg210->scanning) { - fotg210->need_rescan = true; - return; - } - fotg210->scanning = true; - -rescan: - fotg210->need_rescan = false; - if (fotg210->async_count) - scan_async(fotg210); - if (fotg210->intr_count > 0) - scan_intr(fotg210); - if (fotg210->isoc_count > 0) - scan_isoc(fotg210); - if (fotg210->need_rescan) - goto rescan; - fotg210->scanning = false; - - /* the IO watchdog guards against hardware or driver bugs that - * misplace IRQs, and should let us run completely without IRQs. - * such lossage has been observed on both VT6202 and VT8235. - */ - turn_on_io_watchdog(fotg210); -} - -/* Called when the fotg210_hcd module is removed. - */ -static void fotg210_stop(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - - fotg210_dbg(fotg210, "stop\n"); - - /* no more interrupts ... */ - - spin_lock_irq(&fotg210->lock); - fotg210->enabled_hrtimer_events = 0; - spin_unlock_irq(&fotg210->lock); - - fotg210_quiesce(fotg210); - fotg210_silence_controller(fotg210); - fotg210_reset(fotg210); - - hrtimer_cancel(&fotg210->hrtimer); - remove_sysfs_files(fotg210); - remove_debug_files(fotg210); - - /* root hub is shut down separately (first, when possible) */ - spin_lock_irq(&fotg210->lock); - end_free_itds(fotg210); - spin_unlock_irq(&fotg210->lock); - fotg210_mem_cleanup(fotg210); - -#ifdef FOTG210_STATS - fotg210_dbg(fotg210, "irq normal %ld err %ld iaa %ld (lost %ld)\n", - fotg210->stats.normal, fotg210->stats.error, - fotg210->stats.iaa, fotg210->stats.lost_iaa); - fotg210_dbg(fotg210, "complete %ld unlink %ld\n", - fotg210->stats.complete, fotg210->stats.unlink); -#endif - - dbg_status(fotg210, "fotg210_stop completed", - fotg210_readl(fotg210, &fotg210->regs->status)); -} - -/* one-time init, only for memory state */ -static int hcd_fotg210_init(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - u32 temp; - int retval; - u32 hcc_params; - struct fotg210_qh_hw *hw; - - spin_lock_init(&fotg210->lock); - - /* - * keep io watchdog by default, those good HCDs could turn off it later - */ - fotg210->need_io_watchdog = 1; - - hrtimer_setup(&fotg210->hrtimer, fotg210_hrtimer_func, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); - fotg210->next_hrtimer_event = FOTG210_HRTIMER_NO_EVENT; - - hcc_params = fotg210_readl(fotg210, &fotg210->caps->hcc_params); - - /* - * by default set standard 80% (== 100 usec/uframe) max periodic - * bandwidth as required by USB 2.0 - */ - fotg210->uframe_periodic_max = 100; - - /* - * hw default: 1K periodic list heads, one per frame. - * periodic_size can shrink by USBCMD update if hcc_params allows. - */ - fotg210->periodic_size = DEFAULT_I_TDPS; - INIT_LIST_HEAD(&fotg210->intr_qh_list); - INIT_LIST_HEAD(&fotg210->cached_itd_list); - - if (HCC_PGM_FRAMELISTLEN(hcc_params)) { - /* periodic schedule size can be smaller than default */ - switch (FOTG210_TUNE_FLS) { - case 0: - fotg210->periodic_size = 1024; - break; - case 1: - fotg210->periodic_size = 512; - break; - case 2: - fotg210->periodic_size = 256; - break; - default: - BUG(); - } - } - retval = fotg210_mem_init(fotg210, GFP_KERNEL); - if (retval < 0) - return retval; - - /* controllers may cache some of the periodic schedule ... */ - fotg210->i_thresh = 2; - - /* - * dedicate a qh for the async ring head, since we couldn't unlink - * a 'real' qh without stopping the async schedule [4.8]. use it - * as the 'reclamation list head' too. - * its dummy is used in hw_alt_next of many tds, to prevent the qh - * from automatically advancing to the next td after short reads. - */ - fotg210->async->qh_next.qh = NULL; - hw = fotg210->async->hw; - hw->hw_next = QH_NEXT(fotg210, fotg210->async->qh_dma); - hw->hw_info1 = cpu_to_hc32(fotg210, QH_HEAD); - hw->hw_token = cpu_to_hc32(fotg210, QTD_STS_HALT); - hw->hw_qtd_next = FOTG210_LIST_END(fotg210); - fotg210->async->qh_state = QH_STATE_LINKED; - hw->hw_alt_next = QTD_NEXT(fotg210, fotg210->async->dummy->qtd_dma); - - /* clear interrupt enables, set irq latency */ - if (log2_irq_thresh < 0 || log2_irq_thresh > 6) - log2_irq_thresh = 0; - temp = 1 << (16 + log2_irq_thresh); - if (HCC_CANPARK(hcc_params)) { - /* HW default park == 3, on hardware that supports it (like - * NVidia and ALI silicon), maximizes throughput on the async - * schedule by avoiding QH fetches between transfers. - * - * With fast usb storage devices and NForce2, "park" seems to - * make problems: throughput reduction (!), data errors... - */ - if (park) { - park = min_t(unsigned, park, 3); - temp |= CMD_PARK; - temp |= park << 8; - } - fotg210_dbg(fotg210, "park %d\n", park); - } - if (HCC_PGM_FRAMELISTLEN(hcc_params)) { - /* periodic schedule size can be smaller than default */ - temp &= ~(3 << 2); - temp |= (FOTG210_TUNE_FLS << 2); - } - fotg210->command = temp; - - /* Accept arbitrarily long scatter-gather lists */ - if (!hcd->localmem_pool) - hcd->self.sg_tablesize = ~0; - return 0; -} - -/* start HC running; it's halted, hcd_fotg210_init() has been run (once) */ -static int fotg210_run(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - u32 temp; - - hcd->uses_new_polling = 1; - - /* EHCI spec section 4.1 */ - - fotg210_writel(fotg210, fotg210->periodic_dma, - &fotg210->regs->frame_list); - fotg210_writel(fotg210, (u32)fotg210->async->qh_dma, - &fotg210->regs->async_next); - - /* - * hcc_params controls whether fotg210->regs->segment must (!!!) - * be used; it constrains QH/ITD/SITD and QTD locations. - * dma_pool consistent memory always uses segment zero. - * streaming mappings for I/O buffers, like dma_map_single(), - * can return segments above 4GB, if the device allows. - * - * NOTE: the dma mask is visible through dev->dma_mask, so - * drivers can pass this info along ... like NETIF_F_HIGHDMA, - * Scsi_Host.highmem_io, and so forth. It's readonly to all - * host side drivers though. - */ - fotg210_readl(fotg210, &fotg210->caps->hcc_params); - - /* - * Philips, Intel, and maybe others need CMD_RUN before the - * root hub will detect new devices (why?); NEC doesn't - */ - fotg210->command &= ~(CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); - fotg210->command |= CMD_RUN; - fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); - dbg_cmd(fotg210, "init", fotg210->command); - - /* - * Start, enabling full USB 2.0 functionality ... usb 1.1 devices - * are explicitly handed to companion controller(s), so no TT is - * involved with the root hub. (Except where one is integrated, - * and there's no companion controller unless maybe for USB OTG.) - * - * Turning on the CF flag will transfer ownership of all ports - * from the companions to the EHCI controller. If any of the - * companions are in the middle of a port reset at the time, it - * could cause trouble. Write-locking ehci_cf_port_reset_rwsem - * guarantees that no resets are in progress. After we set CF, - * a short delay lets the hardware catch up; new resets shouldn't - * be started before the port switching actions could complete. - */ - down_write(&ehci_cf_port_reset_rwsem); - fotg210->rh_state = FOTG210_RH_RUNNING; - /* unblock posted writes */ - fotg210_readl(fotg210, &fotg210->regs->command); - usleep_range(5000, 10000); - up_write(&ehci_cf_port_reset_rwsem); - fotg210->last_periodic_enable = ktime_get_real(); - - temp = HC_VERSION(fotg210, - fotg210_readl(fotg210, &fotg210->caps->hc_capbase)); - fotg210_info(fotg210, - "USB %x.%x started, EHCI %x.%02x\n", - ((fotg210->sbrn & 0xf0) >> 4), (fotg210->sbrn & 0x0f), - temp >> 8, temp & 0xff); - - fotg210_writel(fotg210, INTR_MASK, - &fotg210->regs->intr_enable); /* Turn On Interrupts */ - - /* GRR this is run-once init(), being done every time the HC starts. - * So long as they're part of class devices, we can't do it init() - * since the class device isn't created that early. - */ - create_debug_files(fotg210); - create_sysfs_files(fotg210); - return 0; } -static int fotg210_setup(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - int retval; - - fotg210->regs = (void __iomem *)fotg210->caps + - HC_LENGTH(fotg210, - fotg210_readl(fotg210, &fotg210->caps->hc_capbase)); - dbg_hcs_params(fotg210, "reset"); - dbg_hcc_params(fotg210, "reset"); - - /* cache this readonly data; minimize chip reads */ - fotg210->hcs_params = fotg210_readl(fotg210, - &fotg210->caps->hcs_params); - - fotg210->sbrn = HCD_USB2; - - /* data structure init */ - retval = hcd_fotg210_init(hcd); - if (retval) - return retval; - - retval = fotg210_halt(fotg210); - if (retval) - return retval; - - fotg210_reset(fotg210); - - return 0; -} - -static irqreturn_t fotg210_irq(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - u32 status, masked_status, pcd_status = 0, cmd; - int bh; - - spin_lock(&fotg210->lock); - - status = fotg210_readl(fotg210, &fotg210->regs->status); - - /* e.g. cardbus physical eject */ - if (status == ~(u32) 0) { - fotg210_dbg(fotg210, "device removed\n"); - goto dead; - } - - /* - * We don't use STS_FLR, but some controllers don't like it to - * remain on, so mask it out along with the other status bits. - */ - masked_status = status & (INTR_MASK | STS_FLR); - - /* Shared IRQ? */ - if (!masked_status || - unlikely(fotg210->rh_state == FOTG210_RH_HALTED)) { - spin_unlock(&fotg210->lock); - return IRQ_NONE; - } - - /* clear (just) interrupts */ - fotg210_writel(fotg210, masked_status, &fotg210->regs->status); - cmd = fotg210_readl(fotg210, &fotg210->regs->command); - bh = 0; - - /* unrequested/ignored: Frame List Rollover */ - dbg_status(fotg210, "irq", status); - - /* INT, ERR, and IAA interrupt rates can be throttled */ - - /* normal [4.15.1.2] or error [4.15.1.1] completion */ - if (likely((status & (STS_INT|STS_ERR)) != 0)) { - if (likely((status & STS_ERR) == 0)) - INCR(fotg210->stats.normal); - else - INCR(fotg210->stats.error); - bh = 1; - } - - /* complete the unlinking of some qh [4.15.2.3] */ - if (status & STS_IAA) { - - /* Turn off the IAA watchdog */ - fotg210->enabled_hrtimer_events &= - ~BIT(FOTG210_HRTIMER_IAA_WATCHDOG); - - /* - * Mild optimization: Allow another IAAD to reset the - * hrtimer, if one occurs before the next expiration. - * In theory we could always cancel the hrtimer, but - * tests show that about half the time it will be reset - * for some other event anyway. - */ - if (fotg210->next_hrtimer_event == FOTG210_HRTIMER_IAA_WATCHDOG) - ++fotg210->next_hrtimer_event; - - /* guard against (alleged) silicon errata */ - if (cmd & CMD_IAAD) - fotg210_dbg(fotg210, "IAA with IAAD still set?\n"); - if (fotg210->async_iaa) { - INCR(fotg210->stats.iaa); - end_unlink_async(fotg210); - } else - fotg210_dbg(fotg210, "IAA with nothing unlinked?\n"); - } - - /* remote wakeup [4.3.1] */ - if (status & STS_PCD) { - int pstatus; - u32 __iomem *status_reg = &fotg210->regs->port_status; - - /* kick root hub later */ - pcd_status = status; - - /* resume root hub? */ - if (fotg210->rh_state == FOTG210_RH_SUSPENDED) - usb_hcd_resume_root_hub(hcd); - - pstatus = fotg210_readl(fotg210, status_reg); - - if (test_bit(0, &fotg210->suspended_ports) && - ((pstatus & PORT_RESUME) || - !(pstatus & PORT_SUSPEND)) && - (pstatus & PORT_PE) && - fotg210->reset_done[0] == 0) { - - /* start 20 msec resume signaling from this port, - * and make hub_wq collect PORT_STAT_C_SUSPEND to - * stop that signaling. Use 5 ms extra for safety, - * like usb_port_resume() does. - */ - fotg210->reset_done[0] = jiffies + msecs_to_jiffies(25); - set_bit(0, &fotg210->resuming_ports); - fotg210_dbg(fotg210, "port 1 remote wakeup\n"); - mod_timer(&hcd->rh_timer, fotg210->reset_done[0]); - } - } - - /* PCI errors [4.15.2.4] */ - if (unlikely((status & STS_FATAL) != 0)) { - fotg210_err(fotg210, "fatal error\n"); - dbg_cmd(fotg210, "fatal", cmd); - dbg_status(fotg210, "fatal", status); -dead: - usb_hc_died(hcd); - - /* Don't let the controller do anything more */ - fotg210->shutdown = true; - fotg210->rh_state = FOTG210_RH_STOPPING; - fotg210->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); - fotg210_writel(fotg210, fotg210->command, - &fotg210->regs->command); - fotg210_writel(fotg210, 0, &fotg210->regs->intr_enable); - fotg210_handle_controller_death(fotg210); - - /* Handle completions when the controller stops */ - bh = 0; - } - - if (bh) - fotg210_work(fotg210); - spin_unlock(&fotg210->lock); - if (pcd_status) - usb_hcd_poll_rh_status(hcd); - return IRQ_HANDLED; -} - -/* non-error returns are a promise to giveback() the urb later - * we drop ownership so next owner (or urb unlink) can get it - * - * urb + dev is in hcd.self.controller.urb_list - * we're queueing TDs onto software and hardware lists - * - * hcd-specific init for hcpriv hasn't been done yet - * - * NOTE: control, bulk, and interrupt share the same code to append TDs - * to a (possibly active) QH, and the same QH scanning code. - */ -static int fotg210_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - struct list_head qtd_list; - - INIT_LIST_HEAD(&qtd_list); - - switch (usb_pipetype(urb->pipe)) { - case PIPE_CONTROL: - /* qh_completions() code doesn't handle all the fault cases - * in multi-TD control transfers. Even 1KB is rare anyway. - */ - if (urb->transfer_buffer_length > (16 * 1024)) - return -EMSGSIZE; - fallthrough; - /* case PIPE_BULK: */ - default: - if (!qh_urb_transaction(fotg210, urb, &qtd_list, mem_flags)) - return -ENOMEM; - return submit_async(fotg210, urb, &qtd_list, mem_flags); - - case PIPE_INTERRUPT: - if (!qh_urb_transaction(fotg210, urb, &qtd_list, mem_flags)) - return -ENOMEM; - return intr_submit(fotg210, urb, &qtd_list, mem_flags); - - case PIPE_ISOCHRONOUS: - return itd_submit(fotg210, urb, mem_flags); - } -} - -/* remove from hardware lists - * completions normally happen asynchronously - */ - -static int fotg210_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - struct fotg210_qh *qh; - unsigned long flags; - int rc; - - spin_lock_irqsave(&fotg210->lock, flags); - rc = usb_hcd_check_unlink_urb(hcd, urb, status); - if (rc) - goto done; - - switch (usb_pipetype(urb->pipe)) { - /* case PIPE_CONTROL: */ - /* case PIPE_BULK:*/ - default: - qh = (struct fotg210_qh *) urb->hcpriv; - if (!qh) - break; - switch (qh->qh_state) { - case QH_STATE_LINKED: - case QH_STATE_COMPLETING: - start_unlink_async(fotg210, qh); - break; - case QH_STATE_UNLINK: - case QH_STATE_UNLINK_WAIT: - /* already started */ - break; - case QH_STATE_IDLE: - /* QH might be waiting for a Clear-TT-Buffer */ - qh_completions(fotg210, qh); - break; - } - break; - - case PIPE_INTERRUPT: - qh = (struct fotg210_qh *) urb->hcpriv; - if (!qh) - break; - switch (qh->qh_state) { - case QH_STATE_LINKED: - case QH_STATE_COMPLETING: - start_unlink_intr(fotg210, qh); - break; - case QH_STATE_IDLE: - qh_completions(fotg210, qh); - break; - default: - fotg210_dbg(fotg210, "bogus qh %p state %d\n", - qh, qh->qh_state); - goto done; - } - break; - - case PIPE_ISOCHRONOUS: - /* itd... */ - - /* wait till next completion, do it then. */ - /* completion irqs can wait up to 1024 msec, */ - break; - } -done: - spin_unlock_irqrestore(&fotg210->lock, flags); - return rc; -} - -/* bulk qh holds the data toggle */ - -static void fotg210_endpoint_disable(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - unsigned long flags; - struct fotg210_qh *qh, *tmp; - - /* ASSERT: any requests/urbs are being unlinked */ - /* ASSERT: nobody can be submitting urbs for this any more */ - -rescan: - spin_lock_irqsave(&fotg210->lock, flags); - qh = ep->hcpriv; - if (!qh) - goto done; - - /* endpoints can be iso streams. for now, we don't - * accelerate iso completions ... so spin a while. - */ - if (qh->hw == NULL) { - struct fotg210_iso_stream *stream = ep->hcpriv; - - if (!list_empty(&stream->td_list)) - goto idle_timeout; - - /* BUG_ON(!list_empty(&stream->free_list)); */ - kfree(stream); - goto done; - } - - if (fotg210->rh_state < FOTG210_RH_RUNNING) - qh->qh_state = QH_STATE_IDLE; - switch (qh->qh_state) { - case QH_STATE_LINKED: - case QH_STATE_COMPLETING: - for (tmp = fotg210->async->qh_next.qh; - tmp && tmp != qh; - tmp = tmp->qh_next.qh) - continue; - /* periodic qh self-unlinks on empty, and a COMPLETING qh - * may already be unlinked. - */ - if (tmp) - start_unlink_async(fotg210, qh); - fallthrough; - case QH_STATE_UNLINK: /* wait for hw to finish? */ - case QH_STATE_UNLINK_WAIT: -idle_timeout: - spin_unlock_irqrestore(&fotg210->lock, flags); - schedule_timeout_uninterruptible(1); - goto rescan; - case QH_STATE_IDLE: /* fully unlinked */ - if (qh->clearing_tt) - goto idle_timeout; - if (list_empty(&qh->qtd_list)) { - qh_destroy(fotg210, qh); - break; - } - fallthrough; - default: - /* caller was supposed to have unlinked any requests; - * that's not our job. just leak this memory. - */ - fotg210_err(fotg210, "qh %p (#%02x) state %d%s\n", - qh, ep->desc.bEndpointAddress, qh->qh_state, - list_empty(&qh->qtd_list) ? "" : "(has tds)"); - break; - } -done: - ep->hcpriv = NULL; - spin_unlock_irqrestore(&fotg210->lock, flags); -} - -static void fotg210_endpoint_reset(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - struct fotg210_qh *qh; - int eptype = usb_endpoint_type(&ep->desc); - int epnum = usb_endpoint_num(&ep->desc); - int is_out = usb_endpoint_dir_out(&ep->desc); - unsigned long flags; - - if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT) - return; - - spin_lock_irqsave(&fotg210->lock, flags); - qh = ep->hcpriv; - - /* For Bulk and Interrupt endpoints we maintain the toggle state - * in the hardware; the toggle bits in udev aren't used at all. - * When an endpoint is reset by usb_clear_halt() we must reset - * the toggle bit in the QH. - */ - if (qh) { - usb_settoggle(qh->dev, epnum, is_out, 0); - if (!list_empty(&qh->qtd_list)) { - WARN_ONCE(1, "clear_halt for a busy endpoint\n"); - } else if (qh->qh_state == QH_STATE_LINKED || - qh->qh_state == QH_STATE_COMPLETING) { - - /* The toggle value in the QH can't be updated - * while the QH is active. Unlink it now; - * re-linking will call qh_refresh(). - */ - if (eptype == USB_ENDPOINT_XFER_BULK) - start_unlink_async(fotg210, qh); - else - start_unlink_intr(fotg210, qh); - } - } - spin_unlock_irqrestore(&fotg210->lock, flags); -} - -static int fotg210_get_frame(struct usb_hcd *hcd) -{ - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - - return (fotg210_read_frame_index(fotg210) >> 3) % - fotg210->periodic_size; -} - -/* The EHCI in ChipIdea HDRC cannot be a separate module or device, - * because its registers (and irq) are shared between host/gadget/otg - * functions and in order to facilitate role switching we cannot - * give the fotg210 driver exclusive access to those. - */ - -static const struct hc_driver fotg210_fotg210_hc_driver = { - .description = hcd_name, - .product_desc = "Faraday USB2.0 Host Controller", - .hcd_priv_size = sizeof(struct fotg210_hcd), - - /* - * generic hardware linkage - */ - .irq = fotg210_irq, - .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, - - /* - * basic lifecycle operations - */ - .reset = hcd_fotg210_init, - .start = fotg210_run, - .stop = fotg210_stop, - .shutdown = fotg210_shutdown, - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = fotg210_urb_enqueue, - .urb_dequeue = fotg210_urb_dequeue, - .endpoint_disable = fotg210_endpoint_disable, - .endpoint_reset = fotg210_endpoint_reset, - - /* - * scheduling support - */ - .get_frame_number = fotg210_get_frame, - - /* - * root hub support - */ - .hub_status_data = fotg210_hub_status_data, - .hub_control = fotg210_hub_control, - .bus_suspend = fotg210_bus_suspend, - .bus_resume = fotg210_bus_resume, - - .relinquish_port = fotg210_relinquish_port, - .port_handed_over = fotg210_port_handed_over, - - .clear_tt_buffer_complete = fotg210_clear_tt_buffer_complete, +static const struct ehci_driver_overrides fotg210_hc_overrides __initconst = { + .reset = fotg210_hcd_reset, + .extra_priv_size = sizeof(struct fotg210_hcd), }; -static void fotg210_init(struct fotg210_hcd *fotg210) -{ - u32 value; - - iowrite32(GMIR_MDEV_INT | GMIR_MOTG_INT | GMIR_INT_POLARITY, - &fotg210->regs->gmir); - - value = ioread32(&fotg210->regs->otgcsr); - value &= ~OTGCSR_A_BUS_DROP; - value |= OTGCSR_A_BUS_REQ; - iowrite32(value, &fotg210->regs->otgcsr); -} - /* * fotg210_hcd_probe - initialize faraday FOTG210 HCDs * @@ -5554,57 +134,39 @@ int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) struct device *dev = &pdev->dev; struct usb_hcd *hcd; int irq; - int retval; - struct fotg210_hcd *fotg210; + int ret; if (usb_disabled()) return -ENODEV; - pdev->dev.power.power_state = PMSG_ON; - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - hcd = usb_create_hcd(&fotg210_fotg210_hc_driver, dev, - dev_name(dev)); - if (!hcd) { - retval = dev_err_probe(dev, -ENOMEM, "failed to create hcd\n"); - goto fail_create_hcd; - } - - hcd->has_tt = 1; - - hcd->regs = fotg->base; + hcd = usb_create_hcd(&fotg210_hc_driver, dev, dev_name(dev)); + if (!hcd) + return dev_err_probe(dev, -ENOMEM, "failed to create hcd\n"); hcd->rsrc_start = fotg->res->start; hcd->rsrc_len = resource_size(fotg->res); + hcd->regs = fotg->base; + hcd_to_fotg210_priv(hcd)->fotg = fotg; + hcd_to_ehci(hcd)->caps = fotg->base; - fotg210 = hcd_to_fotg210(hcd); - - fotg210->fotg = fotg; - fotg210->caps = hcd->regs; - - retval = fotg210_setup(hcd); - if (retval) + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; goto failed_put_hcd; + } - fotg210_init(fotg210); - - retval = usb_add_hcd(hcd, irq, IRQF_SHARED); - if (retval) { - dev_err_probe(dev, retval, "failed to add hcd\n"); + ret = usb_add_hcd(hcd, irq, 0); + if (ret) { + dev_err_probe(dev, ret, "failed to add HCD\n"); goto failed_put_hcd; } device_wakeup_enable(hcd->self.controller); - platform_set_drvdata(pdev, hcd); - return retval; + return ret; failed_put_hcd: usb_put_hcd(hcd); -fail_create_hcd: - return dev_err_probe(dev, retval, "init %s fail\n", dev_name(dev)); + return ret; } /* @@ -5615,29 +177,43 @@ int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) int fotg210_hcd_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct fotg210 *fotg = hcd_to_fotg210_priv(hcd)->fotg; usb_remove_hcd(hcd); usb_put_hcd(hcd); + fotg210_vbus(fotg, false); return 0; } +int fotg210_hcd_suspend(struct device *dev) +{ +#ifdef CONFIG_PM + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return ehci_suspend(hcd, device_may_wakeup(dev)); +#else + return 0; +#endif +} + +int fotg210_hcd_resume(struct device *dev) +{ +#ifdef CONFIG_PM + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return ehci_resume(hcd, false); +#else + return 0; +#endif +} + int __init fotg210_hcd_init(void) { - if (usb_disabled()) - return -ENODEV; - - pr_debug("%s: block sizes: qh %zd qtd %zd itd %zd\n", - hcd_name, sizeof(struct fotg210_qh), - sizeof(struct fotg210_qtd), - sizeof(struct fotg210_itd)); - - fotg210_debug_root = debugfs_create_dir("fotg210", usb_debug_root); - + ehci_init_driver(&fotg210_hc_driver, &fotg210_hc_overrides); return 0; } -void __exit fotg210_hcd_cleanup(void) +void fotg210_hcd_cleanup(void) { - debugfs_remove(fotg210_debug_root); } diff --git a/drivers/usb/fotg210/fotg210-hcd.h b/drivers/usb/fotg210/fotg210-hcd.h deleted file mode 100644 index 13c9342982ee..000000000000 --- a/drivers/usb/fotg210/fotg210-hcd.h +++ /dev/null @@ -1,689 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __LINUX_FOTG210_H -#define __LINUX_FOTG210_H - -#include - -/* definitions used for the EHCI driver */ - -/* - * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to - * __leXX (normally) or __beXX (given FOTG210_BIG_ENDIAN_DESC), depending on - * the host controller implementation. - * - * To facilitate the strongest possible byte-order checking from "sparse" - * and so on, we use __leXX unless that's not practical. - */ -#define __hc32 __le32 -#define __hc16 __le16 - -/* statistics can be kept for tuning/monitoring */ -struct fotg210_stats { - /* irq usage */ - unsigned long normal; - unsigned long error; - unsigned long iaa; - unsigned long lost_iaa; - - /* termination of urbs from core */ - unsigned long complete; - unsigned long unlink; -}; - -/* fotg210_hcd->lock guards shared data against other CPUs: - * fotg210_hcd: async, unlink, periodic (and shadow), ... - * usb_host_endpoint: hcpriv - * fotg210_qh: qh_next, qtd_list - * fotg210_qtd: qtd_list - * - * Also, hold this lock when talking to HC registers or - * when updating hw_* fields in shared qh/qtd/... structures. - */ - -#define FOTG210_MAX_ROOT_PORTS 1 /* see HCS_N_PORTS */ - -/* - * fotg210_rh_state values of FOTG210_RH_RUNNING or above mean that the - * controller may be doing DMA. Lower values mean there's no DMA. - */ -enum fotg210_rh_state { - FOTG210_RH_HALTED, - FOTG210_RH_SUSPENDED, - FOTG210_RH_RUNNING, - FOTG210_RH_STOPPING -}; - -/* - * Timer events, ordered by increasing delay length. - * Always update event_delays_ns[] and event_handlers[] (defined in - * ehci-timer.c) in parallel with this list. - */ -enum fotg210_hrtimer_event { - FOTG210_HRTIMER_POLL_ASS, /* Poll for async schedule off */ - FOTG210_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ - FOTG210_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ - FOTG210_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ - FOTG210_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ - FOTG210_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */ - FOTG210_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ - FOTG210_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ - FOTG210_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ - FOTG210_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */ - FOTG210_HRTIMER_NUM_EVENTS /* Must come last */ -}; -#define FOTG210_HRTIMER_NO_EVENT 99 - -struct fotg210_hcd { /* one per controller */ - /* timing support */ - enum fotg210_hrtimer_event next_hrtimer_event; - unsigned enabled_hrtimer_events; - ktime_t hr_timeouts[FOTG210_HRTIMER_NUM_EVENTS]; - struct hrtimer hrtimer; - - int PSS_poll_count; - int ASS_poll_count; - int died_poll_count; - - /* glue to PCI and HCD framework */ - struct fotg210_caps __iomem *caps; - struct fotg210_regs __iomem *regs; - struct ehci_dbg_port __iomem *debug; - - __u32 hcs_params; /* cached register copy */ - spinlock_t lock; - enum fotg210_rh_state rh_state; - - /* general schedule support */ - bool scanning:1; - bool need_rescan:1; - bool intr_unlinking:1; - bool async_unlinking:1; - bool shutdown:1; - struct fotg210_qh *qh_scan_next; - - /* async schedule support */ - struct fotg210_qh *async; - struct fotg210_qh *dummy; /* For AMD quirk use */ - struct fotg210_qh *async_unlink; - struct fotg210_qh *async_unlink_last; - struct fotg210_qh *async_iaa; - unsigned async_unlink_cycle; - unsigned async_count; /* async activity count */ - - /* periodic schedule support */ -#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ - unsigned periodic_size; - __hc32 *periodic; /* hw periodic table */ - dma_addr_t periodic_dma; - struct list_head intr_qh_list; - unsigned i_thresh; /* uframes HC might cache */ - - union fotg210_shadow *pshadow; /* mirror hw periodic table */ - struct fotg210_qh *intr_unlink; - struct fotg210_qh *intr_unlink_last; - unsigned intr_unlink_cycle; - unsigned now_frame; /* frame from HC hardware */ - unsigned next_frame; /* scan periodic, start here */ - unsigned intr_count; /* intr activity count */ - unsigned isoc_count; /* isoc activity count */ - unsigned periodic_count; /* periodic activity count */ - /* max periodic time per uframe */ - unsigned uframe_periodic_max; - - - /* list of itds completed while now_frame was still active */ - struct list_head cached_itd_list; - struct fotg210_itd *last_itd_to_free; - - /* per root hub port */ - unsigned long reset_done[FOTG210_MAX_ROOT_PORTS]; - - /* bit vectors (one bit per port) - * which ports were already suspended at the start of a bus suspend - */ - unsigned long bus_suspended; - - /* which ports are edicated to the companion controller */ - unsigned long companion_ports; - - /* which ports are owned by the companion during a bus suspend */ - unsigned long owned_ports; - - /* which ports have the change-suspend feature turned on */ - unsigned long port_c_suspend; - - /* which ports are suspended */ - unsigned long suspended_ports; - - /* which ports have started to resume */ - unsigned long resuming_ports; - - /* per-HC memory pools (could be per-bus, but ...) */ - struct dma_pool *qh_pool; /* qh per active urb */ - struct dma_pool *qtd_pool; /* one or more per qh */ - struct dma_pool *itd_pool; /* itd per iso urb */ - - unsigned random_frame; - unsigned long next_statechange; - ktime_t last_periodic_enable; - u32 command; - - /* SILICON QUIRKS */ - unsigned need_io_watchdog:1; - unsigned fs_i_thresh:1; /* Intel iso scheduling */ - - u8 sbrn; /* packed release number */ - - /* irq statistics */ -#ifdef FOTG210_STATS - struct fotg210_stats stats; -# define INCR(x) ((x)++) -#else -# define INCR(x) do {} while (0) -#endif - - struct fotg210 *fotg; /* Overarching FOTG210 device */ - /* silicon clock */ - struct clk *pclk; -}; - -/* convert between an HCD pointer and the corresponding FOTG210_HCD */ -static inline struct fotg210_hcd *hcd_to_fotg210(struct usb_hcd *hcd) -{ - return (struct fotg210_hcd *)(hcd->hcd_priv); -} -static inline struct usb_hcd *fotg210_to_hcd(struct fotg210_hcd *fotg210) -{ - return container_of((void *) fotg210, struct usb_hcd, hcd_priv); -} - -/*-------------------------------------------------------------------------*/ - -/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ - -/* Section 2.2 Host Controller Capability Registers */ -struct fotg210_caps { - /* these fields are specified as 8 and 16 bit registers, - * but some hosts can't perform 8 or 16 bit PCI accesses. - * some hosts treat caplength and hciversion as parts of a 32-bit - * register, others treat them as two separate registers, this - * affects the memory map for big endian controllers. - */ - u32 hc_capbase; -#define HC_LENGTH(fotg210, p) (0x00ff&((p) >> /* bits 7:0 / offset 00h */ \ - (fotg210_big_endian_capbase(fotg210) ? 24 : 0))) -#define HC_VERSION(fotg210, p) (0xffff&((p) >> /* bits 31:16 / offset 02h */ \ - (fotg210_big_endian_capbase(fotg210) ? 0 : 16))) - u32 hcs_params; /* HCSPARAMS - offset 0x4 */ -#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ - - u32 hcc_params; /* HCCPARAMS - offset 0x8 */ -#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ -#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ - u8 portroute[8]; /* nibbles for routing - offset 0xC */ -}; - - -/* Section 2.3 Host Controller Operational Registers */ -struct fotg210_regs { - - /* USBCMD: offset 0x00 */ - u32 command; - -/* EHCI 1.1 addendum */ -/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ -#define CMD_PARK (1<<11) /* enable "park" on async qh */ -#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ -#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ -#define CMD_ASE (1<<5) /* async schedule enable */ -#define CMD_PSE (1<<4) /* periodic schedule enable */ -/* 3:2 is periodic frame list size */ -#define CMD_RESET (1<<1) /* reset HC not bus */ -#define CMD_RUN (1<<0) /* start/stop HC */ - - /* USBSTS: offset 0x04 */ - u32 status; -#define STS_ASS (1<<15) /* Async Schedule Status */ -#define STS_PSS (1<<14) /* Periodic Schedule Status */ -#define STS_RECL (1<<13) /* Reclamation */ -#define STS_HALT (1<<12) /* Not running (any reason) */ -/* some bits reserved */ - /* these STS_* flags are also intr_enable bits (USBINTR) */ -#define STS_IAA (1<<5) /* Interrupted on async advance */ -#define STS_FATAL (1<<4) /* such as some PCI access errors */ -#define STS_FLR (1<<3) /* frame list rolled over */ -#define STS_PCD (1<<2) /* port change detect */ -#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ -#define STS_INT (1<<0) /* "normal" completion (short, ...) */ - - /* USBINTR: offset 0x08 */ - u32 intr_enable; - - /* FRINDEX: offset 0x0C */ - u32 frame_index; /* current microframe number */ - /* CTRLDSSEGMENT: offset 0x10 */ - u32 segment; /* address bits 63:32 if needed */ - /* PERIODICLISTBASE: offset 0x14 */ - u32 frame_list; /* points to periodic list */ - /* ASYNCLISTADDR: offset 0x18 */ - u32 async_next; /* address of next async queue head */ - - u32 reserved1; - /* PORTSC: offset 0x20 */ - u32 port_status; -/* 31:23 reserved */ -#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ -#define PORT_RESET (1<<8) /* reset port */ -#define PORT_SUSPEND (1<<7) /* suspend port */ -#define PORT_RESUME (1<<6) /* resume it */ -#define PORT_PEC (1<<3) /* port enable change */ -#define PORT_PE (1<<2) /* port enable */ -#define PORT_CSC (1<<1) /* connect status change */ -#define PORT_CONNECT (1<<0) /* device connected */ -#define PORT_RWC_BITS (PORT_CSC | PORT_PEC) - u32 reserved2[19]; - - /* OTGCSR: offet 0x70 */ - u32 otgcsr; -#define OTGCSR_HOST_SPD_TYP (3 << 22) -#define OTGCSR_A_BUS_DROP (1 << 5) -#define OTGCSR_A_BUS_REQ (1 << 4) - - /* OTGISR: offset 0x74 */ - u32 otgisr; -#define OTGISR_OVC (1 << 10) - - u32 reserved3[15]; - - /* GMIR: offset 0xB4 */ - u32 gmir; -#define GMIR_INT_POLARITY (1 << 3) /*Active High*/ -#define GMIR_MHC_INT (1 << 2) -#define GMIR_MOTG_INT (1 << 1) -#define GMIR_MDEV_INT (1 << 0) -}; - -/*-------------------------------------------------------------------------*/ - -#define QTD_NEXT(fotg210, dma) cpu_to_hc32(fotg210, (u32)dma) - -/* - * EHCI Specification 0.95 Section 3.5 - * QTD: describe data transfer components (buffer, direction, ...) - * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". - * - * These are associated only with "QH" (Queue Head) structures, - * used with control, bulk, and interrupt transfers. - */ -struct fotg210_qtd { - /* first part defined by EHCI spec */ - __hc32 hw_next; /* see EHCI 3.5.1 */ - __hc32 hw_alt_next; /* see EHCI 3.5.2 */ - __hc32 hw_token; /* see EHCI 3.5.3 */ -#define QTD_TOGGLE (1 << 31) /* data toggle */ -#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) -#define QTD_IOC (1 << 15) /* interrupt on complete */ -#define QTD_CERR(tok) (((tok)>>10) & 0x3) -#define QTD_PID(tok) (((tok)>>8) & 0x3) -#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ -#define QTD_STS_HALT (1 << 6) /* halted on error */ -#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ -#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ -#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ -#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ -#define QTD_STS_STS (1 << 1) /* split transaction state */ -#define QTD_STS_PING (1 << 0) /* issue PING? */ - -#define ACTIVE_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_ACTIVE) -#define HALT_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_HALT) -#define STATUS_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_STS) - - __hc32 hw_buf[5]; /* see EHCI 3.5.4 */ - __hc32 hw_buf_hi[5]; /* Appendix B */ - - /* the rest is HCD-private */ - dma_addr_t qtd_dma; /* qtd address */ - struct list_head qtd_list; /* sw qtd list */ - struct urb *urb; /* qtd's urb */ - size_t length; /* length of buffer */ -} __aligned(32); - -/* mask NakCnt+T in qh->hw_alt_next */ -#define QTD_MASK(fotg210) cpu_to_hc32(fotg210, ~0x1f) - -#define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1) - -/*-------------------------------------------------------------------------*/ - -/* type tag from {qh,itd,fstn}->hw_next */ -#define Q_NEXT_TYPE(fotg210, dma) ((dma) & cpu_to_hc32(fotg210, 3 << 1)) - -/* - * Now the following defines are not converted using the - * cpu_to_le32() macro anymore, since we have to support - * "dynamic" switching between be and le support, so that the driver - * can be used on one system with SoC EHCI controller using big-endian - * descriptors as well as a normal little-endian PCI EHCI controller. - */ -/* values for that type tag */ -#define Q_TYPE_ITD (0 << 1) -#define Q_TYPE_QH (1 << 1) -#define Q_TYPE_SITD (2 << 1) -#define Q_TYPE_FSTN (3 << 1) - -/* next async queue entry, or pointer to interrupt/periodic QH */ -#define QH_NEXT(fotg210, dma) \ - (cpu_to_hc32(fotg210, (((u32)dma)&~0x01f)|Q_TYPE_QH)) - -/* for periodic/async schedules and qtd lists, mark end of list */ -#define FOTG210_LIST_END(fotg210) \ - cpu_to_hc32(fotg210, 1) /* "null pointer" to hw */ - -/* - * Entries in periodic shadow table are pointers to one of four kinds - * of data structure. That's dictated by the hardware; a type tag is - * encoded in the low bits of the hardware's periodic schedule. Use - * Q_NEXT_TYPE to get the tag. - * - * For entries in the async schedule, the type tag always says "qh". - */ -union fotg210_shadow { - struct fotg210_qh *qh; /* Q_TYPE_QH */ - struct fotg210_itd *itd; /* Q_TYPE_ITD */ - struct fotg210_fstn *fstn; /* Q_TYPE_FSTN */ - __hc32 *hw_next; /* (all types) */ - void *ptr; -}; - -/*-------------------------------------------------------------------------*/ - -/* - * EHCI Specification 0.95 Section 3.6 - * QH: describes control/bulk/interrupt endpoints - * See Fig 3-7 "Queue Head Structure Layout". - * - * These appear in both the async and (for interrupt) periodic schedules. - */ - -/* first part defined by EHCI spec */ -struct fotg210_qh_hw { - __hc32 hw_next; /* see EHCI 3.6.1 */ - __hc32 hw_info1; /* see EHCI 3.6.2 */ -#define QH_CONTROL_EP (1 << 27) /* FS/LS control endpoint */ -#define QH_HEAD (1 << 15) /* Head of async reclamation list */ -#define QH_TOGGLE_CTL (1 << 14) /* Data toggle control */ -#define QH_HIGH_SPEED (2 << 12) /* Endpoint speed */ -#define QH_LOW_SPEED (1 << 12) -#define QH_FULL_SPEED (0 << 12) -#define QH_INACTIVATE (1 << 7) /* Inactivate on next transaction */ - __hc32 hw_info2; /* see EHCI 3.6.2 */ -#define QH_SMASK 0x000000ff -#define QH_CMASK 0x0000ff00 -#define QH_HUBADDR 0x007f0000 -#define QH_HUBPORT 0x3f800000 -#define QH_MULT 0xc0000000 - __hc32 hw_current; /* qtd list - see EHCI 3.6.4 */ - - /* qtd overlay (hardware parts of a struct fotg210_qtd) */ - __hc32 hw_qtd_next; - __hc32 hw_alt_next; - __hc32 hw_token; - __hc32 hw_buf[5]; - __hc32 hw_buf_hi[5]; -} __aligned(32); - -struct fotg210_qh { - struct fotg210_qh_hw *hw; /* Must come first */ - /* the rest is HCD-private */ - dma_addr_t qh_dma; /* address of qh */ - union fotg210_shadow qh_next; /* ptr to qh; or periodic */ - struct list_head qtd_list; /* sw qtd list */ - struct list_head intr_node; /* list of intr QHs */ - struct fotg210_qtd *dummy; - struct fotg210_qh *unlink_next; /* next on unlink list */ - - unsigned unlink_cycle; - - u8 needs_rescan; /* Dequeue during giveback */ - u8 qh_state; -#define QH_STATE_LINKED 1 /* HC sees this */ -#define QH_STATE_UNLINK 2 /* HC may still see this */ -#define QH_STATE_IDLE 3 /* HC doesn't see this */ -#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on unlink q */ -#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ - - u8 xacterrs; /* XactErr retry counter */ -#define QH_XACTERR_MAX 32 /* XactErr retry limit */ - - /* periodic schedule info */ - u8 usecs; /* intr bandwidth */ - u8 gap_uf; /* uframes split/csplit gap */ - u8 c_usecs; /* ... split completion bw */ - u16 tt_usecs; /* tt downstream bandwidth */ - unsigned short period; /* polling interval */ - unsigned short start; /* where polling starts */ -#define NO_FRAME ((unsigned short)~0) /* pick new start */ - - struct usb_device *dev; /* access to TT */ - unsigned is_out:1; /* bulk or intr OUT */ - unsigned clearing_tt:1; /* Clear-TT-Buf in progress */ -}; - -/*-------------------------------------------------------------------------*/ - -/* description of one iso transaction (up to 3 KB data if highspeed) */ -struct fotg210_iso_packet { - /* These will be copied to iTD when scheduling */ - u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */ - __hc32 transaction; /* itd->hw_transaction[i] |= */ - u8 cross; /* buf crosses pages */ - /* for full speed OUT splits */ - u32 buf1; -}; - -/* temporary schedule data for packets from iso urbs (both speeds) - * each packet is one logical usb transaction to the device (not TT), - * beginning at stream->next_uframe - */ -struct fotg210_iso_sched { - struct list_head td_list; - unsigned span; - struct fotg210_iso_packet packet[]; -}; - -/* - * fotg210_iso_stream - groups all (s)itds for this endpoint. - * acts like a qh would, if EHCI had them for ISO. - */ -struct fotg210_iso_stream { - /* first field matches fotg210_hq, but is NULL */ - struct fotg210_qh_hw *hw; - - u8 bEndpointAddress; - u8 highspeed; - struct list_head td_list; /* queued itds */ - struct list_head free_list; /* list of unused itds */ - struct usb_device *udev; - struct usb_host_endpoint *ep; - - /* output of (re)scheduling */ - int next_uframe; - __hc32 splits; - - /* the rest is derived from the endpoint descriptor, - * trusting urb->interval == f(epdesc->bInterval) and - * including the extra info for hw_bufp[0..2] - */ - u8 usecs, c_usecs; - u16 interval; - u16 tt_usecs; - u16 maxp; - u16 raw_mask; - unsigned bandwidth; - - /* This is used to initialize iTD's hw_bufp fields */ - __hc32 buf0; - __hc32 buf1; - __hc32 buf2; - - /* this is used to initialize sITD's tt info */ - __hc32 address; -}; - -/*-------------------------------------------------------------------------*/ - -/* - * EHCI Specification 0.95 Section 3.3 - * Fig 3-4 "Isochronous Transaction Descriptor (iTD)" - * - * Schedule records for high speed iso xfers - */ -struct fotg210_itd { - /* first part defined by EHCI spec */ - __hc32 hw_next; /* see EHCI 3.3.1 */ - __hc32 hw_transaction[8]; /* see EHCI 3.3.2 */ -#define FOTG210_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ -#define FOTG210_ISOC_BUF_ERR (1<<30) /* Data buffer error */ -#define FOTG210_ISOC_BABBLE (1<<29) /* babble detected */ -#define FOTG210_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ -#define FOTG210_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) -#define FOTG210_ITD_IOC (1 << 15) /* interrupt on complete */ - -#define ITD_ACTIVE(fotg210) cpu_to_hc32(fotg210, FOTG210_ISOC_ACTIVE) - - __hc32 hw_bufp[7]; /* see EHCI 3.3.3 */ - __hc32 hw_bufp_hi[7]; /* Appendix B */ - - /* the rest is HCD-private */ - dma_addr_t itd_dma; /* for this itd */ - union fotg210_shadow itd_next; /* ptr to periodic q entry */ - - struct urb *urb; - struct fotg210_iso_stream *stream; /* endpoint's queue */ - struct list_head itd_list; /* list of stream's itds */ - - /* any/all hw_transactions here may be used by that urb */ - unsigned frame; /* where scheduled */ - unsigned pg; - unsigned index[8]; /* in urb->iso_frame_desc */ -} __aligned(32); - -/*-------------------------------------------------------------------------*/ - -/* - * EHCI Specification 0.96 Section 3.7 - * Periodic Frame Span Traversal Node (FSTN) - * - * Manages split interrupt transactions (using TT) that span frame boundaries - * into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN - * makes the HC jump (back) to a QH to scan for fs/ls QH completions until - * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. - */ -struct fotg210_fstn { - __hc32 hw_next; /* any periodic q entry */ - __hc32 hw_prev; /* qh or FOTG210_LIST_END */ - - /* the rest is HCD-private */ - dma_addr_t fstn_dma; - union fotg210_shadow fstn_next; /* ptr to periodic q entry */ -} __aligned(32); - -/*-------------------------------------------------------------------------*/ - -/* Prepare the PORTSC wakeup flags during controller suspend/resume */ - -#define fotg210_prepare_ports_for_controller_suspend(fotg210, do_wakeup) \ - fotg210_adjust_port_wakeup_flags(fotg210, true, do_wakeup) - -#define fotg210_prepare_ports_for_controller_resume(fotg210) \ - fotg210_adjust_port_wakeup_flags(fotg210, false, false) - -/*-------------------------------------------------------------------------*/ - -/* - * Some EHCI controllers have a Transaction Translator built into the - * root hub. This is a non-standard feature. Each controller will need - * to add code to the following inline functions, and call them as - * needed (mostly in root hub code). - */ - -static inline unsigned int -fotg210_get_speed(struct fotg210_hcd *fotg210, unsigned int portsc) -{ - return (readl(&fotg210->regs->otgcsr) - & OTGCSR_HOST_SPD_TYP) >> 22; -} - -/* Returns the speed of a device attached to a port on the root hub. */ -static inline unsigned int -fotg210_port_speed(struct fotg210_hcd *fotg210, unsigned int portsc) -{ - switch (fotg210_get_speed(fotg210, portsc)) { - case 0: - return 0; - case 1: - return USB_PORT_STAT_LOW_SPEED; - case 2: - default: - return USB_PORT_STAT_HIGH_SPEED; - } -} - -/*-------------------------------------------------------------------------*/ - -#define fotg210_has_fsl_portno_bug(e) (0) - -/* - * While most USB host controllers implement their registers in - * little-endian format, a minority (celleb companion chip) implement - * them in big endian format. - * - * This attempts to support either format at compile time without a - * runtime penalty, or both formats with the additional overhead - * of checking a flag bit. - * - */ - -#define fotg210_big_endian_mmio(e) 0 -#define fotg210_big_endian_capbase(e) 0 - -static inline unsigned int fotg210_readl(const struct fotg210_hcd *fotg210, - __u32 __iomem *regs) -{ - return readl(regs); -} - -static inline void fotg210_writel(const struct fotg210_hcd *fotg210, - const unsigned int val, __u32 __iomem *regs) -{ - writel(val, regs); -} - -/* cpu to fotg210 */ -static inline __hc32 cpu_to_hc32(const struct fotg210_hcd *fotg210, const u32 x) -{ - return cpu_to_le32(x); -} - -/* fotg210 to cpu */ -static inline u32 hc32_to_cpu(const struct fotg210_hcd *fotg210, const __hc32 x) -{ - return le32_to_cpu(x); -} - -static inline u32 hc32_to_cpup(const struct fotg210_hcd *fotg210, - const __hc32 *x) -{ - return le32_to_cpup(x); -} - -/*-------------------------------------------------------------------------*/ - -static inline unsigned fotg210_read_frame_index(struct fotg210_hcd *fotg210) -{ - return fotg210_readl(fotg210, &fotg210->regs->frame_index); -} - -/*-------------------------------------------------------------------------*/ - -#endif /* __LINUX_FOTG210_H */ diff --git a/drivers/usb/fotg210/fotg210.h b/drivers/usb/fotg210/fotg210.h index c44c0afe2956..aab0f0300dc8 100644 --- a/drivers/usb/fotg210/fotg210.h +++ b/drivers/usb/fotg210/fotg210.h @@ -2,6 +2,8 @@ #ifndef __FOTG210_H #define __FOTG210_H +#include + enum gemini_port { GEMINI_PORT_NONE = 0, GEMINI_PORT_0, @@ -15,6 +17,8 @@ struct fotg210 { struct clk *pclk; struct regmap *map; enum gemini_port port; + enum usb_dr_mode mode; + bool is_fotg210; }; void fotg210_vbus(struct fotg210 *fotg, bool enable); @@ -22,22 +26,37 @@ void fotg210_vbus(struct fotg210 *fotg, bool enable); #ifdef CONFIG_USB_FOTG210_HCD int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg); int fotg210_hcd_remove(struct platform_device *pdev); +int fotg210_hcd_suspend(struct device *dev); +int fotg210_hcd_resume(struct device *dev); int fotg210_hcd_init(void); void fotg210_hcd_cleanup(void); #else static inline int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) { - return 0; + return -ENODEV; } + static inline int fotg210_hcd_remove(struct platform_device *pdev) { - return 0; + return -ENODEV; } + +static inline int fotg210_hcd_suspend(struct device *dev) +{ + return -ENODEV; +} + +static inline int fotg210_hcd_resume(struct device *dev) +{ + return -ENODEV; +} + static inline int fotg210_hcd_init(void) { return 0; } + static inline void fotg210_hcd_cleanup(void) { } @@ -50,8 +69,9 @@ int fotg210_udc_remove(struct platform_device *pdev); static inline int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) { - return 0; + return -ENODEV; } + static inline int fotg210_udc_remove(struct platform_device *pdev) { return 0; From 2ea73ea69deb4cb0c246c4df6d59ce85240a8e63 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:17:43 +0200 Subject: [PATCH 073/158] usb: fotg210-udc: fix endpoint and resource handling Use the matching DMA direction when unmapping requests, reset the endpoint sequence before clearing its number, and update rather than accumulate endpoint configuration fields. Only dequeue requests that are actually queued and reject interrupts not owned by the UDC. Keep peripheral mode from sourcing VBUS, avoid sleeping under the UDC spinlock, and unwind the IRQ, notifier, PHY, MMIO, and gadget resources in ownership order. Suggested-by: Daniel Palmer Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-usb-fotg2-v3-6-dd92ecf5675b@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 97 +++++++++++++++++++------------ drivers/usb/fotg210/fotg210-udc.h | 2 +- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index d9e024873a42..fdd2cf82fde5 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -124,6 +124,7 @@ static void fotg210_set_fifo_dir(struct fotg210_ep *ep, u32 epnum, u32 dir_in) u32 val; val = ioread32(fotg210->reg + FOTG210_FIFOMAP); + val &= ~FIFOMAP_NA(epnum - 1); val |= (dir_in ? FIFOMAP_DIRIN(epnum - 1) : FIFOMAP_DIROUT(epnum - 1)); iowrite32(val, fotg210->reg + FOTG210_FIFOMAP); } @@ -134,6 +135,7 @@ static void fotg210_set_tfrtype(struct fotg210_ep *ep, u32 epnum, u32 type) u32 val; val = ioread32(fotg210->reg + FOTG210_FIFOCF); + val &= ~FIFOCF_TYPE(3, epnum - 1); val |= FIFOCF_TYPE(type, epnum - 1); iowrite32(val, fotg210->reg + FOTG210_FIFOCF); } @@ -147,6 +149,7 @@ static void fotg210_set_mps(struct fotg210_ep *ep, u32 epnum, u32 mps, FOTG210_OUTEPMPSR(epnum); val = ioread32(fotg210->reg + offset); + val &= ~INOUTEPMPSR_MPS(~0); val |= INOUTEPMPSR_MPS(mps); iowrite32(val, fotg210->reg + offset); } @@ -209,12 +212,13 @@ static int fotg210_ep_release(struct fotg210_ep *ep) { if (!ep->epnum) return 0; + + fotg210_reset_tseq(ep->fotg210, ep->epnum); + ep->epnum = 0; ep->stall = 0; ep->wedged = 0; - fotg210_reset_tseq(ep->fotg210, ep->epnum); - return 0; } @@ -338,6 +342,7 @@ static void fotg210_start_dma(struct fotg210_ep *ep, struct fotg210_request *req) { struct device *dev = &ep->fotg210->gadget.dev; + enum dma_data_direction direction; dma_addr_t d; u8 *buffer; u32 length; @@ -361,8 +366,8 @@ static void fotg210_start_dma(struct fotg210_ep *ep, length = req->req.length - req->req.actual; } - d = dma_map_single(dev, buffer, length, - ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + direction = ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + d = dma_map_single(dev, buffer, length, direction); if (dma_mapping_error(dev, d)) { pr_err("dma_mapping_error\n"); @@ -379,7 +384,7 @@ static void fotg210_start_dma(struct fotg210_ep *ep, /* update actual transfer length */ req->req.actual += length; - dma_unmap_single(dev, d, length, DMA_TO_DEVICE); + dma_unmap_single(dev, d, length, direction); } static void fotg210_ep0_queue(struct fotg210_ep *ep, @@ -445,7 +450,7 @@ static int fotg210_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) req = container_of(_req, struct fotg210_request, req); spin_lock_irqsave(&ep->fotg210->lock, flags); - if (!list_empty(&ep->queue)) + if (!list_empty(&req->queue)) fotg210_done(ep, req, -ECONNRESET); spin_unlock_irqrestore(&ep->fotg210->lock, flags); @@ -886,6 +891,8 @@ static irqreturn_t fotg210_irq(int irq, void *_fotg210) u32 int_msk = ioread32(fotg210->reg + FOTG210_DMIGR); int_grp &= ~int_msk; + if (!int_grp) + return IRQ_NONE; spin_lock(&fotg210->lock); @@ -1002,6 +1009,14 @@ static void fotg210_disable_unplug(struct fotg210_udc *fotg210) iowrite32(reg, fotg210->reg + FOTG210_PHYTMSR); } +static void fotg210_enable_unplug(struct fotg210_udc *fotg210) +{ + u32 reg = ioread32(fotg210->reg + FOTG210_PHYTMSR); + + reg |= PHYTMSR_UNPLUG; + iowrite32(reg, fotg210->reg + FOTG210_PHYTMSR); +} + static int fotg210_udc_start(struct usb_gadget *g, struct usb_gadget_driver *driver) { @@ -1009,20 +1024,23 @@ static int fotg210_udc_start(struct usb_gadget *g, u32 value; int ret; - /* hook up the driver */ + /* Hook up the driver before enabling device interrupts. */ fotg210->driver = driver; fotg210->gadget.dev.of_node = fotg210->dev->of_node; fotg210->gadget.speed = USB_SPEED_UNKNOWN; - dev_info(fotg210->dev, "bound driver %s\n", driver->driver.name); - if (!IS_ERR_OR_NULL(fotg210->phy)) { ret = otg_set_peripheral(fotg210->phy->otg, &fotg210->gadget); - if (ret) - dev_err(fotg210->dev, "can't bind to phy\n"); + if (ret) { + fotg210->driver = NULL; + return dev_err_probe(fotg210->dev, ret, + "can't bind to PHY\n"); + } } + dev_info(fotg210->dev, "bound driver %s\n", driver->driver.name); + /* chip enable */ value = ioread32(fotg210->reg + FOTG210_DMCR); value |= DMCR_CHIP_EN; @@ -1076,20 +1094,23 @@ static void fotg210_init(struct fotg210_udc *fotg210) static int fotg210_udc_stop(struct usb_gadget *g) { struct fotg210_udc *fotg210 = gadget_to_fotg210(g); - unsigned long flags; + unsigned long flags; + int ret = 0; if (!IS_ERR_OR_NULL(fotg210->phy)) - return otg_set_peripheral(fotg210->phy->otg, NULL); + ret = otg_set_peripheral(fotg210->phy->otg, NULL); + + /* fotg210_init() sleeps, so it must run outside the spinlock. */ + fotg210_init(fotg210); spin_lock_irqsave(&fotg210->lock, flags); - fotg210_init(fotg210); fotg210->driver = NULL; fotg210->gadget.speed = USB_SPEED_UNKNOWN; spin_unlock_irqrestore(&fotg210->lock, flags); - return 0; + return ret; } /** @@ -1103,8 +1124,12 @@ static int fotg210_vbus_session(struct usb_gadget *g, int is_active) { struct fotg210_udc *fotg210 = gadget_to_fotg210(g); - /* Call down to core integration layer to drive or disable VBUS */ - fotg210_vbus(fotg210->fotg, is_active); + /* A peripheral must never source VBUS; only control its pull-up. */ + if (is_active) + fotg210_disable_unplug(fotg210); + else + fotg210_enable_unplug(fotg210); + return 0; } @@ -1144,28 +1169,24 @@ static int fotg210_phy_event(struct notifier_block *nb, unsigned long action, } } -static struct notifier_block fotg210_phy_notifier = { - .notifier_call = fotg210_phy_event, -}; - int fotg210_udc_remove(struct platform_device *pdev) { struct fotg210_udc *fotg210 = platform_get_drvdata(pdev); int i; usb_del_gadget_udc(&fotg210->gadget); - if (!IS_ERR_OR_NULL(fotg210->phy)) { - usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier); - usb_put_phy(fotg210->phy); - } - iounmap(fotg210->reg); + if (!IS_ERR_OR_NULL(fotg210->phy)) + usb_unregister_notifier(fotg210->phy, + &fotg210->phy_notifier); free_irq(platform_get_irq(pdev, 0), fotg210); + usb_phy_shutdown(fotg210->phy); fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); for (i = 0; i < FOTG210_MAX_NUM_EP; i++) kfree(fotg210->ep[i]); kfree(fotg210); + platform_set_drvdata(pdev, NULL); return 0; } @@ -1188,7 +1209,6 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) return -ENOMEM; fotg210->dev = dev; - fotg210->fotg = fotg; fotg210->phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); if (IS_ERR(fotg210->phy)) { @@ -1216,8 +1236,6 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) spin_lock_init(&fotg210->lock); - platform_set_drvdata(pdev, fotg210); - fotg210->gadget.ops = &fotg210_gadget_ops; fotg210->gadget.max_speed = USB_SPEED_HIGH; @@ -1267,38 +1285,45 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) fotg210_disable_unplug(fotg210); - ret = request_irq(irq, fotg210_irq, IRQF_SHARED, - udc_name, fotg210); + ret = request_irq(irq, fotg210_irq, 0, udc_name, fotg210); if (ret < 0) { dev_err_probe(dev, ret, "request_irq error\n"); goto err_req; } - if (!IS_ERR_OR_NULL(fotg210->phy)) - usb_register_notifier(fotg210->phy, &fotg210_phy_notifier); + if (!IS_ERR_OR_NULL(fotg210->phy)) { + fotg210->phy_notifier.notifier_call = fotg210_phy_event; + ret = usb_register_notifier(fotg210->phy, + &fotg210->phy_notifier); + if (ret) + goto err_notifier; + } ret = usb_add_gadget_udc(dev, &fotg210->gadget); if (ret) goto err_add_udc; + platform_set_drvdata(pdev, fotg210); + dev_info(dev, "version %s\n", DRIVER_VERSION); return 0; err_add_udc: if (!IS_ERR_OR_NULL(fotg210->phy)) - usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier); + usb_unregister_notifier(fotg210->phy, + &fotg210->phy_notifier); +err_notifier: free_irq(irq, fotg210); err_req: fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); err_map: - iounmap(fotg210->reg); - err_alloc: for (i = 0; i < FOTG210_MAX_NUM_EP; i++) kfree(fotg210->ep[i]); + usb_phy_shutdown(fotg210->phy); err_free: kfree(fotg210); diff --git a/drivers/usb/fotg210/fotg210-udc.h b/drivers/usb/fotg210/fotg210-udc.h index 252cb2b8e2fe..11fe67802537 100644 --- a/drivers/usb/fotg210/fotg210-udc.h +++ b/drivers/usb/fotg210/fotg210-udc.h @@ -237,8 +237,8 @@ struct fotg210_udc { unsigned long irq_trigger; struct device *dev; - struct fotg210 *fotg; struct usb_phy *phy; + struct notifier_block phy_notifier; struct usb_gadget gadget; struct usb_gadget_driver *driver; From 4b30c89fb36924983be652bb24fc49e20390bf27 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 13 Aug 2026 23:33:53 +0800 Subject: [PATCH 074/158] usb: musb: da8xx: depopulate OF children on remove da8xx_probe() creates CPPI platform devices with of_platform_populate(), but da8xx_remove() unregisters only the separately created MUSB device and the generic PHY. The OF children therefore remain registered after the glue driver is unbound. Pair the successful population with of_platform_depopulate() during remove. Unregister the MUSB device first because it reuses the parent DT node but was not created by of_platform_populate(). Fixes: d6299b6efbf6 ("usb: musb: Add support of CPPI 4.1 DMA controller to DA8xx") Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260813153353.42817-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/da8xx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index 4209f438ba18..7cefac2e5ef8 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -592,6 +592,7 @@ static void da8xx_remove(struct platform_device *pdev) struct da8xx_glue *glue = platform_get_drvdata(pdev); platform_device_unregister(glue->musb); + of_platform_depopulate(&pdev->dev); usb_phy_generic_unregister(glue->usb_phy); } From 8d70a378e702141424e27e0b8341e421f058635d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 13 Aug 2026 23:32:30 +0800 Subject: [PATCH 075/158] usb: musb: da8xx: depopulate OF children on probe failure da8xx_probe() creates the CPPI child devices before registering the MUSB platform device. If population partially succeeds or the later MUSB registration fails, the probe path unregisters the generic PHY but leaves the OF children registered under a parent whose probe failed. Depopulate the OF children before unregistering the PHY on both failure paths. Use one cleanup label so each resource is unwound in reverse publication order without introducing an unused label. Fixes: d6299b6efbf6 ("usb: musb: Add support of CPPI 4.1 DMA controller to DA8xx") Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260813153230.40421-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/da8xx.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index 7cefac2e5ef8..6c859a1a11d0 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -562,7 +562,7 @@ static int da8xx_probe(struct platform_device *pdev) ret = of_platform_populate(pdev->dev.of_node, NULL, da8xx_auxdata_lookup, &pdev->dev); if (ret) - goto err_unregister_phy; + goto err_depopulate; pinfo = da8xx_dev_info; pinfo.parent = &pdev->dev; @@ -577,12 +577,13 @@ static int da8xx_probe(struct platform_device *pdev) ret = PTR_ERR_OR_ZERO(glue->musb); if (ret) { dev_err(&pdev->dev, "failed to register musb device: %d\n", ret); - goto err_unregister_phy; + goto err_depopulate; } return 0; -err_unregister_phy: +err_depopulate: + of_platform_depopulate(&pdev->dev); usb_phy_generic_unregister(glue->usb_phy); return ret; } From 604feda9ede7678d113534750883db9d09d19826 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:46:52 +0530 Subject: [PATCH 076/158] usb: chipidea: fix typo "binded" in comments Correct "binded" to "bound", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Acked-by: Peter Chen Link: https://patch.msgid.link/20260907061652.38637-1-hemanth.selam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/otg_fsm.c | 2 +- drivers/usb/chipidea/udc.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 929536dc96ec..642729893cc2 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -705,7 +705,7 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) if ((intr_sts & USBi_PCI) && !port_conn) { fsm->b_conn = 0; - /* if gadget driver is binded */ + /* if gadget driver is bound */ if (ci->driver) { /* A device to be peripheral mode */ ci->gadget.is_a_peripheral = 1; diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index d52f89489893..c29b2d0dcc7c 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1862,7 +1862,7 @@ static int ci_udc_get_frame(struct usb_gadget *_gadget) } /* - * ci_hdrc_gadget_connect: caller makes sure gadget driver is binded + * ci_hdrc_gadget_connect: caller makes sure gadget driver is bound */ static void ci_hdrc_gadget_connect(struct usb_gadget *_gadget, int is_active) { From a65295ddc2a10820fd855212be8a2271deff9657 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:53:50 +0200 Subject: [PATCH 077/158] USB: gadget: stop propagating controller OF nodes Some UDC drivers propagate the controller OF node to the gadget device but there are no (and has never been any) users of this as the devicetree binding and composite driver support was never merged. [1] Drop the unused and incomplete support for OF node propagation which if ever needed should be handled by UDC core. Note that this also avoids probe failures when reusing an OF node that describe resources like pinctrl that are managed by driver core (cf. commit 48ed32482c41 ("usb: gadget: aspeed: Fix probe regression")). Link: https://lore.kernel.org/all/1340720833-781-1-git-send-email-aletes.xgr@gmail.com/ [1] Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702145350.109509-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed-vhub/dev.c | 2 -- drivers/usb/gadget/udc/aspeed_udc.c | 1 - drivers/usb/gadget/udc/at91_udc.c | 1 - drivers/usb/gadget/udc/bcm63xx_udc.c | 1 - drivers/usb/gadget/udc/fsl_udc_core.c | 3 --- drivers/usb/gadget/udc/lpc32xx_udc.c | 1 - 6 files changed, 9 deletions(-) diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index 8b9449d16324..0dccdbfd6aef 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -592,8 +592,6 @@ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) else d->gadget.max_speed = USB_SPEED_HIGH; d->gadget.speed = USB_SPEED_UNKNOWN; - d->gadget.dev.of_node = vhub->pdev->dev.of_node; - dev_set_of_node_reused(&d->gadget.dev); rc = usb_add_gadget_udc(d->port_dev, &d->gadget); if (rc != 0) diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 08b24902c7b9..81fa495bbacd 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -1299,7 +1299,6 @@ static int ast_udc_start(struct usb_gadget *gadget, UDC_DBG(udc, "\n"); udc->driver = driver; - udc->gadget.dev.of_node = udc->pdev->dev.of_node; for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { ep = &udc->ep[i]; diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index 8e5d7fb71549..0fccca187803 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -1558,7 +1558,6 @@ static int at91_start(struct usb_gadget *gadget, udc = container_of(gadget, struct at91_udc, gadget); udc->driver = driver; - udc->gadget.dev.of_node = udc->pdev->dev.of_node; udc->enabled = 1; udc->gadget.is_selfpowered = 1; diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index c4f9ea45bdbb..ba91b1ebbf57 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -1830,7 +1830,6 @@ static int bcm63xx_udc_start(struct usb_gadget *gadget, bcm63xx_select_phy_mode(udc, true); udc->driver = driver; - udc->gadget.dev.of_node = udc->dev->of_node; spin_unlock_irqrestore(&udc->lock, flags); diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 20392409afb1..878d70b55530 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2465,9 +2465,6 @@ static int fsl_udc_probe(struct platform_device *pdev) udc_controller->gadget.speed = USB_SPEED_UNKNOWN; udc_controller->gadget.name = driver_name; - /* Setup gadget.dev and register with kernel */ - udc_controller->gadget.dev.of_node = pdev->dev.of_node; - if (!IS_ERR_OR_NULL(udc_controller->transceiver)) udc_controller->gadget.is_otg = 1; diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 044c31869cfb..35cf44025559 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -2889,7 +2889,6 @@ static int lpc32xx_start(struct usb_gadget *gadget, } udc->driver = driver; - udc->gadget.dev.of_node = udc->dev->of_node; udc->enabled = 1; udc->gadget.is_selfpowered = 1; udc->vbus = 0; From d32f8ff69d5fc33aa1979cfd85c3bede4305fecd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 10 Aug 2026 16:31:11 +0700 Subject: [PATCH 078/158] usb: chipidea: ci_hdrc_imx: Propagate errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no IRQ is available, while other errors should be propagated. Propagate errors such as -EPROBE_DEFER and -EINVAL instead of continuing probe without the IRQ. Acked-by: Peter Chen Signed-off-by: bui duc phuc Reviewed-by: Frank Li Link: https://patch.msgid.link/20260810093112.47807-1-phucduc.bui@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 282314eea7fc..6a17b22047b3 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -525,6 +525,10 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) data->supports_runtime_pm = true; data->wakeup_irq = platform_get_irq_optional(pdev, 1); + if (data->wakeup_irq < 0 && data->wakeup_irq != -ENXIO) { + ret = data->wakeup_irq; + goto phy_shutdown; + } if (data->wakeup_irq > 0) { irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name); if (!irq_name) { From f94d9e6a38d581a826ac4ae7b1865b6918d6cd27 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Mon, 17 Aug 2026 01:00:05 -0700 Subject: [PATCH 079/158] usb: dwc2: handle OTG HNP SetFeature requests Without handling for b_hnp_enable, a_hnp_support, and a_alt_hnp_support, HNP cannot be enabled when two OTG controllers are connected. Handle SetFeature for these OTG selectors, gated on otg_caps.hnp_support. The OTG specification only defines SetFeature for them, so reject ClearFeature with -EINVAL instead of silently accepting it. Use dev_dbg for status updates to avoid noisy logs. Advertise the core's HNP/SRP capability during gadget init by restoring GUSBCFG_HNPCAP/SRPCAP from otg_caps, mirroring dwc2_gusbcfg_init() on the host side, instead of clearing them unconditionally. Clear the HNP flags and GOTGCTL_DEVHNPEN on disconnect/reset so OTG state does not leak across sessions. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Link: https://patch.msgid.link/e1f4718cadf28c4348bcf6ebb025f71732416538.1786950594.git.tze.yee.ng@altera.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 51 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f7afbedf9f2c..43947673696d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1780,6 +1780,7 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, struct dwc2_hsotg_ep *ep; int ret; bool halted; + u32 otgctl; u32 recip; u32 wValue; u32 wIndex; @@ -1809,6 +1810,36 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, hsotg->test_mode = wIndex >> 8; break; + case USB_DEVICE_B_HNP_ENABLE: + if (!hsotg->params.otg_caps.hnp_support) + return -ENOENT; + if (!set) + return -EINVAL; + + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl |= GOTGCTL_DEVHNPEN; + dwc2_writel(hsotg, otgctl, GOTGCTL); + hsotg->gadget.b_hnp_enable = 1; + dev_dbg(hsotg->dev, "HNP enabled\n"); + break; + case USB_DEVICE_A_HNP_SUPPORT: + if (!hsotg->params.otg_caps.hnp_support) + return -ENOENT; + if (!set) + return -EINVAL; + + hsotg->gadget.a_hnp_support = 1; + dev_dbg(hsotg->dev, "a_hnp_support set\n"); + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + if (!hsotg->params.otg_caps.hnp_support) + return -ENOENT; + if (!set) + return -EINVAL; + + hsotg->gadget.a_alt_hnp_support = 1; + dev_dbg(hsotg->dev, "a_alt_hnp_support set\n"); + break; default: return -ENOENT; } @@ -3322,6 +3353,13 @@ void dwc2_hsotg_disconnect(struct dwc2_hsotg *hsotg) hsotg->connected = 0; hsotg->test_mode = 0; + if (hsotg->params.otg_caps.hnp_support) { + hsotg->gadget.b_hnp_enable = 0; + hsotg->gadget.a_hnp_support = 0; + hsotg->gadget.a_alt_hnp_support = 0; + dwc2_clear_bit(hsotg, GOTGCTL, GOTGCTL_DEVHNPEN); + } + /* all endpoints should be shutdown */ for (ep = 0; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) @@ -3416,9 +3454,18 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, usbcfg &= ~GUSBCFG_TOUTCAL_MASK; usbcfg |= GUSBCFG_TOUTCAL(7); - /* remove the HNP/SRP and set the PHY */ + /* + * Configure HNP/SRP capability from params (same idea as + * dwc2_gusbcfg_init() for host). Unconditionally clearing these + * bits leaves an HNP-capable OTG gadget unable to negotiate. + */ usbcfg &= ~(GUSBCFG_SRPCAP | GUSBCFG_HNPCAP); - dwc2_writel(hsotg, usbcfg, GUSBCFG); + if (hsotg->params.otg_caps.hnp_support && + hsotg->params.otg_caps.srp_support) + usbcfg |= GUSBCFG_HNPCAP; + if (hsotg->params.otg_caps.srp_support) + usbcfg |= GUSBCFG_SRPCAP; + dwc2_writel(hsotg, usbcfg, GUSBCFG); dwc2_phy_init(hsotg, true); From 44969e9a46ff678df0215ba0caab5590675ff8ea Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Wed, 26 Aug 2026 21:34:01 +0800 Subject: [PATCH 080/158] usb: dwc2: truncate PIO RX FIFO reads to the request's remaining space dwc2_hsotg_rx_data() reads the OUT packet length from the RX FIFO status (GRXSTS.BYTECNT, host-controlled) and, when it exceeds the remaining space of the active gadget request (max_req = req.length - req.actual), merely fires WARN_ON_ONCE(1) and then stores the whole packet into hs_req->req.buf + req->req.actual via dwc2_readl_rep(): req.actual += size; dwc2_readl_rep(hsotg, EPFIFO(ep_idx), req.buf + actual, DIV_ROUND_UP(size, 4)); A packet larger than the request buffer therefore over-writes up to maxpacket bytes past it. The rounded-up word read additionally emits up to 3 bytes past the logical end even when the size does not exceed the remaining space (short-request boundary). Attack chain (USB peripheral/gadget mode, PIO only; the attacker is the USB host): malicious host -> OUT packet on epN -> RX FIFO interrupt -> dwc2_hsotg_handle_rx() (GRXSTS_PKTSTS_OUTRX / SETUPRX) -> dwc2_hsotg_rx_data(hsotg, epnum, BYTECNT=64) with the queued request having req.length=8, req.actual=7 (1 byte left) -> "to_read(64) > max_req(1)" -> WARN_ON_ONCE(1) only -> dwc2_readl_rep() writes 16 words at req.buf+7 -> 63 bytes past the 8-byte request buffer Reproduced (kernel 7.2.0+, KASAN/SLUB debug): calling the real dwc2_hsotg_rx_data() with the above state (request buffer 8 bytes, actual 7, size 64) triggers the WARN and leaves req.actual = 71 (the 64 bytes are accrued regardless), and the SLUB redzone immediately after the 8-byte object plus neighbouring slab objects are overwritten with FIFO content (byte 7 changes from a marker to FIFO data) - the out-of-bounds write is visible byte-for-byte. The FIFO read is done by raw 32-bit I/O words, which generic KASAN does not instrument, so the redzone/neighbour clobber is the forensic evidence. Signed-off-by: Haofeng Li Assisted-by: opencode:deepseek-v4-flash-free Link: https://patch.msgid.link/20260826133401.3796639-1-lihaofeng@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 43947673696d..b3976876c06b 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2298,6 +2298,7 @@ static void dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) int to_read; int max_req; int read_ptr; + u32 drain; if (!hs_req) { u32 epctl = dwc2_readl(hsotg, DOEPCTL(ep_idx)); @@ -2317,30 +2318,46 @@ static void dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) to_read = size; read_ptr = hs_req->req.actual; max_req = hs_req->req.length - read_ptr; + drain = 0; dev_dbg(hsotg->dev, "%s: read %d/%d, done %d/%d\n", __func__, to_read, max_req, read_ptr, hs_req->req.length); if (to_read > max_req) { /* - * more data appeared than we where willing - * to deal with in this request. + * More data appeared than we were willing to deal with in + * this request. Keep only what fits in the request buffer + * and discard the rest from the FIFO, instead of overwriting + * bytes past the request buffer (a 64-byte packet into a + * request with one byte left used to over-write 63 bytes past + * its end). */ - - /* currently we don't deal this */ - WARN_ON_ONCE(1); + dev_dbg(hsotg->dev, "%s: packet %d > request space %d, dropping %d\n", + __func__, to_read, max_req, to_read - max_req); + drain = DIV_ROUND_UP(to_read - max_req, 4); + to_read = max_req; } hs_ep->total_data += to_read; hs_req->req.actual += to_read; - to_read = DIV_ROUND_UP(to_read, 4); /* - * note, we might over-write the buffer end by 3 bytes depending on - * alignment of the data. + * Copy word-at-a-time so the request buffer is exactly filled and + * never over-run by the 4-byte rounding of the previous FIFO bulk + * read (which could also emit up to 3 bytes past the buffer end). */ - dwc2_readl_rep(hsotg, EPFIFO(ep_idx), - hs_req->req.buf + read_ptr, to_read); + while (to_read > 0) { + u32 word = dwc2_readl(hsotg, EPFIFO(ep_idx)); + unsigned int chunk = min_t(unsigned int, to_read, 4); + + memcpy(hs_req->req.buf + read_ptr, &word, chunk); + read_ptr += chunk; + to_read -= chunk; + } + + /* drain the discarded bytes so the next FIFO event is a fresh packet */ + while (drain-- > 0) + (void)dwc2_readl(hsotg, EPFIFO(ep_idx)); } /** From 5f56bc090804559f56e4a1fd950ae2a24cf36518 Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Wed, 9 Sep 2026 10:10:14 +0800 Subject: [PATCH 081/158] usb: dwc2: debugfs: fix memory leak of hsotg->regset hsotg->regset is allocated in dwc2_debugfs_init() using devm_kzalloc(), which ties its lifetime to the device (struct dwc2_hsotg) rather than to the debugfs entries it serves. dwc2_debugfs_exit() removes the debugfs directory but leaves hsotg->regset allocated until the device itself is removed, so the pointer dangles for the remainder of the device lifetime. Switch to kzalloc() and free it explicitly in dwc2_debugfs_exit() so the regset lifetime matches the debugfs lifetime. Set the pointer to NULL after freeing to avoid a stale dangling pointer. Reported-by: kakapapa2 Closes: https://bugzilla.kernel.org/show_bug.cgi?id=219977 Reviewed-by: Thinh Nguyen Signed-off-by: Huang Wei Link: https://patch.msgid.link/20260909021014.906548-1-huangwei@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/debugfs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c index 3116ac72747f..2ecbf6523aaa 100644 --- a/drivers/usb/dwc2/debugfs.c +++ b/drivers/usb/dwc2/debugfs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "core.h" @@ -787,8 +788,7 @@ int dwc2_debugfs_init(struct dwc2_hsotg *hsotg) /* Add gadget debugfs nodes */ dwc2_hsotg_create_debug(hsotg); - hsotg->regset = devm_kzalloc(hsotg->dev, sizeof(*hsotg->regset), - GFP_KERNEL); + hsotg->regset = kzalloc_obj(*hsotg->regset, GFP_KERNEL); if (!hsotg->regset) { ret = -ENOMEM; goto err; @@ -810,4 +810,6 @@ void dwc2_debugfs_exit(struct dwc2_hsotg *hsotg) { debugfs_remove_recursive(hsotg->debug_root); hsotg->debug_root = NULL; + kfree(hsotg->regset); + hsotg->regset = NULL; } From 048884b782b137229399b9f727b6437d9b4f1071 Mon Sep 17 00:00:00 2001 From: Keisuke Tsukuda Date: Tue, 8 Sep 2026 20:33:53 +0000 Subject: [PATCH 082/158] usb: gadget: composite: avoid warning on aborted delayed status A SET_CONFIGURATION request that disables the current configuration, or a disconnect, can abort a control transfer whose status stage was delayed by a function driver. The asynchronous function work can nevertheless finish later and call usb_composite_setup_continue() after reset_config() has cleared delayed_status. This is an expected consequence of aborting the old control transfer, and does not require a kernel warning. Keep the diagnostic available under dynamic debug and ignore the obsolete completion as before. Fixes: 2bac51a1827a ("usb: gadget: composite: reset delayed_status on reset_config") Reported-by: syzbot+a83a5a1ba9ff57935171@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a83a5a1ba9ff57935171 Signed-off-by: Keisuke Tsukuda Link: https://patch.msgid.link/20260908203348.95825-2-tkdkei@outlook.jp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index df39e3487c1f..1b2238147cfe 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -2740,7 +2740,7 @@ void usb_composite_setup_continue(struct usb_composite_dev *cdev) spin_lock_irqsave(&cdev->lock, flags); if (cdev->delayed_status == 0) { - WARN(cdev, "%s: Unexpected call\n", __func__); + DBG(cdev, "%s: Ignoring obsolete call\n", __func__); } else if (--cdev->delayed_status == 0) { DBG(cdev, "%s: Completing delayed status\n", __func__); From e84caed6e7f58d96ea2e4aaa0b947ab05faa451b Mon Sep 17 00:00:00 2001 From: Keisuke Tsukuda Date: Tue, 8 Sep 2026 20:33:54 +0000 Subject: [PATCH 083/158] usb: gadget: f_mass_storage: ignore class requests while inactive SET_CONFIGURATION is handled asynchronously by the mass-storage thread. A class request can arrive after the composite core selects the function but before the thread installs common->fsg, or while the function is being disabled. fsg_setup() already rejects this state with -EOPNOTSUPP, but checking it through fsg_is_set() also emits a warning. Check the active function directly so normal requests during a configuration transition are rejected without hiding fsg_is_set() warnings from other internal paths. Fixes: 8876f5e7d3b2 ("USB: gadget: f_mass_storage: added eject callback") Reported-by: syzbot+d31109047baed8f3b590@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d31109047baed8f3b590 Signed-off-by: Keisuke Tsukuda Acked-by: Alan Stern Link: https://patch.msgid.link/20260908203348.95825-3-tkdkei@outlook.jp Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index fc4818fb2a8b..42fd2d6ffd88 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -463,7 +463,12 @@ static int fsg_setup(struct usb_function *f, u16 w_value = le16_to_cpu(ctrl->wValue); u16 w_length = le16_to_cpu(ctrl->wLength); - if (!fsg_is_set(fsg->common)) + /* + * fsg_is_set() warns when common->fsg is NULL, but that can happen + * normally while a configuration change installs or removes the active + * function. + */ + if (!fsg->common->fsg) return -EOPNOTSUPP; ++fsg->common->ep0_req_tag; /* Record arrival of a new request */ From 6a3ee813d5fc41c4d0e7d19689b885c7bdd6f3be Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Thu, 10 Sep 2026 00:19:26 +0700 Subject: [PATCH 084/158] usb: storage: ene_ub6250: set transport_reset to avoid NULL deref ene_ub6250_probe() sets us->transport but never sets us->transport_reset. get_transport() only fills in transport_reset for the standard USB_PR_CB/CBI/BULK protocols; since this driver matches on VID/PID alone, a device can report any bInterfaceProtocol value and still bind, leaving transport_reset NULL. When the transport then reports an error, usb_stor_invoke_transport() calls us->transport_reset(us) in its Handle_Errors path, dereferencing a NULL function pointer: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:0x0 Call Trace: usb_stor_invoke_transport+0x55a/0x1a40 drivers/usb/storage/transport.c:926 usb_stor_control_thread+0x44c/0x8f0 drivers/usb/storage/usb.c:462 kthread+0x38b/0x480 kernel/kthread.c:436 Set transport_reset to usb_stor_Bulk_reset: ene_send_scsi_cmd() already frames its command/status packets with the shared struct bulk_cb_wrap/bulk_cs_wrap from include/linux/usb/storage.h and only ever transfers over us->send_bulk_pipe/recv_bulk_pipe, i.e. this device speaks the Bulk-Only Transport wire protocol underneath its own SCSI translation layer, so the Bulk-Only reset is the correct recovery action for it, matching the pattern already used by the other subdrivers with a custom transport function (alauda, datafab, jumpshot, karma). Also harden usb_stor_probe2()'s existing sanity check to require transport_reset alongside transport and proto_handler, so a future subdriver making the same mistake fails to probe instead of crashing on the first error recovery. Reported-by: syzbot+356ae236154297f0d60d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=356ae236154297f0d60d Signed-off-by: Nguyen Ngoc Thang Acked-by: Alan Stern Link: https://patch.msgid.link/20260909171926.54111-1-ngocthang2710.1999@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 2 ++ drivers/usb/storage/usb.c | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 895f90c7a3fa..a9358e04ab4b 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -2350,6 +2350,8 @@ static int ene_ub6250_probe(struct usb_interface *intf, us->transport_name = "ene_ub6250"; us->transport = ene_transport; + /* get_transport() only sets this for the standard USB_PR_* protocols */ + us->transport_reset = usb_stor_Bulk_reset; us->max_lun = 0; result = usb_stor_probe2(us); diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 064c7fc8e368..823a4b2e9033 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -1100,8 +1100,10 @@ int usb_stor_probe2(struct us_data *us) int result; struct device *dev = &us->pusb_intf->dev; - /* Make sure the transport and protocol have both been set */ - if (!us->transport || !us->proto_handler) { + /* Make sure the transport, its reset method, and the protocol + * have all been set + */ + if (!us->transport || !us->transport_reset || !us->proto_handler) { result = -ENXIO; goto BadDevice; } From 052024d03b263b7e649021f3e02098928aea249c Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 6 Sep 2026 14:24:00 +0200 Subject: [PATCH 085/158] usb: typec: tipd: Disable mode control for CD321x Apple's CD321X firmware controls which alternate mode is entered and cannot be influenced from Linux. Set the no_mode_control capability to accurately reflect this to the typec subsystem. Signed-off-by: Sven Peter Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260906-b4-tipd-mode-ctrl-v1-1-71032eaaa452@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index f76f563dc42b..9248f0dd3f49 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -150,6 +150,7 @@ struct tipd_data { irq_handler_t irq_handler; u64 irq_mask1; size_t tps_struct_size; + bool no_mode_control; void (*remove)(struct tps6598x *tps); int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); void (*unregister_port)(struct tps6598x *tps); @@ -1251,6 +1252,7 @@ tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) typec_cap.driver_data = tps; typec_cap.ops = &tps6598x_ops; typec_cap.fwnode = fwnode; + typec_cap.no_mode_control = tps->data->no_mode_control; switch (TPS_SYSCONF_PORTINFO(conf)) { case TPS_PORTINFO_SINK_ACCESSORY: @@ -2024,6 +2026,7 @@ static const struct tipd_data cd321x_data = { APPLE_CD_REG_INT_DATA_STATUS_UPDATE | APPLE_CD_REG_INT_PLUG_EVENT, .tps_struct_size = sizeof(struct cd321x), + .no_mode_control = true, .remove = cd321x_remove, .register_port = cd321x_register_port, .unregister_port = cd321x_unregister_port, From bbed88f72dbbb960080282de3b90ca0933fa6226 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:25:53 +0530 Subject: [PATCH 086/158] usb: dwc2: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904105555.37215-1-hemanth.selam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/core.c | 2 +- drivers/usb/dwc2/core.h | 2 +- drivers/usb/dwc2/gadget.c | 6 +++--- drivers/usb/dwc2/hcd.c | 4 ++-- drivers/usb/dwc2/hcd_ddma.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index f375c5185bfe..6a0135e06b00 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -173,7 +173,7 @@ static void dwc2_restore_essential_regs(struct dwc2_hsotg *hsotg, int rmode, /* Umnask global Interrupt in GAHBCFG and restore it */ dwc2_writel(hsotg, gr->gahbcfg | GAHBCFG_GLBL_INTR_EN, GAHBCFG); - /* Clear all pending interupts */ + /* Clear all pending interrupts */ dwc2_writel(hsotg, 0xffffffff, GINTSTS); /* Unmask restore done interrupt */ diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 767251aa1aa3..015a5fb7842d 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -334,7 +334,7 @@ enum dwc2_ep0_state { * (default when phy_type is Full Speed) * @oc_disable: Flag to disable overcurrent condition. * 0 - Allow overcurrent condition to get detected - * 1 - Disable overcurrent condtion to get detected + * 1 - Disable overcurrent condition to get detected * @ts_dline: Enable Term Select Dline pulsing * 0 - No (default) * 1 - Yes diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b3976876c06b..6c8a9f05fff0 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -658,7 +658,7 @@ static int dwc2_hsotg_write_fifo(struct dwc2_hsotg *hsotg, } /** - * get_ep_limit - get the maximum data legnth for this endpoint + * get_ep_limit - get the maximum data length for this endpoint * @hs_ep: The endpoint * * Return the maximum data that can be queued in one go on a given endpoint @@ -5563,7 +5563,7 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, dwc2_hib_restore_common(hsotg, rem_wakeup, 0); if (!reset) { - /* Clear all pending interupts */ + /* Clear all pending interrupts */ dwc2_writel(hsotg, 0xffffffff, GINTSTS); } @@ -5612,7 +5612,7 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, } /* Wait for interrupts which must be cleared */ mdelay(2); - /* Clear all pending interupts */ + /* Clear all pending interrupts */ dwc2_writel(hsotg, 0xffffffff, GINTSTS); /* Restore global registers */ diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 2414291aa908..32ce38c7096a 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -5656,7 +5656,7 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, */ mdelay(100); - /* Clear all pending interupts */ + /* Clear all pending interrupts */ dwc2_writel(hsotg, 0xffffffff, GINTSTS); /* De-assert Restore */ @@ -5726,7 +5726,7 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, hprt0 = dwc2_readl(hsotg, HPRT0); - /* Clear all pending interupts */ + /* Clear all pending interrupts */ dwc2_writel(hsotg, 0xffffffff, GINTSTS); /* Restore global registers */ diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 994a78ad084b..703cda5a5895 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -963,7 +963,7 @@ static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, * whether some of the descriptors (frames) succeeded or not. * Pass error code to completion routine as well, to update * urb->status, some of class drivers might use it to stop - * queing transfer requests. + * queuing transfer requests. */ int err = halt_status == DWC2_HC_XFER_AHB_ERR ? -EIO : -EOVERFLOW; From ed2050bc671b44b2c948dbd4347cc534d44ccc9f Mon Sep 17 00:00:00 2001 From: Martin Rys Date: Sun, 30 Aug 2026 10:02:49 +0200 Subject: [PATCH 087/158] USB: Improve "New device found" printout message Use a colon instead of a comma, like the debug message after this one does. Do not print a space after `bcdDevice=`. This was introduced as the formatting was copied from `lsusb -v` printout, but lsusb output has preceding spaces, this message does not, so it ends up looking odd. Previously the output ended up looking like `idProduct=a000, bcdDevice= 1.00` if the first hex character of bcdDevice was 0. Signed-off-by: Martin Rys Link: https://patch.msgid.link/94a023c9-80de-4c84-85bf-7846de97c240@archlinux.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3345b3298daf..c382215a1177 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2411,7 +2411,7 @@ static void announce_device_ids(struct usb_device *udev) u16 bcdDevice = le16_to_cpu(udev->descriptor.bcdDevice); dev_info(&udev->dev, - "New USB device found, idVendor=%04x, idProduct=%04x, bcdDevice=%2x.%02x\n", + "New USB device found: idVendor=%04x, idProduct=%04x, bcdDevice=%x.%02x\n", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct), bcdDevice >> 8, bcdDevice & 0xff); From dbe68462fa656078eac3a5a09039038dc47e08da Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 11 Sep 2026 12:10:43 +0700 Subject: [PATCH 088/158] usb: chipidea: ci_hdrc_imx: Fix error handling when creating IRQ name devm_kasprintf() can fail when allocating the wakeup IRQ name. In this case, the probe error path should shut down the PHY before returning. Use the phy_shutdown error path instead of err_clk so that the PHY is properly shut down when creating the IRQ name or requesting the wakeup IRQ fails. Reviewed-by: Frank Li Acked-by: Peter Chen Reported-by: Sashiko Bot Link: https://lore.kernel.org/all/20260806065755.C9F361F000E9@smtp.kernel.org/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260911051043.15962-1-phucduc.bui@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 6a17b22047b3..edbf9df9b8f2 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -533,7 +533,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name); if (!irq_name) { ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); - goto err_clk; + goto phy_shutdown; } ret = devm_request_threaded_irq(dev, data->wakeup_irq, @@ -541,7 +541,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) IRQF_ONESHOT | IRQF_NO_AUTOEN, irq_name, data); if (ret) - goto err_clk; + goto phy_shutdown; } ret = imx_usbmisc_init(data->usbmisc_data); From 4d84caebab18c7c5bc84a3ed7cfef439449f7ae3 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 7 Sep 2026 16:42:19 +0300 Subject: [PATCH 089/158] thunderbolt: Write descriptors in tb_ring_poll() When the ring is not in interrupt mode, it depends on something enqueuing new frames to write the descriptors from ring->queue to the hardware, and that can take a long time. Since we know that we just released one slot, we can push the next frame directly from tb_ring_poll(). Reported-by: Alan Borzeszkowski Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 521ba8b57b28..836f5d0013b0 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -439,6 +439,13 @@ struct ring_frame *tb_ring_poll(struct tb_ring *ring) } ring->tail = (ring->tail + 1) % ring->size; + + /* + * There is one more slot available so we can push next + * descriptor to the ring. This is needed when the ring + * is created with %RING_FLAG_NO_INTERRUPT. + */ + ring_write_descriptors(ring, true); } unlock: From de9d89b4086129cd575640ed111b18d43c01a5d4 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 7 Sep 2026 16:40:45 +0300 Subject: [PATCH 090/158] thunderbolt: Add tb_ring_poll_pending() This helper can be used to figure out whether tb_ring_poll() has a frame to be returned. This can be used without the caller needing to take its own lock. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 19 +++++++++++++++++++ include/linux/thunderbolt.h | 1 + 2 files changed, 20 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 836f5d0013b0..6d2be7734a00 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -454,6 +454,25 @@ struct ring_frame *tb_ring_poll(struct tb_ring *ring) } EXPORT_SYMBOL_GPL(tb_ring_poll); +/** + * tb_ring_poll_pending() - Does the ring have completed frame + * @ring: Ring to check + * + * Can be used to check whether there is a completed frame in the ring + * that next call to tb_ring_poll() returns. + * + * Return: %true if a completed frame is waiting, %false otherwise. + */ +bool tb_ring_poll_pending(struct tb_ring *ring) +{ + guard(spinlock_irqsave)(&ring->lock); + + if (!ring->running || ring_empty(ring)) + return false; + return !!(ring->descriptors[ring->tail].flags & RING_DESC_COMPLETED); +} +EXPORT_SYMBOL_GPL(tb_ring_poll_pending); + static void __ring_interrupt_mask(struct tb_ring *ring, bool mask) { int interrupt_index = ring_interrupt_index(ring); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 335fa31ab1df..577bcf6e3810 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -754,6 +754,7 @@ static inline int tb_ring_tx_more(struct tb_ring *ring, struct ring_frame *frame /* Used only when the ring is in polling mode */ struct ring_frame *tb_ring_poll(struct tb_ring *ring); +bool tb_ring_poll_pending(struct tb_ring *ring); void tb_ring_poll_complete(struct tb_ring *ring); int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec); From 7049eb52c81645df81b1867e48bda89f11e585b8 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 7 Sep 2026 16:41:07 +0300 Subject: [PATCH 091/158] thunderbolt: stream: Do not hold the lock while busy polling When in busy poll mode with a reader and writer, and there is nothing completed on the rings both sides keep taking and releasing the lock from each other and this can cause stalls. Avoid this by calling the new API function tb_ring_poll_pending() that only returns true if there is anything available in which case they can take the lock and know there is now work to do. Reported-by: Alan Borzeszkowski Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index 56e155750466..a8b0be27b4a1 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -736,6 +736,22 @@ static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) return 0; } +/* Must not be called with @sdev->lock held */ +static int tbstream_dev_busy_poll_wait(struct tbstream_dev *sdev, + struct tbstream_ring *ring) +{ + for (;;) { + if (signal_pending(current)) + return -ERESTARTSYS; + if (tb_ring_poll_pending(ring->ring)) + return 0; + if (tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) + return 0; + cond_resched(); + } +} + static ssize_t tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) { @@ -779,9 +795,9 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) return -EAGAIN; if (sdev->busy_poll) { - if (signal_pending(current)) - return -ERESTARTSYS; - cond_resched(); + ret = tbstream_dev_busy_poll_wait(sdev, &sdev->rx_ring); + if (ret) + return ret; } else { ret = wait_event_interruptible(sdev->wait, READ_ONCE(sdev->rx_pending) || @@ -893,9 +909,9 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) return -EAGAIN; if (sdev->busy_poll) { - if (signal_pending(current)) - return -ERESTARTSYS; - cond_resched(); + ret = tbstream_dev_busy_poll_wait(sdev, &sdev->tx_ring); + if (ret) + return ret; } else { ret = wait_event_interruptible(sdev->wait, tbstream_ring_available(&sdev->tx_ring) || From 5da91c3694b8060b651aab5d95b2e4729c6e2b56 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 7 Sep 2026 16:41:18 +0300 Subject: [PATCH 092/158] thunderbolt: stream: Check the Rx ring before task starts sleeping In interrupt mode when tb_ring_poll_complete() is called it is possible that a frame was completed right before the ring interrupt was unmasked in which case there will be no interrupt and the task starts sleeping. Avoid this and check if there is anything completed prior putting the task to sleep. The same can happen with poll() so check the ring there too before reporting that there is nothing to read. Assisted-by: LLM Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index a8b0be27b4a1..f5b67c09ed8d 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -801,6 +801,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) } else { ret = wait_event_interruptible(sdev->wait, READ_ONCE(sdev->rx_pending) || + tb_ring_poll_pending(sdev->rx_ring.ring) || tbstream_ring_available(&sdev->rx_ring) || tbstream_dev_valid(sdev) != 0 || tbstream_dev_closed(sdev) || @@ -979,10 +980,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) if (tbstream_ring_available(&sdev->tx_ring)) mask |= EPOLLOUT | EPOLLWRNORM; - if (tbstream_ring_available(&sdev->rx_ring)) + if (tbstream_ring_available(&sdev->rx_ring)) { mask |= EPOLLIN | EPOLLRDNORM; - else + } else { tbstream_dev_complete_rx(sdev); + if (tb_ring_poll_pending(sdev->rx_ring.ring)) + mask |= EPOLLIN | EPOLLRDNORM; + } return mask; } From 600334ef2f29b975485ce062a81fcfa00bd3ef45 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 14 Sep 2026 09:30:20 +0200 Subject: [PATCH 093/158] usb: fotg210: guard host shutdown in gadget-only builds FOTG210 can be built with CONFIG_USB=n, but its shutdown callback references usb_hcd_platform_shutdown() from the USB host core, causing a link failure. Guard the call with CONFIG_USB_FOTG210_HCD so gadget-only builds do not reference the host shutdown helper. Fixes: 7dbc2e1ba8fd ("usb: fotg210: use the common EHCI core") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609141128.DQicnuRW-lkp@intel.com/ Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260914-fotg210-ehci-fixes-v2-1-f98f7d3550d3@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index 7976bdae5f07..7de49b7eb3aa 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -258,7 +258,8 @@ static void fotg210_remove(struct platform_device *pdev) static void fotg210_shutdown(struct platform_device *pdev) { - if (usb_get_dr_mode(&pdev->dev) != USB_DR_MODE_PERIPHERAL) + if (IS_ENABLED(CONFIG_USB_FOTG210_HCD) && + usb_get_dr_mode(&pdev->dev) != USB_DR_MODE_PERIPHERAL) usb_hcd_platform_shutdown(pdev); } From 5753127339fd7c6166ed1391c91ada3d98bb35f1 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 16 Sep 2026 13:43:45 +0200 Subject: [PATCH 094/158] usb: fotg210: fix USB_EHCI_HCD dependency When EHCI is a loadable module, the fotg210 HCD code cannot be built-in: s390-linux-ld: drivers/usb/fotg210/fotg210-hcd.o: in function `fotg210_hcd_reset': fotg210-hcd.c:(.text+0x102): undefined reference to `ehci_setup' Fix this using the same type of dependency that is already used for the USB host support. Signed-off-by: Arnd Bergmann Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260916114358.195465-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/fotg210/Kconfig b/drivers/usb/fotg210/Kconfig index 2c6e76faac64..b7f0b78be7a3 100644 --- a/drivers/usb/fotg210/Kconfig +++ b/drivers/usb/fotg210/Kconfig @@ -16,7 +16,7 @@ if USB_FOTG210 config USB_FOTG210_HCD bool "Faraday FOTG210 USB Host Controller support" depends on USB=y || USB=USB_FOTG210 - depends on USB_EHCI_HCD + depends on USB_EHCI_HCD=y || USB_EHCI_HCD=USB_FOTG210 select USB_EHCI_DEVIANT_PORT_STATUS_REG select USB_EHCI_PORT_RESET_HOOKS select USB_EHCI_PORT_SPEED_HOOK From bbddb219b00af0792242a0ad1dc7fb3344f38733 Mon Sep 17 00:00:00 2001 From: Liu Chao Date: Sun, 13 Sep 2026 20:04:58 +0800 Subject: [PATCH 095/158] usb: fotg210: validate endpoint index in ep0 handlers fotg210_set_feature(), fotg210_clear_feature() and fotg210_get_status() use wIndex from the USB setup packet to index the fotg210->ep[] array without verifying that the endpoint number falls below FOTG210_MAX_NUM_EP (5). USB_ENDPOINT_NUMBER_MASK is 0x0f, so a malicious host can issue a setup packet with wIndex 5..15, resulting in an out-of-bounds array read. The resulting wild pointer is then dereferenced in fotg210_set_epnstall() or fotg210_is_epnstall(), which compute an MMIO register offset from ep->epnum and perform iowrite32 through it. fotg210_clear_feature() is especially problematic: the out-of-bounds access occurs at function entry (source-level) regardless of which USB_RECIP_* case is taken, because the ep pointer is computed before the switch statement. Add upper-bound checks on the endpoint number derived from wIndex in all three functions. Invalid endpoint numbers in clear_feature now trigger fotg210_request_error() (STALL) instead of silently falling through to fotg210_set_cxdone(). Also add the missing le16_to_cpu() conversion for ctrl->wIndex in fotg210_get_status() to fix a sparse endianness warning. Fixes: b84a8dee23fd ("usb: gadget: add Faraday fotg210_udc driver") Cc: stable@vger.kernel.org Signed-off-by: Liu Chao Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260913120458.1608186-1-liuc63@xiaopeng.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index fdd2cf82fde5..e172f6a7856d 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -666,7 +666,7 @@ static void fotg210_set_feature(struct fotg210_udc *fotg210, case USB_RECIP_ENDPOINT: { u8 epnum; epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; - if (epnum) + if (epnum && epnum < FOTG210_MAX_NUM_EP) fotg210_set_epnstall(fotg210->ep[epnum]); else fotg210_set_cxstall(fotg210); @@ -682,8 +682,8 @@ static void fotg210_set_feature(struct fotg210_udc *fotg210, static void fotg210_clear_feature(struct fotg210_udc *fotg210, struct usb_ctrlrequest *ctrl) { - struct fotg210_ep *ep = - fotg210->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK]; + u8 epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + struct fotg210_ep *ep; switch (ctrl->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: @@ -693,7 +693,12 @@ static void fotg210_clear_feature(struct fotg210_udc *fotg210, fotg210_set_cxdone(fotg210); break; case USB_RECIP_ENDPOINT: - if (ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK) { + if (epnum >= FOTG210_MAX_NUM_EP) { + fotg210_request_error(fotg210); + break; + } + if (epnum) { + ep = fotg210->ep[epnum]; if (ep->wedged) { fotg210_set_cxdone(fotg210); break; @@ -749,8 +754,8 @@ static void fotg210_get_status(struct fotg210_udc *fotg210, fotg210->ep0_data = cpu_to_le16(0); break; case USB_RECIP_ENDPOINT: - epnum = ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK; - if (epnum) + epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + if (epnum && epnum < FOTG210_MAX_NUM_EP) fotg210->ep0_data = cpu_to_le16(fotg210_is_epnstall(fotg210->ep[epnum]) << USB_ENDPOINT_HALT); From 7198071826c4966c98f8655bb2c39f972a815bfa Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Thu, 20 Aug 2026 21:17:58 +0000 Subject: [PATCH 096/158] usb: typec: tcpm: Support native battery energy properties in battery AMS Some fuel gauges report battery telemetry in energy rather than charge. Add support for querying native energy properties for Battery Status and Battery Capabilities AMS, falling back to calculating energy from charge and average voltage when primary properties are not supported. +---------+--------------------+----------------------------------+ | Sr. No. | Primary | Fallback | +---------+--------------------+----------------------------------+ | 1 | ENERGY_NOW | CHARGE_NOW + VOLTAGE_AVG | | 2 | ENERGY_FULL_DESIGN | CHARGE_FULL_DESIGN + VOLTAGE_AVG | | 3 | ENERGY_FULL | CHARGE_FULL + VOLTAGE_AVG | +---------+--------------------+----------------------------------+ Note: All properties above are to be prefixed with POWER_SUPPLY_PROP_. Closes: https://lore.kernel.org/all/amVJ1u67qHENBQ5l@venus/ Suggested-by: Sebastian Reichel Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260820-tcpm-energy-props-upstream-v1-1-0a0167863848@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 57 ++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 2d6b14aa2085..5e5f82243c71 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -410,6 +410,9 @@ struct pd_timings { /* Convert microwatt to watt */ #define UW_TO_W(pow) (div_u64((pow), 1000000)) +/* Convert micro-Watt-hours to USB PD energy (increments of 0.1Wh) */ +#define UWH_TO_PD_ENERGY(energy) (UW_TO_W((u64)(energy) * 10)) + /* * 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. @@ -1580,8 +1583,7 @@ 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); + return UWH_TO_PD_ENERGY(energy); } static int tcpm_pd_send_batt_status(struct tcpm_port *port) @@ -1612,16 +1614,17 @@ static int tcpm_pd_send_batt_status(struct tcpm_port *port) else batt_present = val.intval > 0; - ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, - &val); + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_ENERGY_NOW, &val); if (!ret) { - charge_now = val.intval; - ret = power_supply_get_property(batt, - POWER_SUPPLY_PROP_VOLTAGE_AVG, - &val); - if (!ret) - present_charge = tcpm_charge_to_energy(charge_now, - val.intval); + present_charge = UWH_TO_PD_ENERGY(val.intval); + } else { + 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) + present_charge = tcpm_charge_to_energy(charge_now, val.intval); + } } ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); @@ -1678,19 +1681,31 @@ static int tcpm_pd_send_batt_cap(struct tcpm_port *port) 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); + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, &val); + if (!ret) + design_cap = UWH_TO_PD_ENERGY(val.intval); + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_ENERGY_FULL, &val); + if (!ret) + charge_cap = UWH_TO_PD_ENERGY(val.intval); + + if (design_cap != BATTERY_PROPERTY_UNKNOWN && charge_cap != BATTERY_PROPERTY_UNKNOWN) + goto send_cap; + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, &val); + if (ret) + goto send_cap; + + vol = val.intval; + if (design_cap == BATTERY_PROPERTY_UNKNOWN) { + 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 (charge_cap == BATTERY_PROPERTY_UNKNOWN) { + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_FULL, &val); if (!ret) charge_cap = tcpm_charge_to_energy(val.intval, vol); } From d58dffe9ee2c8883193959ff4ef995ec07932874 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 17 Sep 2026 11:19:24 +0200 Subject: [PATCH 097/158] USB: core: amend usb_get_from_anchor() kernel-doc Amend the usb_get_from_anchor() kernel-doc to clarify that the unanchored URB is returned with a reference held. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260917091924.1531457-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/urb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index c06b44ca507b..19e2c21420b8 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -951,8 +951,8 @@ EXPORT_SYMBOL_GPL(usb_wait_anchor_empty_timeout); * usb_get_from_anchor - get an anchor's oldest urb * @anchor: the anchor whose urb you want * - * This will take the oldest urb from an anchor, - * unanchor and return it + * This will take the oldest urb from an anchor, unanchor and return it with + * a reference held. * * Return: The oldest urb from @anchor, or %NULL if @anchor has no * urbs associated with it. From 8d55710a8c439c45737f30ccf626d04241b95303 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 15 Sep 2026 18:55:27 +0300 Subject: [PATCH 098/158] thunderbolt: stream: Improve CLOSE handling on write side If there is only a writer in the stream (i.e the other side just reads and this side just writes) then if the other side is closed the CLOSE packet does is never picked up from the writers RX ring because we do not complete the RX poll. Currently we do that only when something is actually reading the stream. To make this work on write-only stream, complete the RX poll also during ->write_iter(), and then make the stream return -EPIPE to the writer. While there make sdev->closed to use READ/WRITE_ONCE() analogous to other flags. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 150 ++++++++++++++++++++++++++--------- 1 file changed, 112 insertions(+), 38 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index f5b67c09ed8d..8a1e06148a14 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -133,13 +133,18 @@ struct tbstream_ring { * @busy_poll: Instead of interrupts, busy poll the rings * @rx_pending: Receive ring has completions that need to be advanced * @users: Number of times @cdev has been opened - * @closed: CLOSE packet was received + * @close_received: CLOSE packet appeared in the RX ring + * @closed: CLOSE packet was handled in the read side * @removed: Userspace removed the ConfigFS group underneath. * @wait: Waitqueue for open, read and write * @lock: Lock protecting this structure * @tx_ring: Transmit ring * @rx_ring: Receive ring * @list: Stream devices are linked through this + * + * @close_received is used on write side to notify the writer that the + * other side sent CLOSE. @closed on the other hand is used on the read + * side to make read(2) return EOF to the caller. */ struct tbstream_dev { struct config_group group; @@ -154,6 +159,7 @@ struct tbstream_dev { bool busy_poll; bool rx_pending; int users; + bool close_received; bool closed; bool removed; wait_queue_head_t wait; @@ -348,9 +354,19 @@ static inline bool tbstream_dev_removed(const struct tbstream_dev *sdev) return sdev->removed; } +static inline bool tbstream_dev_close_received(const struct tbstream_dev *sdev) +{ + return READ_ONCE(sdev->close_received); +} + static inline bool tbstream_dev_closed(const struct tbstream_dev *sdev) { - return sdev->closed; + return READ_ONCE(sdev->closed); +} + +static inline bool tbstream_dev_rx_pending(const struct tbstream_dev *sdev) +{ + return READ_ONCE(sdev->rx_pending); } static void @@ -366,12 +382,19 @@ tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame, sf->completed = true; sdev->rx_ring.prod++; - if (sf->frame.flags & RING_DESC_CRC_ERROR) + if (sf->frame.flags & RING_DESC_CRC_ERROR) { pr_warn("RX CRC error\n"); - else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN) + } else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN) { pr_warn("RX buffer overrun\n"); - else - wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM); + } else { + __poll_t mask = EPOLLIN | EPOLLRDNORM; + + if (sf->frame.eof == TBSTREAM_CLOSE) { + WRITE_ONCE(sdev->close_received, true); + mask |= EPOLLHUP; + } + wake_up_interruptible_poll(&sdev->wait, mask); + } } static struct tbstream_frame * @@ -553,23 +576,24 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, static int tbstream_dev_send_close(struct tbstream_dev *sdev) { struct tbstream_frame *sf; + ktime_t timeout; - if (sdev->busy_poll) { + /* + * Wait for the ring to have available slots before we send the + * CLOSE packet. + */ + timeout = ktime_add_ms(ktime_get(), 500); + do { + if (tbstream_ring_available(&sdev->tx_ring)) + break; /* - * 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. + * For busy polling we need to advance the ring here as + * well to make the slots available. */ - ktime_t timeout = ktime_add_ms(ktime_get(), 500); - - do { - if (tbstream_ring_available(&sdev->tx_ring)) - break; + if (sdev->busy_poll) tbstream_ring_poll(&sdev->tx_ring); - fsleep(15); - } while (ktime_before(ktime_get(), timeout)); - } + fsleep(15); + } while (ktime_before(ktime_get(), timeout)); sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); if (IS_ERR(sf)) @@ -745,6 +769,13 @@ static int tbstream_dev_busy_poll_wait(struct tbstream_dev *sdev, return -ERESTARTSYS; if (tb_ring_poll_pending(ring->ring)) return 0; + /* + * For TX ring we need to check the RX side too because + * it might have received CLOSE packet. + */ + if (ring == &sdev->tx_ring && + tb_ring_poll_pending(sdev->rx_ring.ring)) + return 0; if (tbstream_dev_valid(sdev) != 0 || tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) return 0; @@ -752,6 +783,22 @@ static int tbstream_dev_busy_poll_wait(struct tbstream_dev *sdev, } } +static bool +tbstream_dev_has_event(struct tbstream_dev *sdev, struct tbstream_ring *ring) +{ + if (tbstream_dev_valid(sdev) != 0) + return true; + if (tbstream_dev_closed(sdev)) + return true; + if (tbstream_dev_removed(sdev)) + return true; + if (tbstream_dev_rx_pending(sdev)) + return true; + if (tbstream_ring_available(ring)) + return true; + return tb_ring_poll_pending(sdev->rx_ring.ring); +} + static ssize_t tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) { @@ -800,12 +847,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) return ret; } else { ret = wait_event_interruptible(sdev->wait, - READ_ONCE(sdev->rx_pending) || - tb_ring_poll_pending(sdev->rx_ring.ring) || - tbstream_ring_available(&sdev->rx_ring) || - tbstream_dev_valid(sdev) != 0 || - tbstream_dev_closed(sdev) || - tbstream_dev_removed(sdev)); + tbstream_dev_has_event(sdev, &sdev->rx_ring)); if (ret) return ret; } @@ -832,7 +874,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (sf->frame.eof == TBSTREAM_CLOSE) { if (!nbytes) { tbstream_dev_consume_rx(sdev); - sdev->closed = true; + WRITE_ONCE(sdev->closed, true); } break; } @@ -868,6 +910,25 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) return nbytes; } +static void tbstream_dev_advance_both(struct tbstream_dev *sdev) +{ + /* + * When busy polling, advance TX completions manually. + * + * We also need to advance the RX side in both modes to be able + * to receive CLOSE packet from the other peer if there is no + * reader. + */ + if (sdev->busy_poll) { + tbstream_ring_poll(&sdev->tx_ring); + tbstream_dev_advance_rx(sdev); + } else if (tbstream_dev_rx_pending(sdev) || + tb_ring_poll_pending(sdev->rx_ring.ring)) { + tbstream_dev_advance_rx(sdev); + tbstream_dev_complete_rx(sdev); + } +} + static ssize_t tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) { @@ -886,9 +947,8 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) return ret; for (;;) { - /* When busy polling, advance any completions manually */ - if (sdev->busy_poll) - tbstream_ring_poll(&sdev->tx_ring); + /* Advance TX (and RX) completions */ + tbstream_dev_advance_both(sdev); ret = tbstream_dev_valid(sdev); if (ret) { @@ -896,7 +956,12 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) return ret; } - if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { + if (tbstream_dev_close_received(sdev)) { + mutex_unlock(&sdev->lock); + return -EPIPE; + } + + if (tbstream_dev_removed(sdev)) { mutex_unlock(&sdev->lock); return -ENXIO; } @@ -915,10 +980,8 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) return ret; } 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)); + tbstream_dev_has_event(sdev, &sdev->tx_ring) || + tbstream_dev_close_received(sdev)); if (ret) return ret; } @@ -978,6 +1041,8 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) */ tbstream_dev_advance_rx(sdev); + if (tbstream_dev_close_received(sdev)) + mask |= EPOLLHUP; if (tbstream_ring_available(&sdev->tx_ring)) mask |= EPOLLOUT | EPOLLWRNORM; if (tbstream_ring_available(&sdev->rx_ring)) { @@ -1037,6 +1102,8 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) sdev->users--; goto err_unlock; } + + sdev->close_received = false; sdev->closed = false; } @@ -1058,11 +1125,18 @@ static int tbstream_dev_fops_release(struct inode *inode, struct file *file) mutex_lock(&sdev->lock); if (--sdev->users == 0) { /* - * Send CLOSE tunneled packet to notify the other end - * that we are closing the file. We do this twice if the - * first one fails. + * Advance now in case there is CLOSE waiting in the RX + * ring. */ - tbstream_dev_send_close(sdev); + tbstream_dev_advance_both(sdev); + /* + * Send CLOSE tunneled packet to notify the other end + * that we are closing the file, if it is not closed + * already. + */ + if (!tbstream_dev_close_received(sdev) && + tbstream_dev_valid(sdev) == 0) + tbstream_dev_send_close(sdev); tbstream_dev_stop(sdev); } mutex_unlock(&sdev->lock); From aaf6230cb2cd5fc50db9ba6db3b813d1a4b1d582 Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 27 Aug 2026 17:18:45 -0700 Subject: [PATCH 099/158] thunderbolt: Make software margining error counter configurable The debugfs software margining run path currently initializes the request with USB4_MARGIN_SW_ERROR_COUNTER_CLEAR, which implicitly clears/enables counters every time software margining is started. On some host-router/firmware implementations that enforce PHY interface for USB4 (PIPE) margining control, CLEAR and START are distinct operations. - CLEAR resets error counters without starting margining - START begins margin testing and error counting Make the error counter operation configurable through debugfs error_counter control instead of hardcoding CLEAR. Additionally, only execute the software margining sampling loop when the selected operation is START; for CLEAR or STOP operations, execute the counter operation and return immediately. Example to CLEAR error counter: cd /sys/kernel/debug/thunderbolt///margining echo clear > error_counter Example to START error counter: cd /sys/kernel/debug/thunderbolt///margining echo start > error_counter After configuring error counter run software margining for changes to take effect: echo 1 > run This aligns the debugfs behavior with the USB4 v2/PIPE expectation and lets users explicitly choose between CLEAR/START/STOP. Co-developed-by: Rene Sapiens Signed-off-by: Rene Sapiens Signed-off-by: Anurag Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 6e9080e7bcec..730916f62d39 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -1156,18 +1156,23 @@ static int margining_run_sw(struct tb_margining *margining, u32 nsamples = margining->dwell_time / DWELL_SAMPLE_INTERVAL; int ret, i; + if (params->error_counter != USB4_MARGIN_SW_ERROR_COUNTER_START) + goto out_stop_or_clear; + ret = usb4_port_sw_margin(margining->port, margining->target, margining->index, params, margining->results); if (ret) - goto out_stop; + return ret; for (i = 0; i <= nsamples; i++) { u32 errors = 0; ret = usb4_port_sw_margin_errors(margining->port, margining->target, margining->index, &margining->results[1]); - if (ret) + if (ret) { + tb_port_warn(margining->port, "failed to read margining error counters\n"); break; + } if (margining->lanes == USB4_MARGINING_LANE_RX0) errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK, @@ -1188,14 +1193,13 @@ static int margining_run_sw(struct tb_margining *margining, fsleep(DWELL_SAMPLE_INTERVAL * USEC_PER_MSEC); } -out_stop: +out_stop_or_clear: /* - * Stop the counters but don't clear them to allow the + * Stop the counters or clear them as per the * different error counter configurations. */ - margining_modify_error_counter(margining, margining->lanes, - USB4_MARGIN_SW_ERROR_COUNTER_STOP); - return ret; + return margining_modify_error_counter(margining, margining->lanes, + params->error_counter); } static int validate_margining(struct tb_margining *margining) @@ -1272,7 +1276,7 @@ static int margining_run_write(void *data, u64 val) if (margining->software) { struct usb4_port_margining_params params = { - .error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR, + .error_counter = margining->error_counter, .lanes = margining->lanes, .time = margining->time, .voltage_time_offset = margining->voltage_time_offset, From c4f71538eec46f0e1e68fbf07381a9b76841466d Mon Sep 17 00:00:00 2001 From: Rene Sapiens Date: Thu, 27 Aug 2026 17:18:46 -0700 Subject: [PATCH 100/158] thunderbolt: Define number of dwords for hardware margining results Use a defined size for hardware margining results in preparation for the software margining extended error counters which requires a larger buffer size. Signed-off-by: Rene Sapiens Co-developed-by: Anurag Signed-off-by: Anurag Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 730916f62d39..ea80354c410a 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -37,6 +37,8 @@ #define COUNTER_SET_LEN 3 +#define HW_MARGINING_DWORDS 3 + /* * USB4 spec doesn't specify dwell range, the range of 100 ms to 500 ms * probed to give good results. @@ -1307,7 +1309,7 @@ static int margining_run_write(void *data, u64 val) margining->lanes); ret = usb4_port_hw_margin(port, margining->target, margining->index, ¶ms, - margining->results, ARRAY_SIZE(margining->results)); + margining->results, HW_MARGINING_DWORDS); } if (down_sw) @@ -1429,7 +1431,7 @@ static int margining_results_show(struct seq_file *s, void *not_used) seq_printf(s, "0x%08x\n", margining->results[0]); /* Only the hardware margining has two result dwords */ if (!margining->software) { - for (int i = 1; i < ARRAY_SIZE(margining->results); i++) + for (int i = 1; i < HW_MARGINING_DWORDS; i++) seq_printf(s, "0x%08x\n", margining->results[i]); if (margining->lanes == USB4_MARGINING_LANE_ALL) { From 879519402cb5116538faf91952180f557083f9c3 Mon Sep 17 00:00:00 2001 From: Rene Sapiens Date: Thu, 27 Aug 2026 17:18:47 -0700 Subject: [PATCH 101/158] thunderbolt: Add extended error counter support for software lane margining Add support for software margining extended error counters. Increase the results buffer size to accommodate the new dwords used for extended error counters for RX0, RX1 and RX2. Signed-off-by: Rene Sapiens Co-developed-by: Anurag Signed-off-by: Anurag Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 34 ++++++++++++++++++++++++++++++++-- drivers/thunderbolt/sb_regs.h | 2 ++ drivers/thunderbolt/tb.h | 2 +- drivers/thunderbolt/usb4.c | 5 +++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index ea80354c410a..43de2938d1e4 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -38,6 +38,7 @@ #define COUNTER_SET_LEN 3 #define HW_MARGINING_DWORDS 3 +#define SW_MARGINING_DWORDS 4 /* * USB4 spec doesn't specify dwell range, the range of 100 ms to 500 ms @@ -496,7 +497,7 @@ struct tb_margining { unsigned int gen; bool asym_rx; u32 caps[3]; - u32 results[3]; + u32 results[5]; enum usb4_margining_lane lanes; unsigned int min_ber_level; unsigned int max_ber_level; @@ -614,6 +615,11 @@ supports_optional_voltage_offset_range(const struct tb_margining *margining) return margining->caps[0] & USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT; } +static bool supports_extended_error_counter(const struct tb_margining *margining) +{ + return margining->caps[0] & USB4_MARGIN_CAP_0_EXT_ERR_COUNTER; +} + static ssize_t margining_ber_level_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) @@ -703,6 +709,8 @@ static int margining_caps_show(struct seq_file *s, void *not_used) ber_level_show(s, margining->max_ber_level); } else { seq_puts(s, "# hardware margining: no\n"); + seq_printf(s, "# extended error counter: %s\n", + str_yes_no(supports_extended_error_counter(margining))); } seq_printf(s, "# all lanes simultaneously: %s\n", @@ -1157,10 +1165,16 @@ static int margining_run_sw(struct tb_margining *margining, { u32 nsamples = margining->dwell_time / DWELL_SAMPLE_INTERVAL; int ret, i; + u32 dwords; if (params->error_counter != USB4_MARGIN_SW_ERROR_COUNTER_START) goto out_stop_or_clear; + if (supports_extended_error_counter(margining)) + dwords = SW_MARGINING_DWORDS; + else + dwords = 1; + ret = usb4_port_sw_margin(margining->port, margining->target, margining->index, params, margining->results); if (ret) @@ -1170,7 +1184,8 @@ static int margining_run_sw(struct tb_margining *margining, u32 errors = 0; ret = usb4_port_sw_margin_errors(margining->port, margining->target, - margining->index, &margining->results[1]); + margining->index, &margining->results[1], + dwords); if (ret) { tb_port_warn(margining->port, "failed to read margining error counters\n"); break; @@ -1450,18 +1465,30 @@ static int margining_results_show(struct seq_file *s, void *not_used) u32 lane_errors, result; seq_printf(s, "0x%08x\n", margining->results[1]); + if (supports_extended_error_counter(margining)) { + seq_printf(s, "0x%08x\n", margining->results[2]); + seq_printf(s, "0x%08x\n", margining->results[3]); + seq_printf(s, "0x%08x\n", margining->results[4]); + } + result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]); if (result == USB4_MARGINING_LANE_RX0 || result == USB4_MARGINING_LANE_ALL) { lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK, margining->results[1]); + if (supports_extended_error_counter(margining)) + lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, + margining->results[2]); seq_printf(s, "# lane 0 errors: %u\n", lane_errors); } if (result == USB4_MARGINING_LANE_RX1 || result == USB4_MARGINING_LANE_ALL) { lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK, margining->results[1]); + if (supports_extended_error_counter(margining)) + lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, + margining->results[3]); seq_printf(s, "# lane 1 errors: %u\n", lane_errors); } if (margining->asym_rx && @@ -1469,6 +1496,9 @@ static int margining_results_show(struct seq_file *s, void *not_used) result == USB4_MARGINING_LANE_ALL)) { lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK, margining->results[1]); + if (supports_extended_error_counter(margining)) + lane_errors |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, + margining->results[4]); seq_printf(s, "# lane 2 errors: %u\n", lane_errors); } } diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h index 5391502a4b87..b801f3a694be 100644 --- a/drivers/thunderbolt/sb_regs.h +++ b/drivers/thunderbolt/sb_regs.h @@ -59,6 +59,7 @@ enum usb4_sb_opcode { #define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK GENMASK(18, 13) #define USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT BIT(19) #define USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK GENMASK(26, 20) +#define USB4_MARGIN_CAP_0_EXT_ERR_COUNTER BIT(28) #define USB4_MARGIN_CAP_1_MAX_VOLT_OFS_OPT_MASK GENMASK(7, 0) #define USB4_MARGIN_CAP_1_TIME_DESTR BIT(8) #define USB4_MARGIN_CAP_1_TIME_INDP_MASK GENMASK(10, 9) @@ -108,5 +109,6 @@ enum usb4_sb_opcode { #define USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK GENMASK(3, 0) #define USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK GENMASK(7, 4) #define USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK GENMASK(11, 8) +#define USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK GENMASK(15, 4) #endif diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index c112954ce3fd..e86845762e22 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1442,7 +1442,7 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, u8 index, const struct usb4_port_margining_params *params, u32 *results); int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, - u8 index, u32 *errors); + u8 index, u32 *errors, size_t dwords); int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index); int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 5fd4fe070f25..fe1b061f7055 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -1826,13 +1826,14 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, * @target: Sideband target * @index: Retimer index if target is %USB4_SB_TARGET_RETIMER * @errors: Error metadata is copied here. + * @dwords: Number of dwords to read error counter values * * This reads back the software margining error counters from the port. * * Return: %0 on success, negative errno otherwise. */ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, - u8 index, u32 *errors) + u8 index, u32 *errors, size_t dwords) { int ret; @@ -1842,7 +1843,7 @@ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, return ret; return usb4_port_sb_read(port, target, index, USB4_SB_METADATA, errors, - sizeof(*errors)); + sizeof(*errors) * dwords); } static inline int usb4_port_retimer_op(struct tb_port *port, u8 index, From d224aacc8cf333700257fe0936f701848c2ea1b8 Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 27 Aug 2026 17:18:48 -0700 Subject: [PATCH 102/158] thunderbolt: Fix sideband register access in usb4_port_sw_margin_errors() The READ_SW_MARGIN_ERR (RDSW) completion payload is returned in the USB4 sideband data window, but the location differs between standard and extended error counters. Standard error counters are returned in USB4_SB_METADATA and fit within a single dword. Extended error counters require 4 dwords and are returned in USB4_SB_DATA. Add conditional logic to select the appropriate register based on the dwords parameter that corresponds with error counter type. Co-developed-by: Rene Sapiens Signed-off-by: Rene Sapiens Signed-off-by: Anurag Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index fe1b061f7055..c9b3931c45ed 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -1836,13 +1836,16 @@ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, u8 index, u32 *errors, size_t dwords) { int ret; + u8 reg; ret = usb4_port_sb_op(port, target, index, USB4_SB_OPCODE_READ_SW_MARGIN_ERR, 150); if (ret) return ret; - return usb4_port_sb_read(port, target, index, USB4_SB_METADATA, errors, + reg = (dwords > 1) ? USB4_SB_DATA : USB4_SB_METADATA; + + return usb4_port_sb_read(port, target, index, reg, errors, sizeof(*errors) * dwords); } From 5ef3b5a4ea78da6ffdf32bcfe7517d4b2860f339 Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 27 Aug 2026 17:18:49 -0700 Subject: [PATCH 103/158] thunderbolt: Extract per-lane extended error counters in software margining USB4 defines two error counter formats for software margining: - Standard counters (USB4 v1): 4-bit per-lane values in DW0. - Extended counters (USB4 v2): 16-bit per-lane values split across DW0 (lower 4 bits) and DW1-DW3 (upper 12 bits). The existing debugfs logic only reads DW0 when checking for errors, which works correctly for standard error counters but only captures the lower 4 bits, missing the 12 MSBs if extended error counter is supported. Read and combine the full counter values from all result dwords before evaluating whether errors occurred on any lane. When running software margining with "all lanes", determine which Rx lanes are actually active and only check those: - ASYM_RX: RX0, RX1, RX2 - ASYM_TX: RX0 - Symmetric: RX0, RX1 Refactor the error counter extraction into a helper function to improve readability. Signed-off-by: Anurag Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 68 ++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 43de2938d1e4..ccefc5dd1ded 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -1160,9 +1160,34 @@ static int margining_mode_show(struct seq_file *s, void *not_used) } DEBUGFS_ATTR_RW(margining_mode); +static u32 margining_get_lane_error(const u32 *results, unsigned int lane, bool extended) +{ + u32 error_count; + + switch (lane) { + case USB4_MARGINING_LANE_RX0: + error_count = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK, results[0]); + break; + case USB4_MARGINING_LANE_RX1: + error_count = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK, results[0]); + break; + case USB4_MARGINING_LANE_RX2: + error_count = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK, results[0]); + break; + default: + return 0; + } + + if (extended) + error_count |= FIELD_PREP(USB4_MARGIN_SW_EXT_ERR_COUNTER_MASK, results[1 + lane]); + + return error_count; +} + static int margining_run_sw(struct tb_margining *margining, struct usb4_port_margining_params *params) { + bool extended_err = supports_extended_error_counter(margining); u32 nsamples = margining->dwell_time / DWELL_SAMPLE_INTERVAL; int ret, i; u32 dwords; @@ -1170,7 +1195,7 @@ static int margining_run_sw(struct tb_margining *margining, if (params->error_counter != USB4_MARGIN_SW_ERROR_COUNTER_START) goto out_stop_or_clear; - if (supports_extended_error_counter(margining)) + if (extended_err) dwords = SW_MARGINING_DWORDS; else dwords = 1; @@ -1191,17 +1216,35 @@ static int margining_run_sw(struct tb_margining *margining, break; } - if (margining->lanes == USB4_MARGINING_LANE_RX0) - errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK, - margining->results[1]); - else if (margining->lanes == USB4_MARGINING_LANE_RX1) - errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK, - margining->results[1]); - else if (margining->lanes == USB4_MARGINING_LANE_RX2) - errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK, - margining->results[1]); - else if (margining->lanes == USB4_MARGINING_LANE_ALL) - errors = margining->results[1]; + if (margining->lanes == USB4_MARGINING_LANE_ALL) { + if (extended_err) { + /* Extended counters with all lanes: check each active lane */ + int width = tb_port_get_link_width(margining->port); + unsigned int lane, max_lane = 2; + + if (width == TB_LINK_WIDTH_ASYM_TX) { + max_lane = 1; + } else if ((width == TB_LINK_WIDTH_ASYM_RX) && margining->asym_rx) { + max_lane = 3; + } else if (width < 0) { + tb_port_warn(margining->port, "failed to read link width\n"); + break; + } + + for (lane = 0; lane < max_lane; lane++) { + errors = margining_get_lane_error(&margining->results[1], + lane, extended_err); + if (errors) + break; + } + } else { + /* Standard counters: all lane errors fit in results[1]*/ + errors = margining->results[1]; + } + } else { + errors = margining_get_lane_error(&margining->results[1], + margining->lanes, extended_err); + } /* Any errors stop the test */ if (errors) @@ -1209,6 +1252,7 @@ static int margining_run_sw(struct tb_margining *margining, fsleep(DWELL_SAMPLE_INTERVAL * USEC_PER_MSEC); } + params->error_counter = USB4_MARGIN_SW_ERROR_COUNTER_STOP; out_stop_or_clear: /* From a93a8e3200002f0c345fec4c340390e9a60aabc7 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 18 Sep 2026 09:19:29 +0300 Subject: [PATCH 104/158] thunderbolt: stream: Make read return framing error to the userspace If any of the frames are corrupted during the receive we currently log a warning and pass them to the userspace without it knowing they were corrupted. Make the driver behave better and return an error (-EIO) to the userspace in that case. For the CLOSE packet, payload does not matter so we handle it the usual way. Reviewed-by: Ivy Lopez Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index 8a1e06148a14..fe88341c4e29 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -375,6 +375,7 @@ tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame, { struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame); struct tbstream_dev *sdev = sf->sdev; + __poll_t mask; if (canceled) return; @@ -382,19 +383,17 @@ tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame, sf->completed = true; sdev->rx_ring.prod++; - if (sf->frame.flags & RING_DESC_CRC_ERROR) { - pr_warn("RX CRC error\n"); - } else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN) { - pr_warn("RX buffer overrun\n"); - } else { - __poll_t mask = EPOLLIN | EPOLLRDNORM; - - if (sf->frame.eof == TBSTREAM_CLOSE) { - WRITE_ONCE(sdev->close_received, true); - mask |= EPOLLHUP; - } - wake_up_interruptible_poll(&sdev->wait, mask); + mask = EPOLLIN | EPOLLRDNORM; + /* + * The CLOSE packet does not have a payload so the flags do not + * matter. The read_iter() deals with the flags. + */ + if (sf->frame.eof == TBSTREAM_CLOSE) { + WRITE_ONCE(sdev->close_received, true); + mask |= EPOLLHUP; } + + wake_up_interruptible_poll(&sdev->wait, mask); } static struct tbstream_frame * @@ -877,6 +876,19 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) WRITE_ONCE(sdev->closed, true); } break; + } else if (sf->frame.flags & + (RING_DESC_CRC_ERROR | RING_DESC_BUFFER_OVERRUN)) { + /* + * If something was already read return that now + * and next read will report the error. + */ + if (!nbytes) { + pr_warn("corrupted frame received, flags %#x\n", + sf->frame.flags); + tbstream_dev_consume_rx(sdev); + ret = -EIO; + } + break; } sf_size = tb_ring_frame_size(&sf->frame); From 20b73dc5c5d2f5b0800bff62cd4586d3472b4dc6 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 20 Sep 2026 17:35:38 -0400 Subject: [PATCH 105/158] USB: gadget: core: Improve documentation for usb_ep_disable() The kerneldoc for usb_ep_disable() in drivers/usb/gadget/udc/core.c starts with a confusing (and probably meaningless) sentence, and it fails to mention that the routine can return before all the completion handlers for previously completed requests on the endpoint have finished. Also, the summary line doesn't say what the routine actually does, and the text doesn't use proper capitalization. Update the documentation to ameliorate these flaws. No code changes. Reported-by: Minseo Kim Link: https://lore.kernel.org/linux-usb/fb56064c-6481-44b3-88e5-8e5ff7b85703@rowland.harvard.edu/ Link: https://lore.kernel.org/linux-usb/0b6b13ff-789e-4ba4-9765-1052a154ef27@rowland.harvard.edu/ Signed-off-by: Alan Stern Link: https://patch.msgid.link/d9f56a9c-2c94-4002-b674-53ba7e5ad5b2@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index f6da12b553a0..d5844384c368 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -142,13 +142,14 @@ int usb_ep_enable(struct usb_ep *ep) EXPORT_SYMBOL_GPL(usb_ep_enable); /** - * usb_ep_disable - endpoint is no longer usable + * usb_ep_disable - unconfigure an endpoint, making it no longer usable * @ep:the endpoint being unconfigured. may not be the endpoint named "ep0". * - * no other task may be using this endpoint when this is called. - * any pending and uncompleted requests will complete with status + * Any pending and uncompleted requests will complete with status * indicating disconnect (-ESHUTDOWN) before this call returns. - * gadget drivers must call usb_ep_enable() again before queueing + * (However, this call will not wait for requests whose completion + * handlers have already started running but not yet returned.) + * Gadget drivers must call usb_ep_enable() again before queuing * requests to the endpoint. * * This routine may be called in an atomic (interrupt) context. From 4ea75eeaf4e55ef0447037f552abbaba4af06144 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 20 Sep 2026 17:39:46 -0400 Subject: [PATCH 106/158] USB: gadgetfs: Fix races and locking in asynchronous I/O It has been known for a long time that the asynchronous I/O handling in the gadgetfs driver is subject to multiple races and locking issues. Even the very first version of AIO support -- see: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git/commit/drivers/usb/gadget/inode.c?id=75787d943ab3770270c6c3f6e154ab8e59f693ef -- had weird locking in ep_aio_cancel(), a tacit acknowledgment that it wasn't being done correctly. (I vaguely recall some discussion about this on the linux-usb-devel mailing list, eventually concluding that the problem was too hard to solve then. See for example these threads: https://marc.info/?l=linux-usb-devel&m=118530826204776&w=2 https://marc.info/?l=linux-usb-devel&m=119129049420190&w=2 Note: usbfs2 was an attempt to update the usbfs API. It never went anywhere.) Regardless, it's well past time to fix all these issues. Minseo Kim has done very thorough testing, and all the races he was able to find are fixed by this patch. So are the lockdep violations. The key is keeping explicit track of the various stages of asynchronous URB submission/completion as well as of AIO cancellation, because bad interactions between those two are responsible for the races. Doing this required adding a new spinlock to serialize access to the new state variables, as well as making local copies of values belonging to a private data structure that could be deallocated at unexpected times. Fixing the lockdep problems required moving the usb_unlink_urb() operation to a separate worker thread (see the comment added to ep_aio_cancel()). Aside from that, the overall structure of the code was not changed very much, although there is quite a lot of churn in several of the AIO-handling subroutines. Reported-by: Minseo Kim Closes: https://lore.kernel.org/CAFmvuTWc880R=mw7=kixfx3Yb8u9thLHPA7D=rm6a8p9dYBBDA@mail.gmail.com/ Tested-by: Minseo Kim Signed-off-by: Alan Stern Link: https://patch.msgid.link/c06cb937-6872-401a-8c94-1944f2838007@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 186 ++++++++++++++++++++++++------ 1 file changed, 152 insertions(+), 34 deletions(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index 67c6ffaf4f72..257ba1f596cf 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -236,6 +236,8 @@ static void put_ep (struct ep_data *data) static const char *CHIP; static DEFINE_MUTEX(sb_mutex); /* Serialize superblock operations */ +static DEFINE_SPINLOCK(aio_lock); /* Protect aio cancellation info */ + /*----------------------------------------------------------------------*/ /* NOTE: don't use dev_printk calls before binding to the gadget @@ -433,40 +435,104 @@ static long ep_ioctl(struct file *fd, unsigned code, unsigned long value) /* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */ +enum aio_req_state { + AIO_SUBMITTING, + AIO_RUNNING, + AIO_COMPLETED, + AIO_GIVEN_BACK, +}; + +enum aio_cancel_state { + AIO_NOT_CANCELLED, + AIO_UNLINKING, + AIO_UNLINK_DONE, +}; + struct kiocb_priv { struct usb_request *req; struct ep_data *epdata; + struct usb_ep *ep; struct kiocb *iocb; struct mm_struct *mm; - struct work_struct work; + struct work_struct copy_work; + struct work_struct unlink_work; void *buf; struct iov_iter to; const void *to_free; unsigned actual; + enum aio_req_state req_state; + enum aio_cancel_state cancel_state; }; +static void ep_unlink_worker(struct work_struct *work) +{ + struct kiocb_priv *priv; + struct usb_request *req; + struct ep_data *epdata; + struct usb_ep *ep; + enum aio_req_state req_state; + + priv = container_of(work, struct kiocb_priv, unlink_work); + req = priv->req; + epdata = priv->epdata; + ep = priv->ep; + + usb_ep_dequeue(ep, req); + + spin_lock_irq(&aio_lock); + req_state = priv->req_state; + priv->cancel_state = AIO_UNLINK_DONE; + spin_unlock_irq(&aio_lock); + + /* + * req and epdata are freed after unlinking and completion are both done. + * priv is freed after unlinking and giveback are both done. + */ + if (req_state >= AIO_COMPLETED) { + /* priv may have been freed already, depending on req_state */ + usb_ep_free_request(ep, req); + put_ep(epdata); + if (req_state == AIO_GIVEN_BACK) + kfree(priv); + } +} + static int ep_aio_cancel(struct kiocb *iocb) { - struct kiocb_priv *priv = iocb->private; - struct ep_data *epdata; - int value; + struct kiocb_priv *priv; + unsigned long flags; - local_irq_disable(); - epdata = priv->epdata; - // spin_lock(&epdata->dev->lock); - if (likely(epdata && epdata->ep && priv->req)) - value = usb_ep_dequeue (epdata->ep, priv->req); - else - value = -EINVAL; - // spin_unlock(&epdata->dev->lock); - local_irq_enable(); + spin_lock_irqsave(&aio_lock, flags); + priv = iocb->private; + if (!priv || priv->cancel_state != AIO_NOT_CANCELLED) { + spin_unlock_irqrestore(&aio_lock, flags); + return -EINVAL; /* Already completed or cancelled */ + } + if (priv->req_state == AIO_SUBMITTING) { + priv->cancel_state = AIO_UNLINK_DONE; + spin_unlock_irqrestore(&aio_lock, flags); + return 0; /* ep_aio() will call us again if needed */ + } - return value; + priv->cancel_state = AIO_UNLINKING; + spin_unlock_irqrestore(&aio_lock, flags); + + /* + * We are called with the aio core holding iocb's context lock. + * usb_ep_dequeue() is allowed to run synchronously, calling the + * completion handler ep_aio_complete() before it returns. + * But ep_aio_complete() may call iocb->io_complete(), which + * tries to acquire the context lock, leading to deadlock. + * For this reason, do the dequeue operation in a work routine. + */ + INIT_WORK(&priv->unlink_work, ep_unlink_worker); + schedule_work(&priv->unlink_work); + return 0; } static void ep_user_copy_worker(struct work_struct *work) { - struct kiocb_priv *priv = container_of(work, struct kiocb_priv, work); + struct kiocb_priv *priv = container_of(work, struct kiocb_priv, copy_work); struct mm_struct *mm = priv->mm; struct kiocb *iocb = priv->iocb; size_t ret; @@ -488,19 +554,25 @@ static void ep_user_copy_worker(struct work_struct *work) kfree(priv->buf); kfree(priv->to_free); - kfree(priv); + + spin_lock_irq(&aio_lock); + priv->req_state = AIO_GIVEN_BACK; + if (priv->cancel_state != AIO_UNLINKING) + kfree(priv); + spin_unlock_irq(&aio_lock); } static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) { struct kiocb *iocb = req->context; struct kiocb_priv *priv = iocb->private; - struct ep_data *epdata = priv->epdata; + enum aio_req_state new_req_state; + enum aio_cancel_state cancel_state; - /* lock against disconnect (and ideally, cancel) */ - spin_lock(&epdata->dev->lock); - priv->req = NULL; - priv->epdata = NULL; + /* Prevent future cancellation */ + spin_lock(&aio_lock); + iocb->private = NULL; + spin_unlock(&aio_lock); /* if this was a write or a read returning no data then we * don't need to copy anything to userspace, so we can @@ -510,25 +582,35 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) mmdrop(priv->mm); kfree(req->buf); kfree(priv->to_free); - kfree(priv); - iocb->private = NULL; iocb->ki_complete(iocb, req->actual ? req->actual : (long)req->status); + new_req_state = AIO_GIVEN_BACK; } else { /* ep_copy_to_user() won't report both; we hide some faults */ if (unlikely(0 != req->status)) - DBG(epdata->dev, "%s fault %d len %d\n", + DBG(priv->epdata->dev, "%s fault %d len %d\n", ep->name, req->status, req->actual); priv->buf = req->buf; priv->actual = req->actual; - INIT_WORK(&priv->work, ep_user_copy_worker); - schedule_work(&priv->work); + new_req_state = AIO_COMPLETED; } - usb_ep_free_request(ep, req); - spin_unlock(&epdata->dev->lock); - put_ep(epdata); + spin_lock(&aio_lock); + priv->req_state = new_req_state; + cancel_state = priv->cancel_state; + spin_unlock(&aio_lock); + + if (new_req_state == AIO_COMPLETED) { + INIT_WORK(&priv->copy_work, ep_user_copy_worker); + schedule_work(&priv->copy_work); + } + if (cancel_state != AIO_UNLINKING) { + usb_ep_free_request(ep, req); + put_ep(priv->epdata); + if (new_req_state == AIO_GIVEN_BACK) + kfree(priv); + } } static ssize_t ep_aio(struct kiocb *iocb, @@ -539,11 +621,12 @@ static ssize_t ep_aio(struct kiocb *iocb, { struct usb_request *req; ssize_t value; + struct usb_ep *ep; + bool need_unlink = false; iocb->private = priv; priv->iocb = iocb; - kiocb_set_cancel_fn(iocb, ep_aio_cancel); get_ep(epdata); priv->epdata = epdata; priv->actual = 0; @@ -555,10 +638,12 @@ static ssize_t ep_aio(struct kiocb *iocb, */ spin_lock_irq(&epdata->dev->lock); value = -ENODEV; - if (unlikely(epdata->ep == NULL)) + ep = epdata->ep; + if (unlikely(ep == NULL)) goto fail; + priv->ep = ep; - req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC); + req = usb_ep_alloc_request(ep, GFP_ATOMIC); value = -ENOMEM; if (unlikely(!req)) goto fail; @@ -568,12 +653,45 @@ static ssize_t ep_aio(struct kiocb *iocb, req->length = len; req->complete = ep_aio_complete; req->context = iocb; - value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC); + + priv->req_state = AIO_SUBMITTING; + priv->cancel_state = AIO_NOT_CANCELLED; + + /* Not allowed to manipulate the aio context while holding dev->lock */ + ++epdata->dev->udc_usage; + spin_unlock_irq(&epdata->dev->lock); + + kiocb_set_cancel_fn(iocb, ep_aio_cancel); + value = usb_ep_queue(ep, req, GFP_KERNEL); + + spin_lock_irq(&epdata->dev->lock); + --epdata->dev->udc_usage; + if (unlikely(0 != value)) { - usb_ep_free_request(epdata->ep, req); + spin_lock(&aio_lock); + iocb->private = NULL; + spin_unlock(&aio_lock); + + usb_ep_free_request(ep, req); goto fail; } spin_unlock_irq(&epdata->dev->lock); + + spin_lock_irq(&aio_lock); + if (iocb->private != NULL) { + priv->req_state = AIO_RUNNING; + + /* Cancelled before or just after submission? */ + if (priv->cancel_state == AIO_UNLINK_DONE) { + priv->cancel_state = AIO_NOT_CANCELLED; + need_unlink = true; + } + } /* Otherwise already completed */ + spin_unlock_irq(&aio_lock); + + if (need_unlink) /* Redo cancel that was too early */ + ep_aio_cancel(iocb); + return -EIOCBQUEUED; fail: From 6f41c4ff394f502d9ed7b15ce862bc494f553a6f Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 20 Sep 2026 17:41:53 -0400 Subject: [PATCH 107/158] USB: gadgetfs: Fix test for copying data in asynchronous I/O The gadgetfs driver contains a test for whether any data from a completed asynchronous I/O transfer needs to be copied back to userspace. This test is not being done correctly in all cases. It's a subtle issue involving whether an iov_iter requires a separate iovec for storing its destination buffer information; sometimes this information can be stored directly in the iov_iter itself. Thus, lack of a separate iovec does not mean that there is no data to copy. Rather than messing around with the internal details of struct iov_iter, fix the test by simply adding a new field to keep track of whether an AIO operation is a read or a write. Reported-by: Minseo Kim Closes: https://lore.kernel.org/CAFmvuTWc880R=mw7=kixfx3Yb8u9thLHPA7D=rm6a8p9dYBBDA@mail.gmail.com/ Tested-by: Minseo Kim Signed-off-by: Alan Stern Link: https://patch.msgid.link/a9a88aa0-a624-4b42-a9f7-661d50a4894b@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index 257ba1f596cf..a3d86327a987 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -462,6 +462,7 @@ struct kiocb_priv { unsigned actual; enum aio_req_state req_state; enum aio_cancel_state cancel_state; + bool is_read; }; static void ep_unlink_worker(struct work_struct *work) @@ -578,7 +579,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) * don't need to copy anything to userspace, so we can * complete the aio request immediately. */ - if (priv->to_free == NULL || unlikely(req->actual == 0)) { + if (!priv->is_read || unlikely(req->actual == 0)) { mmdrop(priv->mm); kfree(req->buf); kfree(priv->to_free); @@ -745,6 +746,7 @@ ep_read_iter(struct kiocb *iocb, struct iov_iter *to) value = -ENOMEM; if (!priv) goto fail; + priv->is_read = true; priv->to_free = dup_iter(&priv->to, to, GFP_KERNEL); if (!iter_is_ubuf(&priv->to) && !priv->to_free) { kfree(priv); From dfb7052396e3591c0ff8212e7d1eb86f403412e5 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 20 Sep 2026 17:43:36 -0400 Subject: [PATCH 108/158] USB: gadgetfs: Wait for work routines before unloading The gadgetfs driver uses work routines to handle asynchronous I/O, but it doesn't make certain that the work routines have completed before it allows itself to be unloaded from memory (if it is built as a module). Testing shows that under the right conditions this can actually happen. Fix this problem by creating a dedicated workqueue for the driver's work routines. Automatic flushing when the workqueue is destroyed will ensure that no work routines are still in operation when the driver is unloaded. Tested-by: Minseo Kim Signed-off-by: Alan Stern Link: https://patch.msgid.link/2f657ef5-6ea0-4820-9579-324b9b812417@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index a3d86327a987..d5cec67bab17 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -237,6 +237,7 @@ static const char *CHIP; static DEFINE_MUTEX(sb_mutex); /* Serialize superblock operations */ static DEFINE_SPINLOCK(aio_lock); /* Protect aio cancellation info */ +static struct workqueue_struct *gadgetfs_wq; /*----------------------------------------------------------------------*/ @@ -514,9 +515,7 @@ static int ep_aio_cancel(struct kiocb *iocb) spin_unlock_irqrestore(&aio_lock, flags); return 0; /* ep_aio() will call us again if needed */ } - priv->cancel_state = AIO_UNLINKING; - spin_unlock_irqrestore(&aio_lock, flags); /* * We are called with the aio core holding iocb's context lock. @@ -527,7 +526,9 @@ static int ep_aio_cancel(struct kiocb *iocb) * For this reason, do the dequeue operation in a work routine. */ INIT_WORK(&priv->unlink_work, ep_unlink_worker); - schedule_work(&priv->unlink_work); + queue_work(gadgetfs_wq, &priv->unlink_work); + + spin_unlock_irqrestore(&aio_lock, flags); return 0; } @@ -604,7 +605,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) if (new_req_state == AIO_COMPLETED) { INIT_WORK(&priv->copy_work, ep_user_copy_worker); - schedule_work(&priv->copy_work); + queue_work(gadgetfs_wq, &priv->copy_work); } if (cancel_state != AIO_UNLINKING) { usb_ep_free_request(ep, req); @@ -2255,10 +2256,17 @@ static int __init gadgetfs_init (void) { int status; + gadgetfs_wq = alloc_workqueue("gadgetfs", WQ_PERCPU, 0); + if (!gadgetfs_wq) + return -ENOMEM; + status = register_filesystem (&gadgetfs_type); - if (status == 0) - pr_info ("%s: %s, version " DRIVER_VERSION "\n", - shortname, driver_desc); + if (status) { + destroy_workqueue(gadgetfs_wq); + return status; + } + + pr_info("%s: %s, version " DRIVER_VERSION "\n", shortname, driver_desc); return status; } module_init (gadgetfs_init); @@ -2267,6 +2275,7 @@ static void __exit gadgetfs_cleanup (void) { pr_debug ("unregister %s\n", shortname); unregister_filesystem (&gadgetfs_type); + destroy_workqueue(gadgetfs_wq); } module_exit (gadgetfs_cleanup); From 42411ff1a7bbd1da609cdc18c0f2c43cd39d2b31 Mon Sep 17 00:00:00 2001 From: Jens Glathe Date: Tue, 22 Sep 2026 14:25:29 +0200 Subject: [PATCH 109/158] usb: typec: mux: ps883x: support TYPEC_DP_STATE_F Several docks, including the Lenovo 40B0 and SSK SC220, still send the legacy TYPEC_DP_STATE_F mode request (deprecated since DisplayPort Alt Mode spec version 1.0b). Group it with TYPEC_DP_STATE_D. Assisted-by: Grok(xAI):4.6 Acked-by: Heikki Krogerus Signed-off-by: Jens Glathe Link: https://patch.msgid.link/20260922-ps883x-disable-usb4-v5-1-02c0414978e6@oldschoolsolutions.biz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/ps883x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c index 64e0a61b776a..a5696aef054e 100644 --- a/drivers/usb/typec/mux/ps883x.c +++ b/drivers/usb/typec/mux/ps883x.c @@ -207,6 +207,7 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state switch (state->mode) { case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED; fallthrough; case TYPEC_DP_STATE_C: From d9eadbba6663a5361699310959422a85e128e416 Mon Sep 17 00:00:00 2001 From: Jens Glathe Date: Tue, 22 Sep 2026 14:25:30 +0200 Subject: [PATCH 110/158] usb: typec: mux: ps883x: add a delay after writing config regs The connection-status registers need time to propagate through the analog front-end (PLLs, lane training) after they are written. A 30 ms delay after ps883x_configure() brings DisplayPort Alt Mode hotplug on the Lenovo 40B0 to a reliable success rate. Tested with T14s G6 and IdeaCentre Mini with the 40B0 dock on the HDMI port with a Type-C host cable. Qualcomm firmware that programs this retimer from an MCU uses a similar post-config delay. Assisted-by: Grok(xAI):4.6 Acked-by: Heikki Krogerus Signed-off-by: Jens Glathe Link: https://patch.msgid.link/20260922-ps883x-disable-usb4-v5-2-02c0414978e6@oldschoolsolutions.biz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/ps883x.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c index a5696aef054e..15aee1d613b1 100644 --- a/drivers/usb/typec/mux/ps883x.c +++ b/drivers/usb/typec/mux/ps883x.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -184,6 +185,15 @@ static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0, return ret; } + /* + * The retimer needs time after the connection-status registers are + * written for the analog front-end (PLLs, lane training) to settle. + * Without this, DisplayPort Alt Mode hotplug is unreliable on some + * docks (e.g. Lenovo 40B0). Qualcomm firmware on platforms that + * program this chip from an MCU uses a similar post-config delay. + */ + fsleep(30000); + return 0; } From 647f31f34d4010fa565de284ef4a5bbd099f6575 Mon Sep 17 00:00:00 2001 From: Jens Glathe Date: Tue, 22 Sep 2026 14:25:31 +0200 Subject: [PATCH 111/158] usb: typec: mux: ps883x: disable USB4 on incomplete USB4 platforms On Qualcomm X1E80100 / X1P42100 platforms using the Parade PS883x retimer, hotplugging USB4-capable docks such as the Lenovo 40B0 can result in working USB but no DisplayPort output. When the dock negotiates USB4, the retimer receives TYPEC_MODE_USB4 and forwards it via typec_mux_set(). The qmp-combo PHY then selects USB3-only because no classic DP altmode SVID is present in the state, leaving the DP transmitter and AUX channel disabled. Reject USB4 with -EOPNOTSUPP on platforms whose USB4 / DP-tunneling stack is not ready yet. The Type-C stack then falls back to USB3 + DP Alt Mode. DP altmode configuration continues to use the existing ps883x_set() path. Use a machine-compatible table rather than a DT property so the quirk stays inside the kernel and can be removed later without creating ABI. Assisted-by: Grok(xAI):4.6 Tested-by: Dr. David Alan Gilbert Acked-by: Heikki Krogerus Signed-off-by: Jens Glathe Link: https://patch.msgid.link/20260922-ps883x-disable-usb4-v5-3-02c0414978e6@oldschoolsolutions.biz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/ps883x.c | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c index 15aee1d613b1..2ec57cfe748a 100644 --- a/drivers/usb/typec/mux/ps883x.c +++ b/drivers/usb/typec/mux/ps883x.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,22 @@ #define CONN_STATUS_2_TBT_UNIDIR_LSRX_ACT_LT BIT(4) #define CONN_STATUS_2_USB4_CONNECTED BIT(7) +/* + * Platforms where the USB4 / DP-tunneling stack is not ready yet. Rejecting + * USB4 here lets the Type-C stack fall back to USB3 + DP Alt Mode instead of + * negotiating USB4 and then failing to drive DisplayPort. + * + * This is a temporary, kernel-contained quirk (not DT ABI). Drop the entries + * once the corresponding USB4 support is complete. + */ +static const char * const ps883x_disable_usb4_compats[] = { + "qcom,x1e80100", + "qcom,x1p42100", + "qcom,hamoa", + "qcom,purwa", + NULL, +}; + struct ps883x_retimer { struct i2c_client *client; struct gpio_desc *reset_gpio; @@ -63,8 +80,21 @@ struct ps883x_retimer { enum typec_orientation orientation; bool in_reset; + bool disable_usb4; }; +static bool ps883x_should_disable_usb4(void) +{ + const char * const *compat; + + for (compat = ps883x_disable_usb4_compats; *compat; compat++) { + if (of_machine_is_compatible(*compat)) + return true; + } + + return false; +} + static int ps883x_enable_vregs(struct ps883x_retimer *retimer) { struct device *dev = &retimer->client->dev; @@ -260,6 +290,9 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED; break; case TYPEC_MODE_USB4: + if (retimer->disable_usb4) + return -EOPNOTSUPP; + eudo_data = state->data; cfg2 |= CONN_STATUS_2_USB4_CONNECTED; @@ -389,6 +422,10 @@ static int ps883x_retimer_probe(struct i2c_client *client) retimer->client = client; + retimer->disable_usb4 = ps883x_should_disable_usb4(); + if (retimer->disable_usb4) + dev_info(dev, "USB4 disabled until platform USB4 support is complete\n"); + mutex_init(&retimer->lock); retimer->regmap = devm_regmap_init_i2c(client, &ps883x_retimer_regmap); From 639df5d23876a548b86fe6526bed8b97edf64d96 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 17 Sep 2026 19:07:00 +0200 Subject: [PATCH 112/158] dt-bindings: usb: qcom,snps-dwc3: Document the Nord DWC3 controller Add a compatible for the DWC3 USB controller found on the Qualcomm Nord SoC. Signed-off-by: Bartosz Golaszewski Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260901103300.60803-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo Link: https://patch.msgid.link/20260917-nord-usb-dwc3-dt-v2-1-8f88aa30c434@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 9f91c8711e02..c1b5f87149f7 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -44,6 +44,7 @@ properties: - qcom,msm8994-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 + - qcom,nord-dwc3 - qcom,qcm2290-dwc3 - qcom,qcs404-dwc3 - qcom,qcs615-dwc3 @@ -361,6 +362,7 @@ allOf: enum: - qcom,eliza-dwc3 - qcom,milos-dwc3 + - qcom,nord-dwc3 - qcom,qcm2290-dwc3 - qcom,qcs615-dwc3 - qcom,sar2130p-dwc3 @@ -570,6 +572,7 @@ allOf: - qcom,ipq8064-dwc3 - qcom,kaanapali-dwc3 - qcom,maili-dwc3 + - qcom,nord-dwc3 - qcom,qcs615-dwc3 - qcom,qcs8300-dwc3 - qcom,qdu1000-dwc3 From 96a8479804e3f7af43e639e9df39f562f1a75fd5 Mon Sep 17 00:00:00 2001 From: Jonathan Gopel Date: Sun, 4 Oct 2026 11:13:50 -0600 Subject: [PATCH 113/158] thunderbolt: Do not RPM complete unrelated subtrees on unplug When a Thunderbolt device is unplugged, that switch and every switch subsequent to it on the bus device list is `complete()`d, not just its child devices. In the case of multiple domains, it may cause, eg, an attempt to `complete()` the uninitialized domain 1 root switch RPM completion on unplug of a switch from domain 0 (though ordering is not guaranteed numerically sorted). I initially noticed this issue on a Dell XPS9720 when the system would seem to sporadically not wake after unplugging a Thunderbolt dock. I tried multiple kernel versions including v7.2.7 and v7.3-rc4 to see if that resolved the issue, but the issue persisted. Eventually I found that I could consistently generate a kernel Oops reporting a page fault at 0xffff_ffff_ffff_fff8 on unplug from one side of the laptop and went bug hunting. I became suspicious of the `bus_for_each_dev()` walk that I ultimately ended up changing. To confirm this issue, I did an eBPF trace of `bus_for_each_dev()`, `complete_rpm()`, and `complete()` while unplugging in a variety of situations and found that an unplug from domain 0 tries to `complete_rpm()` domain 1's switch, whose RPM completion is not initialized and thus is holding a null wait-list pointer, triggering the page fault. Here are the key sections of that eBPF trace. Note that HEAD denotes the completion's own wait-list HEAD: ``` RESCAN_ENTER domain=0 COMPLETE rescan_domain=0 target=0-1 target_domain=0 route=0x1 parent=0-0 unplugged=1 COMPLETION done=0 head=HEAD next=HEAD prev=HEAD BUS_WALK domain=0 start=0-1 callback=complete_rpm CALLBACK_ENTER rescan_domain=0 dev=1-0 parent=domain1 is_switch=1 COMPLETE rescan_domain=0 target=1-0 target_domain=1 route=0x0 parent=domain1 unplugged=0 COMPLETION done=0 head=HEAD next=0 prev=0 complete+5 complete_rpm+43 bus_for_each_dev+133 icm_free_unplugged_children+250 icm_rescan_work+42 ``` This patch gates the `complete()` on an additional condition - that the Thunderbolt domain matches the domain of the device that was unplugged. I only have a single system with multiple Thunderbolt domains - the Dell XPS9720. I am consistently able to reproduce the Oops on that system. Since running with this patch, I have not seen the Oops reoccur. I have also tested it on a Thinkpad X1-Gen12, and that system boots and runs well with it, but it only has a single Thunderbolt domain, so it does not exercise the multi-domain failure case. Fixes: 4f7c2e0d8765 ("thunderbolt: Make sure device runtime resume completes before taking domain lock") Cc: stable@vger.kernel.org Signed-off-by: Jonathan Gopel Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 669807f0eaf8..a295a5e6cc9c 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -2072,7 +2072,7 @@ static int complete_rpm(struct device *dev, void *data) { struct tb_switch *sw = tb_to_switch(dev); - if (sw) + if (sw && sw->tb == data) complete(&sw->rpm_complete); return 0; } @@ -2088,8 +2088,8 @@ static void remove_unplugged_switch(struct tb_switch *sw) * tb_switch_remove() calls pm_runtime_get_sync() that then waits * for it. */ - complete_rpm(&sw->dev, NULL); - bus_for_each_dev(&tb_bus_type, &sw->dev, NULL, complete_rpm); + complete_rpm(&sw->dev, sw->tb); + bus_for_each_dev(&tb_bus_type, &sw->dev, sw->tb, complete_rpm); tb_switch_remove(sw); pm_runtime_mark_last_busy(parent); From 648b08aa37c1b24a61662ad78964f2bca659ca26 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 9 Oct 2026 14:29:34 +0200 Subject: [PATCH 114/158] USB: cdc-acm: fix urb leak on disconnect Make sure to drop the reference taken by usb_anchor_urb() when removing URBs from the delayed URB anchor to prevent them from leaking when the device is later disconnected. Fixes: 140cb81ac8c6 ("USB: cdc-acm: fix broken runtime suspend") Reported-by: Wentao Liang Link: https://lore.kernel.org/20260917161217.2161883-1-vulab@iscas.ac.cn/ Cc: stable@vger.kernel.org # 3.16 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20261009122934.464142-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 0d3f815b2f1e..6a1c57463494 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -782,6 +782,7 @@ static void acm_port_shutdown(struct tty_port *port) urb = usb_get_from_anchor(&acm->delayed); if (!urb) break; + usb_put_urb(urb); wb = urb->context; wb->use = false; usb_autopm_put_interface_async(acm->control); @@ -1710,7 +1711,7 @@ static int acm_resume(struct usb_interface *intf) urb = usb_get_from_anchor(&acm->delayed); if (!urb) break; - + usb_put_urb(urb); acm_start_wb(acm, urb->context); } From 5a64bd5ade3d1996717482fb83b3421324d34d36 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 30 Aug 2026 11:10:40 +0300 Subject: [PATCH 115/158] USB: serial: usb_wwan: replace __get_free_page() with kmalloc() usb_wwan_port_probe() allocates the transfer buffers for the read URBs. 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. While on it, allocate IN_BUFLEN bytes rather than a full page. IN_BUFLEN is the transfer_buffer_length of the read URBs, the page allocator was only used because IN_BUFLEN happens to equal PAGE_SIZE on x86. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Johan Hovold --- drivers/usb/serial/usb_wwan.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index a183cc4515ed..a3929d3a3ec1 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -455,7 +455,7 @@ int usb_wwan_port_probe(struct usb_serial_port *port) init_usb_anchor(&portdata->delayed); for (i = 0; i < N_IN_URB; i++) { - buffer = (u8 *)__get_free_page(GFP_KERNEL); + buffer = kmalloc(IN_BUFLEN, GFP_KERNEL); if (!buffer) goto bail_out_error; portdata->in_buffer[i] = buffer; @@ -492,7 +492,7 @@ int usb_wwan_port_probe(struct usb_serial_port *port) bail_out_error: for (i = 0; i < N_IN_URB; i++) { usb_free_urb(portdata->in_urbs[i]); - free_page((unsigned long)portdata->in_buffer[i]); + kfree(portdata->in_buffer[i]); } kfree(portdata); @@ -510,7 +510,7 @@ void usb_wwan_port_remove(struct usb_serial_port *port) for (i = 0; i < N_IN_URB; i++) { usb_free_urb(portdata->in_urbs[i]); - free_page((unsigned long)portdata->in_buffer[i]); + kfree(portdata->in_buffer[i]); } for (i = 0; i < N_OUT_URB; i++) { usb_free_urb(portdata->out_urbs[i]); From 931aad81f3f2d26276fff6b7bda6793267a65725 Mon Sep 17 00:00:00 2001 From: Reinhard Speyerer Date: Fri, 25 Sep 2026 14:56:52 +0200 Subject: [PATCH 116/158] USB: serial: option: add Rolling Wireless RN947R Add Rolling Wireless RN947R 0x9300 compositions: Standard mode: DIAG + ADB + NMEA + MODEM + RMNET + QDSS + ADPL T: Bus=01 Lev=01 Prnt=01 Port=02 Cnt=02 Dev#= 6 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=33f8 ProdID=9300 Rev= 0.00 S: Manufacturer=Rolling Wireless S: Product=RN947R S: SerialNumber=xxxxxxxxxxx C:* #Ifs= 7 Cfg#= 1 Atr=a0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=usbfs E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 8 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan E: Ad=88(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#=21 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=70 Driver=(none) E: Ad=89(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#=22 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=8a(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms QDL mode: T: Bus=01 Lev=01 Prnt=01 Port=02 Cnt=02 Dev#= 12 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=33f8 ProdID=9300 Rev= 0.00 S: Manufacturer=Rolling Wireless S: Product=RN947R S: SerialNumber=xxxxxxxxxxx C:* #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr= 2mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=10 Driver=option E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Signed-off-by: Reinhard Speyerer Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 828b1be3fee2..dc8f459ca399 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2510,6 +2510,10 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x1003, 0xff), /* Rolling RW135R-GL (laptop MBIM) */ .driver_info = RSVD(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x33f8, 0x9300, 0xff, 0xff, 0x30) }, /* Rolling RN947R */ + { USB_DEVICE_AND_INTERFACE_INFO(0x33f8, 0x9300, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x33f8, 0x9300, 0xff, 0xff, 0x10), + .driver_info = ZLP }, { USB_DEVICE_INTERFACE_CLASS(0x3466, 0x3301, 0xff) }, /* TDTECH MT5710-CN */ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Global */ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0x00, 0x40) }, From 87c56085bcdfec305f5c350fe1c58a62a7f65843 Mon Sep 17 00:00:00 2001 From: Michael Wyraz Date: Sun, 4 Oct 2026 11:43:21 +0200 Subject: [PATCH 117/158] USB: serial: option: add support for ASR 1286:4e3c modem The ASR modem used in the Tuoshi LT22M exposes an RNDIS function on interfaces 0 and 1, a diagnostic port on interface 2, and AT ports on interfaces 3 and 4. Match only the vendor-specific serial interfaces so option does not bind to the RNDIS function. Link: https://lore.kernel.org/all/d12635f9-9706-4dbd-8adb-d673c6a77202@wyraz.de/ Signed-off-by: Michael Wyraz Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index dc8f459ca399..65506f6e1502 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2443,6 +2443,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, + { USB_DEVICE_INTERFACE_CLASS(0x1286, 0x4e3c, 0xff) }, /* ASR modem */ { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ From c78dd373b5c56a5a02da591b583c6b48d0d62954 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 9 Oct 2026 14:32:42 +0200 Subject: [PATCH 118/158] USB: serial: usb_wwan: fix urb leak on disconnect Make sure to drop the reference taken by usb_anchor_urb() when removing URBs from the delayed URB anchor to prevent them from leaking when the device is later disconnected. Fixes: 383cedc3bb43 ("USB: serial: full autosuspend support for the option driver") Reported-by: Wentao Liang Link: https://lore.kernel.org/20260916165608.2085613-1-vulab@iscas.ac.cn Cc: stable@vger.kernel.org # 2.6.32 Cc: Oliver Neukum Signed-off-by: Johan Hovold --- drivers/usb/serial/usb_wwan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index a3929d3a3ec1..2c5109ebb317 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -401,6 +401,7 @@ void usb_wwan_close(struct usb_serial_port *port) urb = usb_get_from_anchor(&portdata->delayed); if (!urb) break; + usb_put_urb(urb); unbusy_queued_urb(urb, portdata); usb_autopm_put_interface_async(serial->interface); } @@ -578,6 +579,8 @@ static int usb_wwan_submit_delayed_urbs(struct usb_serial_port *port) if (!urb) break; + usb_put_urb(urb); + err = usb_submit_urb(urb, GFP_ATOMIC); if (err) { dev_err(&port->dev, "%s: submit urb failed: %d\n", From 8f0d16174102863936d583c90258ea02bf06205c Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sat, 10 Oct 2026 15:50:49 +0200 Subject: [PATCH 119/158] usb: core: don't set drvdata to NULL in usb_unbind_interface() usb_unbind_interface() serves as the remove() callback of struct usb_driver and calls usb_set_intfdata(intf, NULL) to clear the bus device private data pointer. However, the driver core code already sets the bus device private data pointer to NULL in device_unbind_cleanup() *after* devres_release_all(), which makes the call redundant. In addition, it can create unexpected NULL pointer dereference scenarios when drivers use managed APIs. int probe(struct usb_interface *intf, const struct usb_device_id *id) { struct data *data; int ret; data = devm_kzalloc(&intf->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; ret = devm_device_add_group(&intf->dev, &foo_attr_group); if (ret) return ret; ... } ssize_t foo_value_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct data *data = usb_get_intfdata(intf); /* Potential NULL pointer dereference */ return sysfs_emit(buf, "%u\n", data->value); } Nothing prevents usb_unbind_interface() to race with foo_value_show() and set usb_set_intfdata(intf, NULL). The same applies to the unwind path of usb_probe_interface(), which also calls usb_set_intfdata(intf, NULL). Besides that, the Rust driver core code manages a driver's bus device private data and destroys it in device_unbind_cleanup(). If usb_unbind_interface() sets the pointer to NULL prematurely, the Rust driver core code sees NULL, and hence skips the destructor of the bus device private data, which leaks all its resources. Thus, drop usb_set_intfdata(intf, NULL) from usb_probe_interface() and usb_unbind_interface(), and move it to usb_driver_release_interface(), which manually calls the remove() callback of struct usb_driver and hence can't rely on the driver core. Cc: stable@kernel.org Fixes: a995fe1a3aa7 ("rust: driver: drop device private data post unbind") Reviewed-by: Johan Hovold Acked-by: Alan Stern Signed-off-by: Danilo Krummrich Link: https://patch.msgid.link/20261010135131.286498-1-dakr@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 3c3bbaf89609..a086b0253691 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -410,7 +410,6 @@ static int usb_probe_interface(struct device *dev) return error; err: - usb_set_intfdata(intf, NULL); intf->needs_remote_wakeup = 0; intf->condition = USB_INTERFACE_UNBOUND; @@ -497,7 +496,6 @@ static int usb_unbind_interface(struct device *dev) } else { intf->needs_altsetting0 = 1; } - usb_set_intfdata(intf, NULL); intf->condition = USB_INTERFACE_UNBOUND; intf->needs_remote_wakeup = 0; @@ -644,6 +642,7 @@ void usb_driver_release_interface(struct usb_driver *driver, } else { device_lock(dev); usb_unbind_interface(dev); + dev_set_drvdata(dev, NULL); dev->driver = NULL; device_unlock(dev); } From b8c063059f304abebc11039195f5b28298aeddbb Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 11 Oct 2026 07:22:38 +0000 Subject: [PATCH 120/158] usb: octeon-hcd: release the USB block when initialization fails cvmx_usb_initialize() gets as far as selecting the reference clock, enabling USBN_CLK_CTL and taking the PHY out of reset (steps 2b-2e) before anything can go wrong, and its only caller frees the HCD and returns without touching the hardware. Both of its error returns therefore leave the USB block clocked and out of reset on a failed probe. Release it on the way out with cvmx_usb_shutdown(), which is the undo the retry path a few lines above already uses. It cannot refuse with -EBUSY here: the pipe lists it checks are initialised by octeon_usb_probe() before cvmx_usb_initialize() is called, and no pipe can have been opened yet. Fixes: 22bce6d2b3fd ("usb: octeon-hcd: fail the probe when the USB core does not respond") Cc: stable Assisted-by: LLM Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20261011072238.2381564-1-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 256e459ff6d8..4d6558e5492c 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -708,6 +708,7 @@ static int cvmx_usb_initialize(struct device *dev, int channel; int divisor; int retries = 0; + int status; union cvmx_usbcx_hcfg usbcx_hcfg; union cvmx_usbnx_clk_ctl usbn_clk_ctl; union cvmx_usbcx_gintsts usbc_gintsts; @@ -872,7 +873,8 @@ static int cvmx_usb_initialize(struct device *dev, if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", usbcx_gahbcfg.u32); - return -ENODEV; + status = -ENODEV; + goto err_shutdown; } usbcx_gahbcfg.u32 = 0; @@ -961,14 +963,27 @@ static int cvmx_usb_initialize(struct device *dev, dev_dbg(dev, "gintsts after reset: 0x%x\n", (int)usbc_gintsts.u32); if (!usbc_gintsts.s.disconnint && !usbc_gintsts.s.prtint) return 0; - if (retries++ >= 5) - return -EAGAIN; + if (retries++ >= 5) { + status = -EAGAIN; + goto err_shutdown; + } dev_info(dev, "controller reset failed (gintsts=0x%x) - retrying\n", (int)usbc_gintsts.u32); msleep(50); cvmx_usb_shutdown(usb); msleep(50); goto retry; + +err_shutdown: + /* + * Steps 2b-2e above selected the reference clock, enabled + * USBN_CLK_CTL and took the PHY out of reset, and the caller only + * frees the HCD, so hand the block back before giving up. The pipe + * lists cvmx_usb_shutdown() checks are initialised by the caller + * before it gets here, so it cannot refuse with -EBUSY. + */ + cvmx_usb_shutdown(usb); + return status; } /** From 68ee2f70d0257aa327ebf8406cd28475574585f2 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 11 Oct 2026 08:05:06 +0000 Subject: [PATCH 121/158] usb: octeon-hcd: keep SOF interrupts while a pipe needs them cvmx_usb_schedule() now sleeps on an hrtimer until the nearest future periodic deadline instead of counting frames down on every SOF. The scan that finds that deadline only looks at pipes with next_tx_frame > usb->frame_number which is right for picking the deadline, but two kinds of pipe that need a SOF to make progress are invisible to the decision: - A periodic pipe that is due and was *not* started: no idle hardware channel this round, its split window is closed (cvmx_usb_find_ready_pipe() rejects both), or another pipe owns usb->active_split. cvmx_usb_next_pipe() looks at the isochronous and interrupt lists only when is_sof is true. - Any pipe that has sent its start split and waits for the frame its split complete may go out in (split_sc_frame, also checked by cvmx_usb_find_ready_pipe()). A control or bulk pipe on a full or low speed device behind a hub is in this state between its two halves, and with SOF masked nothing guarantees an interrupt that brings frame_number there. When some other periodic pipe sets a far deadline - a hub's status pipe, whenever a hub is attached - the driver masks SOF, arms the timer for that deadline and these pipes wait for it. That is hundreds of frames; the code allows up to 8000 (one second) before it falls back to SOF. Before the hrtimer this could not happen: any pipe with a future deadline set need_sof, so SOF stayed enabled every frame and the pipe was retried on the next one. Note such a pipe and keep SOF enabled while there is one, whether or not another pipe has a future deadline. Nothing changes when there is none, which is the case the timer was added for. Other control and bulk pipes are excluded deliberately: their next_tx_frame is not updated on completion, so they are almost always "due" and would keep SOF enabled permanently. Fixes: aebbc7d63407 ("usb: octeon-hcd: sleep on an hrtimer for far-off periodic transfers") Cc: stable Assisted-by: LLM Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20261011080506.2394708-1-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 4d6558e5492c..4929c6a8e733 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -1938,6 +1938,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) struct cvmx_usb_pipe *pipe; int need_sof; u64 min_due; + bool due_now; enum cvmx_usb_transfer ttype; if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { @@ -1979,15 +1980,32 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) */ need_sof = 0; min_due = ~0ull; + due_now = false; for (ttype = CVMX_USB_TRANSFER_CONTROL; ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { + /* + * Do not sleep past a pipe that is only retried from a + * SOF: a periodic one that is due but was not started + * (no idle channel, closed split window, another pipe's + * split active), or any pipe waiting for the frame its + * split complete may go out in. Other control and bulk + * pipes are left out: their next_tx_frame is not updated + * on completion, so they always look due. + */ + if (pipe->split_sc_frame != -1 || + (pipe->next_tx_frame <= usb->frame_number && + (ttype == CVMX_USB_TRANSFER_ISOCHRONOUS || + ttype == CVMX_USB_TRANSFER_INTERRUPT))) + due_now = true; if (pipe->next_tx_frame > usb->frame_number && pipe->next_tx_frame < min_due) min_due = pipe->next_tx_frame; } } - if (min_due != ~0ull) { + if (due_now) { + need_sof = 1; + } else if (min_due != ~0ull) { u64 delta = min_due - usb->frame_number; /* From ddbddee6592ecf6cf6c24d888780f9ad45a3e30b Mon Sep 17 00:00:00 2001 From: Drobyshev Vladimir Date: Tue, 29 Sep 2026 20:38:33 +0300 Subject: [PATCH 122/158] USB: cdc-acm: add VeriFone VX820 device ID Add USB ID 11ca:0219 for the VeriFone VX820 to the cdc-acm device table and apply the SINGLE_RX_URB quirk. Signed-off-by: Drobyshev Vladimir Link: https://patch.msgid.link/20260929173833.105208-1-enigma_137@icloud.com Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 6a1c57463494..489575d40bd1 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1819,6 +1819,9 @@ static const struct usb_device_id acm_ids[] = { { USB_DEVICE(0x11ca, 0x0201), /* VeriFone Mx870 Gadget Serial */ .driver_info = SINGLE_RX_URB, }, + { USB_DEVICE(0x11ca, 0x0219), /* VeriFone Vx820 Gadget Serial */ + .driver_info = SINGLE_RX_URB, + }, { USB_DEVICE(0x1901, 0x0006), /* GE Healthcare Patient Monitor UI Controller */ .driver_info = DISABLE_ECHO, /* DISABLE ECHO in termios flag */ }, From 40224df29729618ecd88c5c0077e71b1e82c7a3a Mon Sep 17 00:00:00 2001 From: Ali Resool Tuama Date: Fri, 2 Oct 2026 20:42:12 +0300 Subject: [PATCH 123/158] USB: quirks: add USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE for GameSir G7 SE The GameSir G7 SE wired controller first enumerates as 3537:10a0 and then re-enumerates in a mode it picks from how the host enumerated it. When the configuration descriptor is fetched the Linux way, 9 bytes followed by the full 80, the controller disconnects about 1 ms after its string descriptors have been read, before SET_CONFIGURATION, and comes back as 3537:1082, a generic HID gamepad without force feedback. When the configuration descriptor is fetched in one 255-byte request, as Windows does, it comes back as 3537:1010 in Xbox One (GIP) mode, which xpad already supports, including rumble. Skipping the device qualifier requests (USB_QUIRK_DEVICE_QUALIFIER) makes no difference; the configuration descriptor request size alone decides the mode. Add USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE for the 10a0 identity, as is already done for the ShanWan Wireless Gamepad. Cc: stable Assisted-by: claude-opus-5-5 Signed-off-by: Ali Resool Tuama Link: https://patch.msgid.link/20261002-master-v2-1-24b05d8792b5@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index b5b577f0b931..a4bcbe9c913c 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -612,6 +612,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* ezcap401 - BOS descriptor fetch hangs at SuperSpeed Plus */ { USB_DEVICE(0x32ed, 0x0401), .driver_info = USB_QUIRK_NO_BOS }, + /* GameSir G7 SE */ + { USB_DEVICE(0x3537, 0x10a0), .driver_info = USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE }, + /* DELL USB GEN2 */ { USB_DEVICE(0x413c, 0xb062), .driver_info = USB_QUIRK_NO_LPM | USB_QUIRK_RESET_RESUME }, From 7c590f0716ed3c9ae9809dbc6d488d76c19fdf52 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Thu, 1 Oct 2026 07:46:08 +0800 Subject: [PATCH 124/158] usb: typec: ucsi: Skip CAM query when partner has no alt modes Since Thunderbolt alternate mode support was added, `ucsi_altmode_update_active()` is called on every Connector Partner Changed event. This made the driver send `UCSI_GET_CURRENT_CAM` to PPMs even when the partner had no alternate modes registered. Firmware that does not support the optional alternate mode details answered Not Supported, which produced a spurious error on every partner change: ``` ucsi_acpi USBC000:00: GET_CURRENT_CAM command failed ``` Fixed by skipping the command when there is nothing to update. Fixes: da87d45b1951 ("usb: typec: ucsi: Add Thunderbolt alternate mode support") Assisted-by: Codex:deepseek-v4.1-flash Cc: stable Signed-off-by: Gris Ge Reviewed-by: Andrei Kuchynski Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260930234611.468765-2-cnfourt@gmail.com 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 a5479e488591..5aab8ab9f013 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1519,7 +1519,8 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (con->partner && (change & UCSI_CONSTAT_PARTNER_CHANGE)) { ucsi_partner_change(con); - ucsi_altmode_update_active(con); + if (con->partner_altmode[0]) + ucsi_altmode_update_active(con); /* Complete pending data role swap */ if (!completion_done(&con->complete)) From 02ee864e8d2142c5648998944c3306ea6a763c26 Mon Sep 17 00:00:00 2001 From: Seungjin Bae Date: Sat, 3 Oct 2026 17:55:20 -0400 Subject: [PATCH 125/158] usb: typec: ucsi: ccg: Validate altmode index in GET_CURRENT_CAM response When the PPM reports more than one DisplayPort alternate mode for a connector, ucsi_ccg_update_altmodes() merges them into a single entry in uc->updated[] and sets uc->has_multiple_dp. In that case, ucsi_ccg_update_get_current_cam_cmd() rewrites the response of the GET_CURRENT_CAM command. The response is a single byte holding the index of the currently active alternate mode, and it is provided by the PPM firmware. The function uses this byte directly as an index into uc->orig[], and then uses the linked_idx read from that entry as the translated index into uc->updated[]. Both arrays have UCSI_MAX_ALTMODES entries, but neither index is checked against that size. If a malicious or buggy PPM reports a value of UCSI_MAX_ALTMODES or larger, e.g. 0xFF, uc->orig[cam].linked_idx reads over the end of uc->orig[]. The byte read from there is then used as the index for writing cam into uc->updated[new_cam].active_idx, so the out-of-bounds read is followed by an out-of-bounds write. This happens without any userspace action, since the UCSI core issues GET_CURRENT_CAM on its own when handling connector changes. Fix this by rejecting responses whose index is out of range, printing an error and returning -EPROTO, so that the caller cannot accept this. Also check linked_idx before using it as an index, so that the write into uc->updated[] is always within bounds. The response is now only processed when the command completed without an error. ucsi_sync_control_common() only reads the response when the CCI reports command completion, so otherwise the buffer is not filled and must not be mistaken for an invalid index. This bug was found with a Python script that traces where firmware input is used. Fixes: 170a6726d0e2 ("usb: typec: ucsi: add support for separate DP altmode devices") Cc: stable Reported-by: Nathan Rebello Assisted-by: LLM Signed-off-by: Seungjin Bae Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20261003215520.611083-2-eeodqql09@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_ccg.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 91c2958a708c..80e3e84b418d 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -384,14 +384,28 @@ static int ucsi_ccg_init(struct ucsi_ccg *uc) return -ETIMEDOUT; } -static void ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data) +static int ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data) { u8 cam, new_cam; cam = data[0]; + if (cam >= UCSI_MAX_ALTMODES) { + dev_err(uc->dev, "PPM returned invalid alternate mode index %u\n", + cam); + return -EPROTO; + } + new_cam = uc->orig[cam].linked_idx; + if (new_cam >= UCSI_MAX_ALTMODES) { + dev_err(uc->dev, "invalid linked alternate mode index %u for %u\n", + new_cam, cam); + return -EPROTO; + } + uc->updated[new_cam].active_idx = cam; data[0] = new_cam; + + return 0; } static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi, @@ -636,8 +650,10 @@ static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, switch (UCSI_COMMAND(command)) { case UCSI_GET_CURRENT_CAM: - if (uc->has_multiple_dp) - ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)data); + if (!ret && uc->has_multiple_dp && + (*cci & UCSI_CCI_COMMAND_COMPLETE) && + !(*cci & UCSI_CCI_ERROR)) + ret = ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)data); break; case UCSI_GET_ALTERNATE_MODES: if (UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_SOP) { From 24d2a690fb83c40913c0b3f38151ae3997a5898f Mon Sep 17 00:00:00 2001 From: Henry Tseng Date: Fri, 9 Oct 2026 18:16:14 +0300 Subject: [PATCH 126/158] usb: xhci: return an error if the host is not halted xhci_reset() returns 0 when the host is not halted, without ever writing CMD_RESET. Every other path that fails to reset the host returns an error, so callers that check the return value are told the reset succeeded on the one path where it did not happen. Return -EBUSY when the reset is aborted because the host is not halted. Signed-off-by: Henry Tseng Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-2-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 a9e47e178c28..2af6a7b91e55 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -195,7 +195,7 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us) if ((state & STS_HALT) == 0) { xhci_warn(xhci, "Host controller not halted, aborting reset.\n"); - return 0; + return -EBUSY; } xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Reset the HC"); From bbb013bc73bd32d95d2aa3f130db323336759c2a Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Fri, 9 Oct 2026 18:16:15 +0300 Subject: [PATCH 127/158] usb: xhci: Unlock for command abort polling xhci_abort_cmd_ring() requests abort, waits for the CRR bit to clear, drops xhci->lock and waits for the Command Ring Stopped event. The CRR wait timeout is 5 seconds as suggested by xHCI 4.6.1.2, which means that if the xHC fails to complete the operation at all, we poll with the lock held and IRQs disabled for several seconds. If any other CPU tries to acquire the lock, it will spin likewise. IRQs get delays, drivers log errors, tasks freeze, it's a mess. So drop the lock earlier, before waiting for the CRR bit. It should be safe - the sole caller sets cmd_ring_state to CMD_RING_STATE_ABORTED before calling us, which will prevent others from ringing the command doorbell and interfering with the abort. Queuing new commands during this time poses no danger, and if the command we try to abort actually completes concurrently, existing code already needs to deal with this. And in my testing it does - it's trivial to trigger this on ASM1042, where Address Device can't be aborted, but it completes as soon as the offending device is unplugged, including during abort attempt. Note that the lock still covers reinit_completion(), so it won't race with complete() being called by the event handler. And works are not reentrant, so another timeout can't expire while the lock is dropped. We will configure timeout anew when restarting the ring. One other difference is that now we also drop the lock if abort fails. This too should be harmless. Commands queued during this time will be released like any other pending commands. If the aborted command does complete before we regain the lock, it's a waste, but not regression. Reported-by: Pedro Fonseca Link: https://lore.kernel.org/linux-usb/16f65081-5a3c-4c30-9811-9017796a3373@fonseca.com.pt/ Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index ec278a9f9540..82dd93c2afdd 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -494,7 +494,7 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags) struct xhci_segment *new_seg = xhci->cmd_ring->deq_seg; union xhci_trb *new_deq = xhci->cmd_ring->dequeue; u64 crcr; - int ret; + int ret, completed; xhci_dbg(xhci, "Abort command ring\n"); @@ -521,25 +521,27 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags) * In the future we should distinguish between -ENODEV and -ETIMEDOUT * and try to recover a -ETIMEDOUT with a host controller reset. */ + spin_unlock_irqrestore(&xhci->lock, flags); ret = xhci_handshake(&xhci->op_regs->cmd_ring, CMD_RING_RUNNING, 0, 5 * 1000 * 1000); - if (ret < 0) { - xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret); - xhci_halt(xhci); - xhci_hc_died(xhci); - return ret; - } /* * Writing the CMD_RING_ABORT bit should cause a cmd completion event, * however on some host hw the CMD_RING_RUNNING bit is correctly cleared * but the completion event in never sent. Wait 2 secs (arbitrary * number) to handle those cases after negation of CMD_RING_RUNNING. */ - spin_unlock_irqrestore(&xhci->lock, flags); - ret = wait_for_completion_timeout(&xhci->cmd_ring_stop_completion, + if (ret >= 0) + completed = wait_for_completion_timeout(&xhci->cmd_ring_stop_completion, msecs_to_jiffies(2000)); spin_lock_irqsave(&xhci->lock, flags); - if (!ret) { + + if (ret < 0) { + xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret); + xhci_halt(xhci); + xhci_hc_died(xhci); + return ret; + } + if (!completed) { xhci_dbg(xhci, "No stop event for abort, ring start fail?\n"); xhci_cleanup_command_queue(xhci); } else { From d7458590f585aea58f24047195f163adc6ecb045 Mon Sep 17 00:00:00 2001 From: Zain Aboobacker Date: Fri, 9 Oct 2026 18:16:16 +0300 Subject: [PATCH 128/158] usb: xhci: fix typos in comments Fix various spelling mistakes in comments found by codespell. Assisted-by: Claude:claude-opus-5-5 codespell Signed-off-by: Zain Aboobacker Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 14 +++++++------- drivers/usb/host/xhci.c | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 82dd93c2afdd..cbce9f8f07fa 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -243,7 +243,7 @@ static void inc_enq_past_link(struct xhci_hcd *xhci, struct xhci_ring *ring, u32 * fixed in the 0.96 specification errata, but we have to assume that all 0.95 * xHCI hardware can't handle the chain bit being cleared on a link TRB. * - * On 0.95 and some 0.96 HCs the chain bit is set once at segment initalization + * On 0.95 and some 0.96 HCs the chain bit is set once at segment initialization * and never changed here. On all others, modify it as requested by the caller. */ if (!xhci_link_chain_quirk(xhci, ring->type)) { @@ -663,7 +663,7 @@ struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, * Get the hw dequeue pointer xHC stopped on, either directly from the * endpoint context, or if streams are in use from the stream context. * The returned hw_dequeue contains the lowest four bits with cycle state - * and possbile stream context type. + * and possible stream context type. */ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, unsigned int ep_index, unsigned int stream_id) @@ -991,7 +991,7 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, int err; /* - * Avoid resetting endpoint if link is inactive or device disonnected. + * Avoid resetting endpoint if link is inactive or device disconnected. * Can cause host hang. * Device will be reset to recover an inactive link, so don't do anything */ @@ -1386,7 +1386,7 @@ static void xhci_kill_endpoint_urbs(struct xhci_hcd *xhci, * held for the URBs to finish during device disconnect, blocking host remove. * * Call with xhci->lock held. - * lock is relased and re-acquired while giving back urb. + * lock is released and re-acquired while giving back urb. */ void xhci_hc_died(struct xhci_hcd *xhci) { @@ -1971,7 +1971,7 @@ static void handle_device_notification(struct xhci_hcd *xhci, } /* - * Quirk hanlder for errata seen on Cavium ThunderX2 processor XHCI + * Quirk handler for errata seen on Cavium ThunderX2 processor XHCI * Controller. * As per ThunderX2errata-129 USB 2 device may come up as USB 1 * If a connection to a USB 1 device is followed by another connection @@ -3089,7 +3089,7 @@ static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir) /* * Handle all OS-owned events on an interrupter event ring. It may drop - * and reaquire xhci->lock between event processing. + * and reacquire xhci->lock between event processing. */ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool skip_events) @@ -3803,7 +3803,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* * If next available TRB is the Link TRB in the ring segment then * enqueue a No Op TRB, this can prevent the Setup and Data Stage - * TRB to be breaked by the Link TRB. + * TRB to be broken by the Link TRB. */ if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->enqueue + 1)) { field = TRB_TYPE(TRB_TR_NOOP) | ep_ring->cycle_state; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2af6a7b91e55..9564dde8bb34 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -406,7 +406,7 @@ static void compliance_mode_recovery(struct timer_list *t) * The quirk creates a timer that polls every 2 seconds the link state of * each host controller's port and recovers it by issuing a Warm reset * if Compliance mode is detected, otherwise the port will become "dead" (no - * device connections or disconnections will be detected anymore). Becasue no + * device connections or disconnections will be detected anymore). Because no * status event is generated when entering compliance mode (per xhci spec), * this quirk is needed on systems that have the failing hardware installed. */ @@ -796,7 +796,7 @@ static void xhci_save_registers(struct xhci_hcd *xhci) xhci->s3.config_reg = readl(&xhci->op_regs->config_reg); /* save both primary and all secondary interrupters */ - /* fixme, shold we lock to prevent race with remove secondary interrupter? */ + /* fixme, should we lock to prevent race with remove secondary interrupter? */ for (i = 0; i < xhci->max_interrupters; i++) { ir = xhci->interrupters[i]; if (!ir) @@ -2046,7 +2046,7 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, /* * Configuration and alternate setting changes must be done in - * process context, not interrupt context (or so documenation + * process context, not interrupt context (or so documentation * for usb_set_interface() and usb_set_configuration() claim). */ if (xhci_endpoint_init(xhci, virt_dev, udev, ep, GFP_NOIO) < 0) { @@ -3302,7 +3302,7 @@ static void xhci_endpoint_disable(struct usb_hcd *hcd, * state. For software that wishes to reset the data toggle or sequence number * of an endpoint that isn't in the halted state this function will issue a * configure endpoint command with the Drop and Add bits set for the target - * endpoint. Refer to the additional note in xhci spcification section 4.6.8. + * endpoint. Refer to the additional note in xhci specification section 4.6.8. * * vdev may be lost due to xHC restore error and re-initialization during S3/S4 * resume. A new vdev will be allocated later by xhci_discover_or_reset_device() @@ -4296,7 +4296,7 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) pm_runtime_get_noresume(hcd->self.controller); /* Is this a LS or FS device under a HS hub? */ - /* Hub or peripherial? */ + /* Hub or peripheral? */ return 1; disable_slot: @@ -4512,7 +4512,7 @@ static int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev) /* * Transfer the port index into real index in the HW port status - * registers. Caculate offset between the port's PORTSC register + * registers. Calculate offset between the port's PORTSC register * and port status base. Divide the number of per port register * to get the real index. The raw port number bases 1. */ @@ -5394,7 +5394,7 @@ static void xhci_hcd_init_usb3_data(struct xhci_hcd *xhci, struct usb_hcd *hcd) /* * Early xHCI 1.1 spec did not mention USB 3.1 capable hosts * should return 0x31 for sbrn, or that the minor revision - * is a two digit BCD containig minor and sub-minor numbers. + * is a two digit BCD containing minor and sub-minor numbers. * This was later clarified in xHCI 1.2. * * Some USB 3.1 capable hosts therefore have sbrn 0x30, and From e3e3a104759392c8641f60b2de1f4231aba99e46 Mon Sep 17 00:00:00 2001 From: Hongyu Xie Date: Fri, 9 Oct 2026 18:16:17 +0300 Subject: [PATCH 129/158] xhci: check device notification type before forwarding wake event The xHCI driver programs the Device Notification Control register to only enable the Function Wake device notification (N1), so any Device Notification Event TRB received is expected to be a function wake notification. handle_device_notification() does however not check the Notification Type field of the event (xHCI 1.2 section 6.4.2.7, DW0 bits 7:4), and forwards every device notification event as a function wake. A host controller that delivers an unexpected notification type (e.g. due to broken firmware or emulation) would trigger a spurious wake notification on the parent hub. Parse the notification type and drop events other than Function Wake with a warning, mirroring the slot ID validation in the same function. DEV_NOTE_FWAKE is the DNCTRL register bit for notification type 1 (N1), while the event TRB carries the notification type value itself, so add a separate DEV_NOTE_TYPE_FWAKE constant for the comparison. [mn:] reduce warning to a debug message Signed-off-by: Hongyu Xie Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 9 +++++++++ drivers/usb/host/xhci.h | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index cbce9f8f07fa..7f480db2983e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1954,6 +1954,7 @@ static void handle_device_notification(struct xhci_hcd *xhci, union xhci_trb *event) { u32 slot_id; + u32 type; struct usb_device *udev; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->generic.field[3])); @@ -1963,6 +1964,14 @@ static void handle_device_notification(struct xhci_hcd *xhci, return; } + /* xHCI 1.2 6.4.2.7: Notification Type is DW0 bits 7:4 */ + type = TRB_TO_DEV_NOTE_TYPE(le32_to_cpu(event->generic.field[0])); + if (type != DEV_NOTE_TYPE_FWAKE) { + xhci_dbg(xhci, "Unsupported device notification type %u for slot ID %u\n", + type, slot_id); + return; + } + xhci_dbg(xhci, "Device Wake Notification event for slot ID %u\n", slot_id); udev = xhci->devs[slot_id]->udev; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c7bfa7f028d3..ec4bfeb4887c 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -187,6 +187,8 @@ struct xhci_op_regs { * SW does need to pay attention to function wake notifications. */ #define DEV_NOTE_FWAKE BIT(1) +/* Notification Type value carried by a Device Notification Event TRB (6.4.2.7) */ +#define DEV_NOTE_TYPE_FWAKE 1 /* CRCR - Command Ring Control Register - cmd_ring bitmasks */ /* bit 0 - Cycle bit indicates the ownership of the command ring */ @@ -996,6 +998,9 @@ enum xhci_ep_reset_type { #define TRB_TO_PACKET_TYPE(p) ((p) & 0x1f) #define TRB_TO_ROOTHUB_PORT(p) (((p) & (0xff << 24)) >> 24) +/* Device Notification Event TRB fields, 6.4.2.7 */ +#define TRB_TO_DEV_NOTE_TYPE(p) (((p) & (0xf << 4)) >> 4) + enum xhci_setup_dev { SETUP_CONTEXT_ONLY, SETUP_CONTEXT_ADDRESS, From 47f4e983b5de7616fb11d5f537fc9a605387d136 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Fri, 9 Oct 2026 18:16:18 +0300 Subject: [PATCH 130/158] xhci: dbc: lock the minor IDR on registration failure dbc_tty_minors is protected by dbc_tty_minors_lock when entries are allocated and during normal device removal. The registration error path removes an entry without taking that lock. Different DbC instances have separate event work items, so this removal can race with an IDR update for another instance. Take the same mutex around the error-path removal. Fixes: e1ec140f273e ("xhci: dbgtty: use IDR to support several dbc instances.") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-dbgtty.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 3d51e8d82659..2249cc16800c 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -535,7 +535,9 @@ static int xhci_dbc_tty_register_device(struct xhci_dbc *dbc) err_free_fifo: kfifo_free(&port->port.xmit_fifo); err_exit_port: + mutex_lock(&dbc_tty_minors_lock); idr_remove(&dbc_tty_minors, port->minor); + mutex_unlock(&dbc_tty_minors_lock); err_idr: xhci_dbc_tty_exit_port(port); From 535faefca4029de000a4f76b7025e683e918f413 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Fri, 9 Oct 2026 18:16:19 +0300 Subject: [PATCH 131/158] usb: xhci: Guarantee URB giveback on Ring Underrun/Overrun In this case we know that the xHC has released ownership of all missed TDs, we only don't know which were missed and which were queued later. URBs are queued atomically, so we can safely give back all TDs of the currently executing URB. Unlike the previous policy, this does actually ensure that the class driver will learn about the error and won't see all of its URBs still in progress when all TDs are missed on xHCI 1.0. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 7f480db2983e..8b915a1d5b25 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2649,6 +2649,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, unsigned int slot_id; int ep_index; struct xhci_td *td = NULL; + struct urb *missed_urb = NULL; dma_addr_t ep_trb_dma; union xhci_trb *ep_trb; int status = -EINPROGRESS; @@ -2867,26 +2868,31 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID) return 0; + /* + * If skip flag is still set at xrun, we are on xHCI 1.0 and our TRB + * pointer is zero again. All missed TDs can be given back, but we + * don't know which were missed and which were queued after the xrun + * occurred. We can safely give back the first pending URB. + */ + if (ring_xrun_event) { + if (!missed_urb) + missed_urb = td->urb; + + if (td->urb != missed_urb) { + xhci_dbg(xhci, "Skipped one URB for slot %u ep %u", + slot_id, ep_index); + return 0; + } + } + /* * TD was missed, skip it. Core already initialized frame->status * to -EXDEV and frame->actual_length to 0, nothing more to do. */ xhci_dequeue_td(xhci, td, ep_ring, 0); - if (!list_empty(&ep_ring->td_list)) { - if (ring_xrun_event) { - /* - * If we are here, we are on xHCI 1.0 host with no - * idea how many TDs were missed or where the xrun - * occurred. New TDs may have been added after the - * xrun, so skip only one TD to be safe. - */ - xhci_dbg(xhci, "Skipped one TD for slot %u ep %u", - slot_id, ep_index); - return 0; - } + if (!list_empty(&ep_ring->td_list)) continue; - } xhci_dbg(xhci, "All TDs skipped for slot %u ep %u. Clear skip flag.\n", slot_id, ep_index); From 6814c2152d5dd385165f03da0d9d0f7bca918df9 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Fri, 9 Oct 2026 18:16:20 +0300 Subject: [PATCH 132/158] usb: xhci: Don't set the skip flag on non-isoc endpoints These events are unique to isochronous endpoints, ignore them otherwise. Update debug messages to reflect new policies. We could also log invalid events as errors, but it seems nobody has ever had problems with that, so don't bother. This allows dropping the isoc check when skipping TDs. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 8b915a1d5b25..2dd11732bb87 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2778,16 +2778,18 @@ static int handle_tx_event(struct xhci_hcd *xhci, * Set skip flag of the ep_ring; Complete the missed tds as * short transfer when process the ep_ring next time. */ - ep->skip = true; + if (ep_ring->type == TYPE_ISOC) + ep->skip = true; xhci_dbg(xhci, - "Miss service interval error for slot %u ep %u, set skip flag%s\n", - slot_id, ep_index, ep_trb_dma ? ", skip now" : ""); + "Missed Service Error for slot %u ep %u, skip %d, try now %d\n", + slot_id, ep_index, ep->skip, !!ep_trb_dma); break; case COMP_NO_PING_RESPONSE_ERROR: - ep->skip = true; + if (ep_ring->type == TYPE_ISOC) + ep->skip = true; xhci_dbg(xhci, - "No Ping response error for slot %u ep %u, Skip one Isoc TD\n", - slot_id, ep_index); + "No Ping response error for slot %u ep %u, skip %d\n", + slot_id, ep_index, ep->skip); return 0; case COMP_INCOMPATIBLE_DEVICE_ERROR: @@ -2863,7 +2865,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, /* Is this TRB not part of the currently executing TD? */ if (!trb_in_td(td, ep_trb_dma)) { - if (ep->skip && usb_endpoint_xfer_isoc(&td->urb->ep->desc)) { + if (ep->skip) { /* this event is unlikely to match any TD, don't skip them all */ if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID) return 0; From f7845f604ea111ab097db0876dd1a8a433ef018e Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Fri, 9 Oct 2026 18:16:21 +0300 Subject: [PATCH 133/158] usb: xhci: Shorten the TD skipping loop Half of this loop is code which only executes once to deal with cases where no TD matches the event and then it returns. This code needs not to be in any kind of loop, so get it out. Optimize conditionals remaining in the loop body. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 68 +++++++++++++++++------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 2dd11732bb87..7597ef8105c6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2862,10 +2862,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, td = list_first_entry(&ep_ring->td_list, struct xhci_td, td_list); - /* Is this TRB not part of the currently executing TD? */ - if (!trb_in_td(td, ep_trb_dma)) { + if (ep->skip) { - if (ep->skip) { + if (!trb_in_td(td, ep_trb_dma)) { /* this event is unlikely to match any TD, don't skip them all */ if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID) return 0; @@ -2903,38 +2902,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, goto check_endpoint_halted; } - /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */ - if (ring_xrun_event) - return 0; - - /* - * Skip the Force Stopped Event. The 'ep_trb' of FSE is not in the current - * TD pointed by 'ep_ring->dequeue' because that the hardware dequeue - * pointer still at the previous TRB of the current TD. The previous TRB - * maybe a Link TD or the last TRB of the previous TD. The command - * completion handle will take care the rest. - */ - if (trb_comp_code == COMP_STOPPED || - trb_comp_code == COMP_STOPPED_LENGTH_INVALID) { - return 0; - } - - /* - * Some hosts give a spurious success event after a short - * transfer or error on last TRB. Ignore it. - */ - if (xhci_spurious_success_tx_event(xhci, ep_ring)) { - xhci_dbg(xhci, "Spurious event dma %pad, comp_code %u after %u\n", - &ep_trb_dma, trb_comp_code, ep_ring->old_trb_comp_code); - ep_ring->old_trb_comp_code = 0; - return 0; - } - - /* HC is busted, give up! */ - goto debug_finding_td; - } - - if (ep->skip) { xhci_dbg(xhci, "Found td. Clear skip flag for slot %u ep %u.\n", slot_id, ep_index); @@ -2949,6 +2916,37 @@ static int handle_tx_event(struct xhci_hcd *xhci, */ } while (ep->skip); + /* Handle events not referencing the current TD */ + if (!trb_in_td(td, ep_trb_dma)) { + /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */ + if (ring_xrun_event) + return 0; + + /* + * Skip the Force Stopped Event. The 'ep_trb' of FSE is not in the current + * TD pointed by 'ep_ring->dequeue' because that the hardware dequeue + * pointer still at the previous TRB of the current TD. The previous TRB + * maybe a Link TD or the last TRB of the previous TD. The command + * completion handle will take care the rest. + */ + if (trb_comp_code == COMP_STOPPED || trb_comp_code == COMP_STOPPED_LENGTH_INVALID) + return 0; + + /* + * Some hosts give a spurious success event after a short + * transfer or error on last TRB. Ignore it. + */ + if (xhci_spurious_success_tx_event(xhci, ep_ring)) { + xhci_dbg(xhci, "Spurious event dma %pad, comp_code %u after %u\n", + &ep_trb_dma, trb_comp_code, ep_ring->old_trb_comp_code); + ep_ring->old_trb_comp_code = 0; + return 0; + } + + /* HC is busted, give up! */ + goto debug_finding_td; + } + ep_ring->old_trb_comp_code = trb_comp_code; /* Get out if a TD was queued at enqueue after the xrun occurred */ From 0c6d9901ea97bda748f63882ee75ae26f90043d3 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Fri, 9 Oct 2026 18:16:22 +0300 Subject: [PATCH 134/158] usb: xhci: Rework and improve the TD matching and skipping logic Matching events with TDs and giving back missed TDs is carried out by a complicated loop. Replace it with a simpler linear logic: 0. Having verified that 'td_list' isn't empty, 1. Scan it to find the matching TD and count missed TDs, 2. Perform necessary adjustments for corner cases, 3. Give back missed TDs, if applicable, using a short and tidy loop, 4. Check if the event refers to the expected TD and proceed as usual. Besides cleaning up the code, this provides a few improvements: - when the skip flag is set, no TD is given back unless we found a match or otherwise know how many TDs should be given back - when the skip flag is clear, we know if the event refers to a "future" TD so we can log this in the Scary Error Message to aid debugging. While altering the error message, drop a pointless goto. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 141 ++++++++++++++++------------------- 1 file changed, 63 insertions(+), 78 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 7597ef8105c6..243b1fd2b2f6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -127,11 +127,16 @@ static bool link_trb_toggles_cycle(union xhci_trb *trb) return le32_to_cpu(trb->link.control) & LINK_TOGGLE; } +static int num_tds_not_done(struct urb *urb) +{ + struct urb_priv *urb_priv = urb->hcpriv; + + return urb_priv->num_tds - urb_priv->num_tds_done; +} + static bool last_td_in_urb(struct xhci_td *td) { - struct urb_priv *urb_priv = td->urb->hcpriv; - - return urb_priv->num_tds_done == urb_priv->num_tds; + return !num_tds_not_done(td->urb); } static bool unhandled_event_trb(struct xhci_ring *ring) @@ -2624,14 +2629,19 @@ 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) +static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, int *missed_tds, dma_addr_t dma) { struct xhci_td *td; - if (dma) + if (dma) { list_for_each_entry(td, &ep_ring->td_list, td_list) if (trb_in_td(td, dma)) return td; + else + (*missed_tds)++; + } + + *missed_tds = 0; return NULL; } @@ -2648,8 +2658,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, struct xhci_ring *ep_ring; unsigned int slot_id; int ep_index; - struct xhci_td *td = NULL; - struct urb *missed_urb = NULL; + struct xhci_td *td; + int missed_tds = 0; dma_addr_t ep_trb_dma; union xhci_trb *ep_trb; int status = -EINPROGRESS; @@ -2832,13 +2842,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_dequeue_td(xhci, td, ep_ring, td->status); } - /* - * 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)) { /* * Don't print wanings if ring is empty due to a stopped endpoint generating an @@ -2858,66 +2861,50 @@ static int handle_tx_event(struct xhci_hcd *xhci, goto check_endpoint_halted; } - do { - td = list_first_entry(&ep_ring->td_list, struct xhci_td, - td_list); + td = find_td_by_dma(ep_ring, &missed_tds, ep_trb_dma); - if (ep->skip) { - - if (!trb_in_td(td, ep_trb_dma)) { - /* this event is unlikely to match any TD, don't skip them all */ - if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID) - return 0; - - /* - * If skip flag is still set at xrun, we are on xHCI 1.0 and our TRB - * pointer is zero again. All missed TDs can be given back, but we - * don't know which were missed and which were queued after the xrun - * occurred. We can safely give back the first pending URB. - */ - if (ring_xrun_event) { - if (!missed_urb) - missed_urb = td->urb; - - if (td->urb != missed_urb) { - xhci_dbg(xhci, "Skipped one URB for slot %u ep %u", - slot_id, ep_index); - return 0; - } - } - - /* - * TD was missed, skip it. Core already initialized frame->status - * to -EXDEV and frame->actual_length to 0, nothing more to do. - */ - xhci_dequeue_td(xhci, td, ep_ring, 0); - - if (!list_empty(&ep_ring->td_list)) - continue; - - xhci_dbg(xhci, "All TDs skipped for slot %u ep %u. Clear skip flag.\n", - slot_id, ep_index); - ep->skip = false; - td = NULL; - goto check_endpoint_halted; - } - - xhci_dbg(xhci, - "Found td. Clear skip flag for slot %u ep %u.\n", - slot_id, ep_index); - ep->skip = false; + if (ep->skip) { + if (!td) { + /* + * xHCI 1.0 allowed MSE events to have zero TRB pointers. Some old chips + * also generate bogus non-zero pointers. We know, don't bother warning. + * Missed TDs will be given back by the next event with a valid pointer. + */ + if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && + xhci->hci_version <= 0x100) + return 0; + /* + * If skip flag is still set at xrun, we are on xHCI 1.0 and our TRB pointer + * is zero again. All missed TDs can be given back, but we don't know which + * were missed and which were queued after the xrun occurred. We can safely + * give back the first pending URB to let the class driver know. + */ + if (ring_xrun_event) + missed_tds = num_tds_not_done(list_first_entry(&ep_ring->td_list, + struct xhci_td, td_list)->urb); + /* In other cases missed_tds is zero */ } - /* - * If ep->skip is set, it means there are missed tds on the - * endpoint ring need to take care of. - * Process them as short transfer until reach the td pointed by - * the event. - */ - } while (ep->skip); + /* + * Give back missed TDs. Core already initialized their frame->status to -EXDEV + * and frame->actual_length to 0, nothing more to do. + */ + for (int i = 0; i < missed_tds; i++) + xhci_dequeue_td(xhci, + list_first_entry(&ep_ring->td_list, struct xhci_td, td_list), + ep_ring, 0); + + /* the list may become empty on ring_xrun_event */ + if (td || list_empty(&ep_ring->td_list)) + ep->skip = false; + + xhci_dbg(xhci, "Skipped %d TDs on slot %u ep %u comp_code %u, TD found %d, skip flag %d\n", + missed_tds, slot_id, ep_index, trb_comp_code, !!td, ep->skip); + missed_tds = 0; + } /* Handle events not referencing the current TD */ - if (!trb_in_td(td, ep_trb_dma)) { + if (!td || missed_tds) { /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */ if (ring_xrun_event) return 0; @@ -2944,7 +2931,13 @@ static int handle_tx_event(struct xhci_hcd *xhci, } /* HC is busted, give up! */ - goto debug_finding_td; + td = list_first_entry(&ep_ring->td_list, struct xhci_td, td_list); + xhci_err(xhci, "Event dma %pad for ep %d comp_code %u not part of TD at %016llx - %016llx, missed %d\n", + &ep_trb_dma, ep_index, trb_comp_code, + (u64)xhci_trb_virt_to_dma(td->start_seg, td->start_trb), + (u64)xhci_trb_virt_to_dma(td->end_seg, td->end_trb), + missed_tds); + return -ESHUTDOWN; } ep_ring->old_trb_comp_code = trb_comp_code; @@ -2982,14 +2975,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, return 0; -debug_finding_td: - xhci_err(xhci, "Event dma %pad for ep %d status %d not part of TD at %016llx - %016llx\n", - &ep_trb_dma, ep_index, trb_comp_code, - (unsigned long long)xhci_trb_virt_to_dma(td->start_seg, td->start_trb), - (unsigned long long)xhci_trb_virt_to_dma(td->end_seg, td->end_trb)); - - return -ESHUTDOWN; - err_out: xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", (unsigned long long) xhci_trb_virt_to_dma( From 68c5cb7574c567e83ba95258cb146db95e069a87 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Fri, 9 Oct 2026 18:16:23 +0300 Subject: [PATCH 135/158] usb: xhci: Fix bounce buffer overflow High-speed devices with out of spec 1024 byte bulk endpoints exist and are allowed by USB core, but xhci-hcd always sets packet size to 512. The exact nature of these devices isn't documented, commit fb5ee84ea72c ("USB: Accept bulk endpoints with 1024-byte maxpacket") only states that they "don't work with xHCI host controllers", whatever it means. But somebody (or a malicious device) can try, and then the driver will allocate a 512 byte bounce buffer for this endpoint and may write up to 1024 bytes into it if particular scatter-gather URBs are used, because xhci_align_td() obtains packet size from the descriptor. Fix this. As a side effect, TRBs will be aligned to the packet size chosen by the driver on all endpoints of all speeds. Alignment serves the xHC, not device, so this is fine. Only out of spec devices are affected anyway. Reported-by: co+fd80bc5967eb22c3@bugs.sh Link: https://lore.kernel.org/linux-usb/D4tcSGerkYkIV1DmaUo1t8TaR5qQElDLkidn@bugs.sh/ Fixes: f9c589e142d0 ("xhci: TD-fragment, align the unsplittable case with a bounce buffer") Cc: stable@vger.kernel.org Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 243b1fd2b2f6..c23434001e9c 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3546,15 +3546,13 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred, static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len, - u32 *trb_buff_len, struct xhci_segment *seg) + u32 *trb_buff_len, struct xhci_segment *seg, u32 max_pkt) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; unsigned int unalign; - unsigned int max_pkt; u32 new_buff_len; size_t len; - max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); unalign = (enqd_len + *trb_buff_len) % max_pkt; /* we got lucky, last normal TRB data on segment is packet aligned */ @@ -3699,9 +3697,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, if (enqd_len + trb_buff_len < full_len) { field |= TRB_CHAIN; if (trb_is_link(ring->enqueue + 1)) { - if (xhci_align_td(xhci, urb, enqd_len, - &trb_buff_len, - ring->enq_seg)) { + if (xhci_align_td(xhci, urb, enqd_len, &trb_buff_len, + ring->enq_seg, ring->bounce_buf_len)) { send_addr = ring->enq_seg->bounce_dma; /* TD bounced at least, and last on this seg */ td->bounce_seg = ring->enq_seg; From fd616dc74e8f58f351f2b295620027116cd6e92e Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 9 Oct 2026 18:16:24 +0300 Subject: [PATCH 136/158] xhci: Prevent invalid vdev dereference during sideband unregister Offloaded USB audio devices using the xhci-sideband API store a pointer to the xhci virtual device (vdev) in the sideband structure when registering. This pointer typically remains valid throughout the lifetime of the USB device. If a configured offloaded device requires a reset, the USB core usually unbinds or notifies the audio driver beforehand, ensuring that the sideband is unregistered before the vdev is freed. An exception occurs when the USB core resets a device to recover from a failed resume, but a subsequent 'address device' request also fails. To recover in this specific scenario, the xHCI driver disables and re-enables the slot, which frees and re-allocates the vdev. xhci_sideband_unregister() later dereferences the stale, previously freed vdev pointer during disconnect, triggering a kernel oops: Unable to handle kernel paging request at virtual address dead000000000122 Call trace: xhci_get_ep_ctx+0x0/0x38 xhci_sideband_unregister+0x68/0xf0 uaudio_disconnect+0x70/0x144 usb_audio_disconnect+0x7c/0x268 usb_unbind_interface+0x13c/0x340 device_release_driver_internal+0x1c4/0x2bc usb_disable_device+0x84/0x190 usb_disconnect+0xe8/0x338 hub_event+0xbd8/0x19ac Fix this by preventing the reallocation of the vdev in this specific error path if the device is registered for sideband use. Just propagate the error directly to the USB core. It should either retry enumeration or detect the disconnected device, and handle it accordingly. Debugging this issue to the dangling vdev pointer, reporting it, testing, and initial patch with different solution by Lianqin Hu Reported-by: Lianqin Hu Closes: https://lore.kernel.org/linux-usb/TYUPR06MB6217000B59003EDF233D7246D2B22@TYUPR06MB6217.apcprd06.prod.outlook.com/ Tested-by: Lianqin Hu Fixes: de66754e9f80 ("xhci: sideband: add initial api to register a secondary interrupter entity") Cc: stable@vger.kernel.org Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20261009151624.618967-12-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 9564dde8bb34..30ac1bc4559b 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -4435,10 +4435,13 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, dev_warn(&udev->dev, "Device not responding to setup %s.\n", act); mutex_unlock(&xhci->mutex); - ret = xhci_disable_and_free_slot(xhci, udev->slot_id); - if (!ret) { - if (xhci_alloc_dev(hcd, udev) == 1) - xhci_setup_addressable_virt_dev(xhci, udev); + + if (!virt_dev->sideband) { + ret = xhci_disable_and_free_slot(xhci, udev->slot_id); + if (!ret) { + if (xhci_alloc_dev(hcd, udev) == 1) + xhci_setup_addressable_virt_dev(xhci, udev); + } } kfree(command->completion); kfree(command); From 29381088ac50a4910d18a5f8079d9bd505f839be Mon Sep 17 00:00:00 2001 From: Stephen Horvath Date: Sun, 4 Oct 2026 02:27:31 +0200 Subject: [PATCH 137/158] usb: xhci-pci: Add AMD 600 series to xhci-pci-prom21 It seems the xhci-pci-prom21 module only has IDs for the 800 series chipsets. However, the 600 series chipsets also have Promontory 21 chips, so we should add their IDs to the list as well. I have tested this on my X670 board featuring DID `0x43f7`. `1022:43f7` also appears on the PCI ID database (https://pci-ids.ucw.cz/read/PC/1022/43f7). Device IDs `0x43f9` (B665) & `0x43fa` (A620) were suggested by Shyam. Also list the new IDs in Documentation/hwmon/prom21-xhci.rst. Cc: stable@vger.kernel.org # 7.2.x Cc: Shyam Sundar S K Cc: Jihong Min Signed-off-by: Stephen Horvath Tested-by: Mirko Jechow [mirko.jechow@gmx.de: add new IDs to prom21-xhci.rst] Signed-off-by: Mirko Jechow Link: https://patch.msgid.link/20261004002731.49927-1-mirko.jechow@gmx.de Signed-off-by: Greg Kroah-Hartman --- Documentation/hwmon/prom21-xhci.rst | 2 +- drivers/usb/host/xhci-pci-prom21.c | 3 +++ drivers/usb/host/xhci-pci.c | 3 +++ drivers/usb/host/xhci-pci.h | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/prom21-xhci.rst b/Documentation/hwmon/prom21-xhci.rst index 7984fb187bd8..6c0b6e736892 100644 --- a/Documentation/hwmon/prom21-xhci.rst +++ b/Documentation/hwmon/prom21-xhci.rst @@ -9,7 +9,7 @@ Supported chips: Prefix: 'prom21_xhci' - PCI IDs: 1022:43fc, 1022:43fd + PCI IDs: 1022:43f7, 1022:43f9, 1022:43fa, 1022:43fc, 1022:43fd Author: diff --git a/drivers/usb/host/xhci-pci-prom21.c b/drivers/usb/host/xhci-pci-prom21.c index 6486f4a09345..5f088a47d79f 100644 --- a/drivers/usb/host/xhci-pci-prom21.c +++ b/drivers/usb/host/xhci-pci-prom21.c @@ -111,6 +111,9 @@ static void prom21_xhci_remove(struct pci_dev *dev) } static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43F7) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43F9) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FA) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) }, { /* end: all zeroes */ } diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index a8889081ae82..b407676808fc 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -701,6 +701,9 @@ static const struct pci_device_id pci_ids_renesas[] = { /* handled by xhci-pci-prom21 if enabled */ static const struct pci_device_id pci_ids_prom21[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43F7) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43F9) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FA) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) }, { /* end: all zeroes */ } diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h index 11f435f94322..533efc7db801 100644 --- a/drivers/usb/host/xhci-pci.h +++ b/drivers/usb/host/xhci-pci.h @@ -4,6 +4,9 @@ #ifndef XHCI_PCI_H #define XHCI_PCI_H +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43F7 0x43f7 +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43F9 0x43f9 +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FA 0x43fa #define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC 0x43fc #define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD 0x43fd From 033d24c2052ce9433754e2e85da7e5483707fd83 Mon Sep 17 00:00:00 2001 From: Swark Yang Date: Wed, 5 Aug 2026 22:09:33 -0700 Subject: [PATCH 138/158] dt-bindings: usb: generic-xhci: add Axiado AX3005 xHCI variant The Axiado AX3005 xHCI controller is software-compatible with the generic xHCI programming interface and uses "generic-xhci" as its fallback, as do the Marvell Armada 37xx/8k controllers. Add "axiado,ax3005-xhci" to the existing compatible enum instead of adding a separate schema entry. Since the enum no longer covers only Armada parts, drop the "Armada 37xx/8k SoCs" description rather than leave it inaccurate. Signed-off-by: Swark Yang Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260805-upstrea-ax3005-usb-v4-v4-1-796af97bfce4@axiado.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/generic-xhci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/generic-xhci.yaml b/Documentation/devicetree/bindings/usb/generic-xhci.yaml index 62678abd74b5..9af6d0172e56 100644 --- a/Documentation/devicetree/bindings/usb/generic-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-xhci.yaml @@ -19,9 +19,9 @@ properties: - enum: - marvell,armada-375-xhci - marvell,armada-380-xhci - - description: Armada 37xx/8k SoCs - items: + - items: - enum: + - axiado,ax3005-xhci - marvell,armada3700-xhci - marvell,armada-8k-xhci - const: generic-xhci From 1dabdb6790c1215d078a13b747eb15d4b1986462 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 9 Oct 2026 09:51:54 -0300 Subject: [PATCH 139/158] usb: xhci-pci: Add TUSB73x0 definitions Instead of hard-coding the TUSB73X0 PCI ID, USB_CTRL register address and the PWRON_POLARITY, introduce definitions for them to make the code easier to read. No functional change. Signed-off-by: Fabio Estevam Acked-by: Bjorn Helgaas # pci_ids.h Link: https://patch.msgid.link/20261009125154.266435-1-festevam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/pci-quirks.c | 3 ++- drivers/usb/host/xhci-pci.c | 9 +++++++-- include/linux/pci_ids.h | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index ba158d63bb8b..f670e7aab984 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -1189,7 +1189,8 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev) val = readl(base + ext_cap_offset); /* Auto handoff never worked for these devices. Force it and continue */ - if ((pdev->vendor == PCI_VENDOR_ID_TI && pdev->device == 0x8241) || + if ((pdev->vendor == PCI_VENDOR_ID_TI && + pdev->device == PCI_DEVICE_ID_TI_TUSB73X0) || (pdev->vendor == PCI_VENDOR_ID_RENESAS && pdev->device == 0x0014)) { val = (val | XHCI_HC_OS_OWNED) & ~XHCI_HC_BIOS_OWNED; diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index b407676808fc..e278db940a58 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -28,6 +28,9 @@ #define SPARSE_DISABLE_BIT 17 #define SPARSE_CNTL_ENABLE 0xC12C +#define TUSB73X0_USB_CTRL 0xe0 +#define TUSB73X0_PWRON_POLARITY BIT(22) + /* Device for a quirk */ #define PCI_VENDOR_ID_FRESCO_LOGIC 0x1b73 #define PCI_DEVICE_ID_FRESCO_LOGIC_PDK 0x1000 @@ -479,7 +482,8 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == PCI_DEVICE_ID_ASMEDIA_3042_XHCI) xhci->quirks |= XHCI_RESET_ON_RESUME; - if (pdev->vendor == PCI_VENDOR_ID_TI && pdev->device == 0x8241) + if (pdev->vendor == PCI_VENDOR_ID_TI && + pdev->device == PCI_DEVICE_ID_TI_TUSB73X0) xhci->quirks |= XHCI_LIMIT_ENDPOINT_INTERVAL_7; if ((pdev->vendor == PCI_VENDOR_ID_BROADCOM || @@ -678,7 +682,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) dma_set_max_seg_size(&dev->dev, UINT_MAX); if (device_property_read_bool(&dev->dev, "ti,pwron-active-high")) - pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22); + pci_clear_and_set_config_dword(dev, TUSB73X0_USB_CTRL, 0, + TUSB73X0_PWRON_POLARITY); return 0; diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 63774ae2bf71..b896315b43fd 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -889,6 +889,7 @@ #define PCI_DEVICE_ID_TI_XX12 0x8039 #define PCI_DEVICE_ID_TI_XX12_FM 0x803b #define PCI_DEVICE_ID_TI_XIO2000A 0x8231 +#define PCI_DEVICE_ID_TI_TUSB73X0 0x8241 #define PCI_DEVICE_ID_TI_1130 0xac12 #define PCI_DEVICE_ID_TI_1031 0xac13 #define PCI_DEVICE_ID_TI_1131 0xac15 From abda9e31fde33ef689345f8e96f89d2d1e8aa7a9 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 3 Sep 2026 15:45:37 +0800 Subject: [PATCH 140/158] dt-bindings: usb: Add Tegra264 XUSB host support Extend the Tegra234 XUSB host binding to cover Tegra264: - Add nvidia,tegra264-xusb compatible string - Document optional USB wake interrupts for Tegra264 - Document Tegra264 PMC wake event to port mapping - List the up to five IOMMU specifiers for the additional XUSB host stream IDs, since their order is now significant - Constrain interrupts and iommus counts for both Tegra234 and Tegra264 via allOf conditionals, since each SoC wires up a fixed number of wake interrupts and stream IDs Signed-off-by: Wayne Chang Acked-by: Conor Dooley Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903074539.613220-2-waynec@nvidia.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/nvidia,tegra234-xusb.yaml | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml b/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml index ec0993497fbb..3f56110be915 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml @@ -17,7 +17,9 @@ description: | properties: compatible: - const: nvidia,tegra234-xusb + enum: + - nvidia,tegra234-xusb + - nvidia,tegra264-xusb reg: items: @@ -43,6 +45,7 @@ properties: - description: USB wake event 4 - description: USB wake event 5 - description: USB wake event 6 + - description: USB wake event 7 description: | The first two interrupts are required for the USB host controller. The remaining USB wake event interrupts are optional. Each USB wake event is @@ -50,10 +53,9 @@ properties: platform. The USB host controller can function even if no wake-up events are defined. The USB wake event interrupts are handled by the Tegra PMC; hence, the interrupt controller for these is the PMC and the interrupt - IDs correspond to the PMC wake event IDs. A complete list of wake event - IDs is provided below, and this information is also present in the Tegra - TRM document. + IDs correspond to the PMC wake event IDs. + Tegra234 PMC wake events: PMC wake-up 76 for USB3 port 0 wakeup PMC wake-up 77 for USB3 port 1 wakeup PMC wake-up 78 for USB3 port 2 and port 3 wakeup @@ -62,6 +64,16 @@ properties: PMC wake-up 81 for USB2 port 2 wakeup PMC wake-up 82 for USB2 port 3 wakeup + Tegra264 PMC wake events: + PMC wake-up 79 for USB3 port 0 wakeup + PMC wake-up 80 for USB3 port 1 wakeup + PMC wake-up 81 for USB3 port 2 wakeup + PMC wake-up 82 for USB3 port 3 wakeup + PMC wake-up 83 for USB2 port 0 wakeup + PMC wake-up 84 for USB2 port 1 wakeup + PMC wake-up 85 for USB2 port 2 wakeup + PMC wake-up 86 for USB2 port 3 wakeup + clocks: items: - description: XUSB host clock @@ -97,7 +109,13 @@ properties: - const: write iommus: - maxItems: 1 + minItems: 1 + items: + - description: XUSB host stream ID + - description: XUSB host stream ID for virtual function 1 + - description: XUSB host stream ID for virtual function 2 + - description: XUSB host stream ID for virtual function 3 + - description: XUSB host stream ID for virtual function 4 nvidia,xusb-padctl: $ref: /schemas/types.yaml#/definitions/phandle @@ -137,6 +155,30 @@ properties: allOf: - $ref: usb-xhci.yaml + - if: + properties: + compatible: + contains: + const: nvidia,tegra234-xusb + then: + properties: + interrupts: + maxItems: 9 + iommus: + maxItems: 1 + + - if: + properties: + compatible: + contains: + const: nvidia,tegra264-xusb + then: + properties: + interrupts: + maxItems: 10 + iommus: + minItems: 5 + unevaluatedProperties: false examples: From 1ff864af3cb70f081a4c3ea1ffe9daa8156da4d8 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 3 Sep 2026 15:45:38 +0800 Subject: [PATCH 141/158] usb: host: xhci-tegra: Skip MBOX MSG_ENABLED on Tegra264 Tegra264 XUSB does not use the firmware mailbox handshake to enable controller messages (MBOX_CMD_MSG_ENABLED). Add a per-SoC flag and short-circuit __tegra_xusb_enable_firmware_messages() so both probe and resume avoid sending the command. Signed-off-by: Wayne Chang Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903074539.613220-3-waynec@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 15dfa7e1a7dd..2f7660e38aed 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -251,6 +251,7 @@ struct tegra_xusb_soc { bool otg_set_port_power; bool has_bar2; + bool enable_firmware_messages; }; struct tegra_xusb_context { @@ -1261,6 +1262,9 @@ static int __tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) struct tegra_xusb_mbox_msg msg; int err; + if (!tegra->soc->enable_firmware_messages) + return 0; + /* Enable firmware messages from controller. */ msg.cmd = MBOX_CMD_MSG_ENABLED; msg.data = 0; @@ -2577,6 +2581,7 @@ static const struct tegra_xusb_soc tegra124_soc = { .owner = 0xf0, .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, + .enable_firmware_messages = true, }; #if IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) || IS_ENABLED(CONFIG_ARCH_TEGRA_132_SOC) MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); @@ -2618,6 +2623,7 @@ static const struct tegra_xusb_soc tegra210_soc = { .owner = 0xf0, .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, + .enable_firmware_messages = true, }; #if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) MODULE_FIRMWARE("nvidia/tegra210/xusb.bin"); @@ -2667,6 +2673,7 @@ static const struct tegra_xusb_soc tegra186_soc = { .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, .lpm_support = true, + .enable_firmware_messages = true, }; static const char * const tegra194_supply_names[] = { @@ -2701,6 +2708,7 @@ static const struct tegra_xusb_soc tegra194_soc = { .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, .lpm_support = true, + .enable_firmware_messages = true, }; #if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) MODULE_FIRMWARE("nvidia/tegra194/xusb.bin"); @@ -2738,6 +2746,7 @@ static const struct tegra_xusb_soc tegra234_soc = { }, .lpm_support = true, .has_bar2 = true, + .enable_firmware_messages = true, }; static const struct of_device_id tegra_xusb_of_match[] = { From 02ad63e19449b1637ffca8d26c383223cfc8e6c1 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 3 Sep 2026 15:45:39 +0800 Subject: [PATCH 142/158] usb: host: xhci-tegra: Add Tegra264 XHCI support This change adds Tegra264 XUSB host mode controller support. The host controller is very similar to the existing Tegra234 XHCI, except the number of max_num_wakes. Signed-off-by: Wayne Chang Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260903074539.613220-4-waynec@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 2f7660e38aed..50d733b2ad71 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -2749,12 +2749,40 @@ static const struct tegra_xusb_soc tegra234_soc = { .enable_firmware_messages = true, }; +static const struct tegra_xusb_soc tegra264_soc = { + .supply_names = tegra194_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_supply_names), + .phy_types = tegra194_phy_types, + .num_types = ARRAY_SIZE(tegra194_phy_types), + .max_num_wakes = 8, + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 4, }, + .usb2 = { .offset = 4, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .ops = &tegra234_ops, + .mbox = { + .cmd = XUSB_BAR2_ARU_MBOX_CMD, + .data_in = XUSB_BAR2_ARU_MBOX_DATA_IN, + .data_out = XUSB_BAR2_ARU_MBOX_DATA_OUT, + .owner = XUSB_BAR2_ARU_MBOX_OWNER, + .smi_intr = XUSB_BAR2_ARU_SMI_INTR, + }, + .lpm_support = true, + .has_bar2 = true, + .enable_firmware_messages = false, +}; + static const struct of_device_id tegra_xusb_of_match[] = { { .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc }, { .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc }, { .compatible = "nvidia,tegra186-xusb", .data = &tegra186_soc }, { .compatible = "nvidia,tegra194-xusb", .data = &tegra194_soc }, { .compatible = "nvidia,tegra234-xusb", .data = &tegra234_soc }, + { .compatible = "nvidia,tegra264-xusb", .data = &tegra264_soc }, { }, }; MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); From bbf6b99a57b2479bf51c3c8ad0946f4b8c33ed69 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Wed, 16 Sep 2026 14:18:58 +0800 Subject: [PATCH 143/158] usb: typec: ucsi_ccg: Add retry mechanism in ccg_read CCG4 firmware occasionally fails to respond to I2C read requests, especially when reading cci and intr_reg from the ISR path. In these cases, i2c_transfer() returns -EREMOTEIO. The CCG4 is EOL, and Infineon no longer supports firmware updates, so a software workaround is necessary. Attempt the read up to CCG_READ_MAX_RETRIES (4) times in total when -EREMOTEIO is returned, sleeping ~1 ms via fsleep() between attempts. This allows recovery from transient I2C failures without affecting other error paths. The sleep is safe because ccg_read() is called both from process context and from ccg_irq_handler(), which runs in threaded IRQ context (registered via request_threaded_irq() with a NULL hard-IRQ handler), so sleeping is allowed in both cases. Fixes: 247c554a14aa ("usb: typec: ucsi: add support for Cypress CCGx") Cc: stable Signed-off-by: Wayne Chang Reviewed-by: Jon Hunter Tested-by: Jon Hunter Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260916061858.1471606-1-waynec@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_ccg.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 80e3e84b418d..3f12db95c7f4 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -135,6 +135,8 @@ struct version_format { #define NVIDIA_FTB_DP_OFFSET (2) #define NVIDIA_FTB_DBG_OFFSET (3) +#define CCG_READ_MAX_RETRIES 4 + struct version_info { struct version_format base; struct version_format app; @@ -255,6 +257,7 @@ static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) }; u32 rlen, rem_len = len, max_read_len = len; int status; + int retry_count; /* check any max_read_len limitation on i2c adapter */ if (quirks && quirks->max_read_len) @@ -266,7 +269,17 @@ static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) rlen = min_t(u16, rem_len, max_read_len); msgs[1].len = rlen; put_unaligned_le16(rab, buf); - status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + + for (retry_count = 0; retry_count < CCG_READ_MAX_RETRIES; retry_count++) { + status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + + /* Only retry on -EREMOTEIO, and not after the last attempt */ + if (status != -EREMOTEIO || retry_count == CCG_READ_MAX_RETRIES - 1) + break; + + fsleep(1000); + } + if (status < 0) { dev_err(uc->dev, "i2c_transfer failed %d\n", status); pm_runtime_put_sync(uc->dev); From e7daa54802c566ed3f22e81513f84c8c9197c5d1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 9 Oct 2026 14:33:59 +0200 Subject: [PATCH 144/158] USB: serial: sierra: fix urb leak on write completion Make sure to drop the reference taken by usb_anchor_urb() also on successful submission of delayed write URBs during runtime resume to prevent them from leaking on completion. Fixes: e6929a9020ac ("USB: support for autosuspend in sierra while online") Reported-by: Wentao Liang Link: https://lore.kernel.org/20260916165352.2085480-1-vulab@iscas.ac.cn Cc: stable@vger.kernel.org # 2.6.32 Cc: Oliver Neukum Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/sierra.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 6e443aacae07..f2cc7f364d20 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -972,12 +972,13 @@ static int sierra_submit_delayed_urbs(struct usb_serial_port *port) intfdata->in_flight--; usb_unanchor_urb(urb); kfree(urb->transfer_buffer); - usb_free_urb(urb); spin_lock(&portdata->lock); portdata->outstanding_urbs--; spin_unlock(&portdata->lock); } + + usb_put_urb(urb); } if (ec) From b88f018678f3565d1773b0ea61583eac1398a599 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Tue, 11 Aug 2026 14:04:56 +0800 Subject: [PATCH 145/158] USB: serial: mxuport: clean up product ID definitions and device table Rename the product ID definitions after the corresponding product names to make the product mapping explicit. Introduce a helper macro to keep each device-table entry on one line and make the table easier to scan. Signed-off-by: Crescent Hsieh Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 43 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index 088d5dd8abb5..a4e0467171c6 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -28,14 +28,14 @@ /* Definitions for the vendor ID and device ID */ #define MX_USBSERIAL_VID 0x110A #define MX_UPORT1250_PID 0x1250 -#define MX_UPORT1251_PID 0x1251 +#define MX_UPORT1250I_PID 0x1251 #define MX_UPORT1410_PID 0x1410 #define MX_UPORT1450_PID 0x1450 -#define MX_UPORT1451_PID 0x1451 -#define MX_UPORT1618_PID 0x1618 -#define MX_UPORT1658_PID 0x1658 -#define MX_UPORT1613_PID 0x1613 -#define MX_UPORT1653_PID 0x1653 +#define MX_UPORT1450I_PID 0x1451 +#define MX_UPORT1610_8_PID 0x1618 +#define MX_UPORT1650_8_PID 0x1658 +#define MX_UPORT1610_16_PID 0x1613 +#define MX_UPORT1650_16_PID 0x1653 /* Definitions for USB info */ #define HEADER_SIZE 4 @@ -157,26 +157,21 @@ struct mxuport_port { #define MX_PORTS_OFFSET 1 #define MX_PORTS(n) (((n) - MX_PORTS_OFFSET) & MX_PORTS_MASK) +#define MX_DEVICE(pid, ports) \ + { USB_DEVICE(MX_USBSERIAL_VID, pid), \ + .driver_info = MX_PORTS(ports) } + /* Table of devices that work with this driver */ static const struct usb_device_id mxuport_idtable[] = { - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1250_PID), - .driver_info = MX_PORTS(2) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1251_PID), - .driver_info = MX_PORTS(2) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1410_PID), - .driver_info = MX_PORTS(4) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1450_PID), - .driver_info = MX_PORTS(4) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1451_PID), - .driver_info = MX_PORTS(4) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1618_PID), - .driver_info = MX_PORTS(8) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1658_PID), - .driver_info = MX_PORTS(8) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1613_PID), - .driver_info = MX_PORTS(16) }, - { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1653_PID), - .driver_info = MX_PORTS(16) }, + MX_DEVICE(MX_UPORT1250_PID, 2), + MX_DEVICE(MX_UPORT1250I_PID, 2), + MX_DEVICE(MX_UPORT1410_PID, 4), + MX_DEVICE(MX_UPORT1450_PID, 4), + MX_DEVICE(MX_UPORT1450I_PID, 4), + MX_DEVICE(MX_UPORT1610_8_PID, 8), + MX_DEVICE(MX_UPORT1650_8_PID, 8), + MX_DEVICE(MX_UPORT1610_16_PID, 16), + MX_DEVICE(MX_UPORT1650_16_PID, 16), {} /* Terminating entry */ }; From 4707336330a423191fc28ead250b1963d35719c5 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Tue, 11 Aug 2026 14:04:57 +0800 Subject: [PATCH 146/158] USB: serial: mxuport: clean up firmware version handling Add a small helper for parsing firmware versions and use it for the firmware image. This avoids open-coded firmware image offsets in probe() and retains the existing image-size validation before reading the version bytes. Keep the existing version comparison policy unchanged, but store the version components explicitly so that the version can be printed without unpacking a raw integer. Print the firmware version fields in decimal, as these fields represent readable version components rather than hexadecimal values. Signed-off-by: Crescent Hsieh Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 78 ++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index a4e0467171c6..61d4c6e1a9ce 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -43,9 +43,22 @@ #define DOWN_BLOCK_SIZE 64 /* Definitions for firmware info */ -#define VER_ADDR_1 0x20 -#define VER_ADDR_2 0x24 -#define VER_ADDR_3 0x28 +#define MX_FW_VER_BYTE1_OFF 0x20 +#define MX_FW_VER_BYTE2_OFF 0x24 +#define MX_FW_VER_BYTE3_OFF 0x28 + +struct mxuport_fw_version { + u8 major; + u8 minor; + u8 build; + u32 value; +}; + +static const u16 mxuport_fw_ver_offsets[] = { + MX_FW_VER_BYTE1_OFF, + MX_FW_VER_BYTE2_OFF, + MX_FW_VER_BYTE3_OFF, +}; /* Definitions for USB vendor request */ #define RQ_VENDOR_NONE 0x00 @@ -965,8 +978,31 @@ static int mxuport_calc_num_ports(struct usb_serial *serial, return num_ports; } +static void mxuport_compose_fw_version(struct mxuport_fw_version *version, + u8 major, u8 minor, u8 build) +{ + version->major = major; + version->minor = minor; + version->build = build; + version->value = (major << 16) | (minor << 8) | build; +} + +static int mxuport_parse_fw_version(const struct firmware *fw, + const u16 *offsets, + struct mxuport_fw_version *version) +{ + if (fw->size <= offsets[2]) + return -EINVAL; + + mxuport_compose_fw_version(version, fw->data[offsets[0]], + fw->data[offsets[1]], fw->data[offsets[2]]); + + return 0; +} + /* Get the version of the firmware currently running. */ -static int mxuport_get_fw_version(struct usb_serial *serial, u32 *version) +static int mxuport_get_fw_version(struct usb_serial *serial, + struct mxuport_fw_version *version) { u8 *ver_buf; int err; @@ -983,7 +1019,7 @@ static int mxuport_get_fw_version(struct usb_serial *serial, u32 *version) goto out; } - *version = (ver_buf[0] << 16) | (ver_buf[1] << 8) | ver_buf[2]; + mxuport_compose_fw_version(version, ver_buf[0], ver_buf[1], ver_buf[2]); err = 0; out: kfree(ver_buf); @@ -1043,9 +1079,8 @@ static int mxuport_probe(struct usb_serial *serial, const struct usb_device_id *id) { u16 productid = le16_to_cpu(serial->dev->descriptor.idProduct); + struct mxuport_fw_version version, local_ver; const struct firmware *fw_p = NULL; - u32 version; - int local_ver; char buf[32]; int err; @@ -1060,10 +1095,8 @@ static int mxuport_probe(struct usb_serial *serial, if (err < 0) return err; - dev_dbg(&serial->interface->dev, "Device firmware version v%x.%x.%x\n", - (version & 0xff0000) >> 16, - (version & 0xff00) >> 8, - (version & 0xff)); + dev_dbg(&serial->interface->dev, "Device firmware version v%u.%u.%u\n", + version.major, version.minor, version.build); snprintf(buf, sizeof(buf) - 1, "moxa/moxa-%04x.fw", productid); @@ -1075,35 +1108,30 @@ static int mxuport_probe(struct usb_serial *serial, /* Use the firmware already in the device */ err = 0; } else { - if (fw_p->size <= VER_ADDR_3) { + err = mxuport_parse_fw_version(fw_p, mxuport_fw_ver_offsets, &local_ver); + if (err) { dev_err(&serial->interface->dev, "Firmware %s is too short\n", buf); - err = -EINVAL; goto out; } - local_ver = ((fw_p->data[VER_ADDR_1] << 16) | - (fw_p->data[VER_ADDR_2] << 8) | - fw_p->data[VER_ADDR_3]); dev_dbg(&serial->interface->dev, - "Available firmware version v%x.%x.%x\n", - fw_p->data[VER_ADDR_1], fw_p->data[VER_ADDR_2], - fw_p->data[VER_ADDR_3]); - if (local_ver > version) { + "Available firmware version v%u.%u.%u\n", + local_ver.major, local_ver.minor, local_ver.build); + + if (local_ver.value > version.value) { err = mxuport_download_fw(serial, fw_p); if (err) goto out; - err = mxuport_get_fw_version(serial, &version); + err = mxuport_get_fw_version(serial, &version); if (err < 0) goto out; } } dev_info(&serial->interface->dev, - "Using device firmware version v%x.%x.%x\n", - (version & 0xff0000) >> 16, - (version & 0xff00) >> 8, - (version & 0xff)); + "Using device firmware version v%u.%u.%u\n", + version.major, version.minor, version.build); /* * Contains the features of this hardware. Store away for From a3ad29ec47d3bb4fb3fa20bf8fcc0a466052dd2c Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Tue, 11 Aug 2026 14:04:58 +0800 Subject: [PATCH 147/158] USB: serial: mxuport: add MUX50U-based device support Add support for MUX50U-based UPort G2 devices and Platform UARTs. Add their product IDs and list the supported products by family. Encode the number of ports and device family in each USB device ID entry. Use the family to select the firmware image and version offsets, and the method used to determine whether a firmware download is needed. UPort G2 devices use the shared UP firmware image (moxa-up-mux50u.fw), while Platform UARTs use the shared PF firmware image (moxa-pf-mux50u.fw). Platform UARTs are board-level UARTs accessed through the USB bus. The factory firmware on MUX50U-based devices reports its version in a different format from the runtime firmware image, so version comparison cannot reliably determine whether a firmware download is needed. Check the system-register remap state instead to determine whether the device is already running the runtime firmware from SRAM. Keep the existing per-product firmware selection and version comparison for UPort G1 devices. Signed-off-by: Crescent Hsieh Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 170 +++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 18 deletions(-) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index 61d4c6e1a9ce..35123572ca4d 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -6,10 +6,20 @@ * Copyright (c) 2013 Andrew Lunn * * Supports the following Moxa USB to serial converters: - * 2 ports : UPort 1250, UPort 1250I - * 4 ports : UPort 1410, UPort 1450, UPort 1450I - * 8 ports : UPort 1610-8, UPort 1650-8 - * 16 ports : UPort 1610-16, UPort 1650-16 + * + * UPort G1: + * UPort 1250, UPort 1250I + * UPort 1410, UPort 1450, UPort 1450I + * UPort 1610-8, UPort 1650-8 + * UPort 1610-16, UPort 1650-16 + * UPort G2: + * UPort 1250-G2, UPort 1250I-G2 + * UPort 1410-G2, UPort 1450-G2, UPort 1450I-G2 + * UPort 1610-8-G2, UPort 1650-8-G2 + * UPort 1650-8-G2-Hub, UPort 1650I-8-G2 + * Platform UART: + * MU250U, MU450U, MU850U + * MUx50U-3, MUx50U-5, MUx50U-6, MUx50U-7 */ #include @@ -37,6 +47,24 @@ #define MX_UPORT1610_16_PID 0x1613 #define MX_UPORT1650_16_PID 0x1653 +#define MX_UPORT1250_G2_PID 0x1252 +#define MX_UPORT1250I_G2_PID 0x1253 +#define MX_UPORT1410_G2_PID 0x1411 +#define MX_UPORT1450_G2_PID 0x1452 +#define MX_UPORT1450I_G2_PID 0x1453 +#define MX_UPORT1610_8_G2_PID 0x1619 +#define MX_UPORT1650_8_G2_PID 0x1659 +#define MX_UPORT1650_8_G2_HUB_PID 0x165a +#define MX_UPORT1650I_8_G2_PID 0x165b + +#define MX_MU250U_PID 0x0250 +#define MX_MU450U_PID 0x0450 +#define MX_MU850U_PID 0x0850 +#define MX_MUX50U_6_PID 0x7002 +#define MX_MUX50U_3_PID 0x7003 +#define MX_MUX50U_5_PID 0x7004 +#define MX_MUX50U_7_PID 0x7005 + /* Definitions for USB info */ #define HEADER_SIZE 4 #define EVENT_LENGTH 8 @@ -46,6 +74,13 @@ #define MX_FW_VER_BYTE1_OFF 0x20 #define MX_FW_VER_BYTE2_OFF 0x24 #define MX_FW_VER_BYTE3_OFF 0x28 +#define MX_MUX50U_FW_VER_BYTE1_OFF 0x86 +#define MX_MUX50U_FW_VER_BYTE2_OFF 0x88 +#define MX_MUX50U_FW_VER_BYTE3_OFF 0x8a + +#define MX_SYS_REG_REMAP_OFF 0x20 +#define MX_PWR_REMAP_MASK 0x03 +#define MX_REMAP_TO_SRAM 0x02 struct mxuport_fw_version { u8 major; @@ -60,6 +95,12 @@ static const u16 mxuport_fw_ver_offsets[] = { MX_FW_VER_BYTE3_OFF, }; +static const u16 mxuport_mux50u_fw_ver_offsets[] = { + MX_MUX50U_FW_VER_BYTE1_OFF, + MX_MUX50U_FW_VER_BYTE2_OFF, + MX_MUX50U_FW_VER_BYTE3_OFF, +}; + /* Definitions for USB vendor request */ #define RQ_VENDOR_NONE 0x00 #define RQ_VENDOR_SET_BAUD 0x01 /* Set baud rate */ @@ -96,6 +137,8 @@ static const u16 mxuport_fw_ver_offsets[] = { #define RQ_VENDOR_RESET_DEVICE 0x23 /* Try to reset the device */ #define RQ_VENDOR_QUERY_FW_CONFIG 0x24 +#define RQ_VENDOR_GET_SYS_REG 0x34 + #define RQ_VENDOR_GET_VERSION 0x81 /* Get firmware version */ #define RQ_VENDOR_GET_PAGE 0x82 /* Read flash page */ #define RQ_VENDOR_GET_ROM_PROC 0x83 /* Get ROM process state */ @@ -170,21 +213,44 @@ struct mxuport_port { #define MX_PORTS_OFFSET 1 #define MX_PORTS(n) (((n) - MX_PORTS_OFFSET) & MX_PORTS_MASK) -#define MX_DEVICE(pid, ports) \ +#define MX_FAMILY_MASK GENMASK(5, 4) +#define MX_UPORT_G1 0 +#define MX_UPORT_G2 BIT(4) +#define MX_PLATFORM_UART BIT(5) +#define MX_FAMILY(info) ((info) & MX_FAMILY_MASK) +#define MX_DEVICE_INFO(ports, family) (MX_PORTS(ports) | (family)) + +#define MX_DEVICE(pid, ports, family) \ { USB_DEVICE(MX_USBSERIAL_VID, pid), \ - .driver_info = MX_PORTS(ports) } + .driver_info = MX_DEVICE_INFO(ports, family) } /* Table of devices that work with this driver */ static const struct usb_device_id mxuport_idtable[] = { - MX_DEVICE(MX_UPORT1250_PID, 2), - MX_DEVICE(MX_UPORT1250I_PID, 2), - MX_DEVICE(MX_UPORT1410_PID, 4), - MX_DEVICE(MX_UPORT1450_PID, 4), - MX_DEVICE(MX_UPORT1450I_PID, 4), - MX_DEVICE(MX_UPORT1610_8_PID, 8), - MX_DEVICE(MX_UPORT1650_8_PID, 8), - MX_DEVICE(MX_UPORT1610_16_PID, 16), - MX_DEVICE(MX_UPORT1650_16_PID, 16), + MX_DEVICE(MX_UPORT1250_PID, 2, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1250I_PID, 2, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1410_PID, 4, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1450_PID, 4, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1450I_PID, 4, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1610_8_PID, 8, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1650_8_PID, 8, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1610_16_PID, 16, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1650_16_PID, 16, MX_UPORT_G1), + MX_DEVICE(MX_UPORT1250_G2_PID, 2, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1250I_G2_PID, 2, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1410_G2_PID, 4, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1450_G2_PID, 4, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1450I_G2_PID, 4, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1610_8_G2_PID, 8, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1650_8_G2_PID, 8, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1650_8_G2_HUB_PID, 8, MX_UPORT_G2), + MX_DEVICE(MX_UPORT1650I_8_G2_PID, 8, MX_UPORT_G2), + MX_DEVICE(MX_MU250U_PID, 2, MX_PLATFORM_UART), + MX_DEVICE(MX_MU450U_PID, 4, MX_PLATFORM_UART), + MX_DEVICE(MX_MU850U_PID, 8, MX_PLATFORM_UART), + MX_DEVICE(MX_MUX50U_6_PID, 6, MX_PLATFORM_UART), + MX_DEVICE(MX_MUX50U_3_PID, 3, MX_PLATFORM_UART), + MX_DEVICE(MX_MUX50U_5_PID, 5, MX_PLATFORM_UART), + MX_DEVICE(MX_MUX50U_7_PID, 7, MX_PLATFORM_UART), {} /* Terminating entry */ }; @@ -1000,6 +1066,56 @@ static int mxuport_parse_fw_version(const struct firmware *fw, return 0; } +static bool mxuport_is_mux50u_device(unsigned long features) +{ + switch (MX_FAMILY(features)) { + case MX_UPORT_G2: + case MX_PLATFORM_UART: + return true; + default: + return false; + } +} + +static void mxuport_get_fw_name(unsigned long features, u16 productid, + char *buf, size_t size) +{ + switch (MX_FAMILY(features)) { + case MX_UPORT_G2: + strscpy(buf, "moxa/moxa-up-mux50u.fw", size); + break; + case MX_PLATFORM_UART: + strscpy(buf, "moxa/moxa-pf-mux50u.fw", size); + break; + default: + snprintf(buf, size, "moxa/moxa-%04x.fw", productid); + break; + } +} + +static int mxuport_fw_running_from_sram(struct usb_serial *serial, bool *from_sram) +{ + int err; + u32 reg; + u8 *buf; + + buf = kzalloc_objs(*buf, sizeof(reg)); + if (!buf) + return -ENOMEM; + + err = mxuport_recv_ctrl_urb(serial, RQ_VENDOR_GET_SYS_REG, + MX_SYS_REG_REMAP_OFF, 0, buf, sizeof(reg)); + if (err < 0) + goto out; + + reg = get_unaligned_be32(buf); + *from_sram = (((reg >> 24) & MX_PWR_REMAP_MASK) == MX_REMAP_TO_SRAM); + err = 0; +out: + kfree(buf); + return err; +} + /* Get the version of the firmware currently running. */ static int mxuport_get_fw_version(struct usb_serial *serial, struct mxuport_fw_version *version) @@ -1080,7 +1196,10 @@ static int mxuport_probe(struct usb_serial *serial, { u16 productid = le16_to_cpu(serial->dev->descriptor.idProduct); struct mxuport_fw_version version, local_ver; + unsigned long features = id->driver_info; const struct firmware *fw_p = NULL; + const u16 *fw_version_offsets; + bool download_fw, from_sram; char buf[32]; int err; @@ -1098,7 +1217,12 @@ static int mxuport_probe(struct usb_serial *serial, dev_dbg(&serial->interface->dev, "Device firmware version v%u.%u.%u\n", version.major, version.minor, version.build); - snprintf(buf, sizeof(buf) - 1, "moxa/moxa-%04x.fw", productid); + if (mxuport_is_mux50u_device(features)) + fw_version_offsets = mxuport_mux50u_fw_ver_offsets; + else + fw_version_offsets = mxuport_fw_ver_offsets; + + mxuport_get_fw_name(features, productid, buf, sizeof(buf)); err = request_firmware(&fw_p, buf, &serial->interface->dev); if (err) { @@ -1108,7 +1232,7 @@ static int mxuport_probe(struct usb_serial *serial, /* Use the firmware already in the device */ err = 0; } else { - err = mxuport_parse_fw_version(fw_p, mxuport_fw_ver_offsets, &local_ver); + err = mxuport_parse_fw_version(fw_p, fw_version_offsets, &local_ver); if (err) { dev_err(&serial->interface->dev, "Firmware %s is too short\n", buf); @@ -1119,7 +1243,17 @@ static int mxuport_probe(struct usb_serial *serial, "Available firmware version v%u.%u.%u\n", local_ver.major, local_ver.minor, local_ver.build); - if (local_ver.value > version.value) { + if (mxuport_is_mux50u_device(features)) { + err = mxuport_fw_running_from_sram(serial, &from_sram); + if (err) + goto out; + + download_fw = !from_sram; + } else { + download_fw = local_ver.value > version.value; + } + + if (download_fw) { err = mxuport_download_fw(serial, fw_p); if (err) goto out; From 296ebbfc856e25bf0f10af648b3fe899a381bfce Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Tue, 11 Aug 2026 14:04:59 +0800 Subject: [PATCH 148/158] USB: serial: add generic write control callbacks Some USB serial devices use device-specific flow control to determine whether write submission may proceed. The generic write path currently provides no way to pause submission based on driver-specific state. It also provides no way for a driver to restore its state if URB submission fails. Add an optional write_ready() callback to struct usb_serial_driver. Call it with port->lock held after confirming that data is queued and a write URB is available. If the callback returns false, stop write processing and leave the data in the FIFO. The driver must call usb_serial_generic_write_start() when writes may proceed again. Add an optional write_rollback() callback for restoring driver-specific state after usb_submit_urb() fails. Call it with port->lock held after restoring the generic transmit accounting, and pass it the length of the prepared transfer. Drivers which do not provide these callbacks retain the existing generic write behaviour. Signed-off-by: Crescent Hsieh Signed-off-by: Johan Hovold --- drivers/usb/serial/generic.c | 11 ++++++++--- include/linux/usb/serial.h | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 17272701fab0..ba78635b0392 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -152,6 +152,7 @@ int usb_serial_generic_prepare_write_buffer(struct usb_serial_port *port, int usb_serial_generic_write_start(struct usb_serial_port *port, gfp_t mem_flags) { + struct usb_serial_driver *type = port->serial->type; struct urb *urb; int count, result; unsigned long flags; @@ -161,7 +162,8 @@ int usb_serial_generic_write_start(struct usb_serial_port *port, return 0; retry: spin_lock_irqsave(&port->lock, flags); - if (!port->write_urbs_free || !kfifo_len(&port->write_fifo)) { + if (!port->write_urbs_free || !kfifo_len(&port->write_fifo) || + (type->write_ready && !type->write_ready(port))) { clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags); spin_unlock_irqrestore(&port->lock, flags); return 0; @@ -171,8 +173,7 @@ int usb_serial_generic_write_start(struct usb_serial_port *port, spin_unlock_irqrestore(&port->lock, flags); urb = port->write_urbs[i]; - count = port->serial->type->prepare_write_buffer(port, - urb->transfer_buffer, + count = type->prepare_write_buffer(port, urb->transfer_buffer, port->bulk_out_size); urb->transfer_buffer_length = count; usb_serial_debug_data(&port->dev, __func__, count, urb->transfer_buffer); @@ -188,6 +189,10 @@ int usb_serial_generic_write_start(struct usb_serial_port *port, set_bit(i, &port->write_urbs_free); spin_lock_irqsave(&port->lock, flags); port->tx_bytes -= count; + + if (type->write_rollback) + type->write_rollback(port, count); + spin_unlock_irqrestore(&port->lock, flags); clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index ab0a32a90629..5866f19d3c7b 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -305,9 +305,17 @@ struct usb_serial_driver { void (*write_bulk_callback)(struct urb *urb); /* Called by the generic read bulk callback */ void (*process_read_urb)(struct urb *urb); + /* + * Called with port->lock held before preparing another write URB. + * Drivers returning false must restart the generic write path when + * writes may proceed again. + */ + bool (*write_ready)(struct usb_serial_port *port); /* Called by the generic write implementation */ int (*prepare_write_buffer)(struct usb_serial_port *port, void *dest, size_t size); + /* Called with port->lock held to roll back a failed write submission */ + void (*write_rollback)(struct usb_serial_port *port, int count); }; #define to_usb_serial_driver(d) \ container_of(d, struct usb_serial_driver, driver) From 83b54ef616b729a6100d418970246e58d10d163f Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Tue, 11 Aug 2026 14:05:00 +0800 Subject: [PATCH 149/158] USB: serial: mxuport: handle SEND_NEXT transmit flow control The device uses a per-port SEND_NEXT request and event to pace host-to-device transmission. During burn-in testing, sustained writes without waiting for this event resulted in per-port buffer overflows and data errors. The issue can be reproduced on existing G1 devices as well. The driver initiates the protocol by setting the SEND_NEXT bit in a bulk-out header and then stops submitting data for that port. The firmware reports a SEND_NEXT event when the corresponding per-port buffer is empty, at which point the driver resumes transmission. Track the payload placed in bulk-out transfers for each port and set the request bit when it reaches bulk_out_size. Use the generic write control callbacks to hold further submissions until the event arrives and to roll back the payload count and hold state if usb_submit_urb() fails. Use bulk_out_size as a common threshold for all device families instead of encoding their firmware buffer sizes in the driver. Signed-off-by: Crescent Hsieh Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 61 +++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index 35123572ca4d..96dc85defb0b 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -156,6 +156,8 @@ static const u16 mxuport_mux50u_fw_ver_offsets[] = { #define UPORT_EVENT_LSR 4 /* Line status */ #define UPORT_EVENT_MCR 5 /* Modem control */ +#define UPORT_REQUEST_SEND_NEXT 0x80 + /* Definitions for serial event type */ #define SERIAL_EV_CTS 0x0008 /* CTS changed state */ #define SERIAL_EV_DSR 0x0010 /* DSR changed state */ @@ -202,6 +204,8 @@ static const u16 mxuport_mux50u_fw_ver_offsets[] = { /* This structure holds all of the local port information */ struct mxuport_port { + u32 sent_payload; + u8 hold_reason; u8 mcr_state; /* Last MCR state */ u8 msr_state; /* Last MSR state */ struct mutex mutex; /* Protects mcr_state */ @@ -256,6 +260,13 @@ static const struct usb_device_id mxuport_idtable[] = { MODULE_DEVICE_TABLE(usb, mxuport_idtable); +static bool mxuport_write_ready(struct usb_serial_port *port) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + + return !(mxport->hold_reason & MX_WAIT_FOR_SEND_NEXT); +} + /* * Add a four byte header containing the port number and the number of * bytes of data in the message. Return the number of bytes in the @@ -264,22 +275,40 @@ MODULE_DEVICE_TABLE(usb, mxuport_idtable); static int mxuport_prepare_write_buffer(struct usb_serial_port *port, void *dest, size_t size) { + struct mxuport_port *mxport = usb_get_serial_port_data(port); + bool request_send_next; + unsigned long flags; u8 *buf = dest; int count; - count = kfifo_out_locked(&port->write_fifo, buf + HEADER_SIZE, - size - HEADER_SIZE, - &port->lock); + spin_lock_irqsave(&port->lock, flags); + count = kfifo_out(&port->write_fifo, buf + HEADER_SIZE, size - HEADER_SIZE); + mxport->sent_payload += count; + request_send_next = mxport->sent_payload >= port->bulk_out_size; + if (request_send_next) + mxport->hold_reason |= MX_WAIT_FOR_SEND_NEXT; + spin_unlock_irqrestore(&port->lock, flags); put_unaligned_be16(port->port_number, buf); put_unaligned_be16(count, buf + 2); + if (request_send_next) + buf[0] |= UPORT_REQUEST_SEND_NEXT; + dev_dbg(&port->dev, "%s - size %zd count %d\n", __func__, size, count); return count + HEADER_SIZE; } +static void mxuport_write_rollback(struct usb_serial_port *port, int count) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + + mxport->sent_payload -= count - HEADER_SIZE; + mxport->hold_reason &= ~MX_WAIT_FOR_SEND_NEXT; +} + /* Read the given buffer in from the control pipe. */ static int mxuport_recv_ctrl_urb(struct usb_serial *serial, u8 request, u16 value, u16 index, @@ -498,14 +527,24 @@ static void mxuport_lsr_event(struct usb_serial_port *port, u8 buf[4]) static void mxuport_process_read_urb_event(struct usb_serial_port *port, u8 buf[4], u32 event) { + struct mxuport_port *mxport = usb_get_serial_port_data(port); + bool resume_tx = false; + unsigned long flags; + dev_dbg(&port->dev, "%s - receive event : %04x\n", __func__, event); switch (event) { case UPORT_EVENT_SEND_NEXT: - /* - * Sent as part of the flow control on device buffers. - * Not currently used. - */ + spin_lock_irqsave(&port->lock, flags); + if (mxport->hold_reason & MX_WAIT_FOR_SEND_NEXT) { + mxport->hold_reason &= ~MX_WAIT_FOR_SEND_NEXT; + mxport->sent_payload = 0; + resume_tx = true; + } + spin_unlock_irqrestore(&port->lock, flags); + + if (resume_tx) + usb_serial_generic_write_start(port, GFP_ATOMIC); break; case UPORT_EVENT_MSR: mxuport_msr_event(port, buf); @@ -1353,6 +1392,7 @@ static int mxuport_open(struct tty_struct *tty, struct usb_serial_port *port) { struct mxuport_port *mxport = usb_get_serial_port_data(port); struct usb_serial *serial = port->serial; + unsigned long flags; int err; /* Set receive host (enable) */ @@ -1373,6 +1413,11 @@ static int mxuport_open(struct tty_struct *tty, struct usb_serial_port *port) if (tty) mxuport_set_termios(tty, port, NULL); + spin_lock_irqsave(&port->lock, flags); + mxport->sent_payload = 0; + mxport->hold_reason &= ~MX_WAIT_FOR_SEND_NEXT; + spin_unlock_irqrestore(&port->lock, flags); + /* * TODO: use RQ_VENDOR_GET_MSR, once we know what it * returns. @@ -1466,7 +1511,9 @@ static struct usb_serial_driver mxuport_device = { .tiocmset = mxuport_tiocmset, .dtr_rts = mxuport_dtr_rts, .process_read_urb = mxuport_process_read_urb, + .write_ready = mxuport_write_ready, .prepare_write_buffer = mxuport_prepare_write_buffer, + .write_rollback = mxuport_write_rollback, .resume = mxuport_resume, }; From d446199711161e35c7f97f1556753c81546fe0e2 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Tue, 11 Aug 2026 14:05:01 +0800 Subject: [PATCH 150/158] USB: serial: mxuport: support RS485 mode configuration Add support for TIOCSRS485 and TIOCGRS485 so that userspace can configure the serial interface mode using struct serial_rs485. Map the serial_rs485 flags to the device interface modes as follows: - RS232 = no flags set - RS422 = SER_RS485_ENABLED | SER_RS485_MODE_RS422 - RS485_2W (half-duplex) = SER_RS485_ENABLED - RS485_4W (full-duplex) = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX Unsupported flags and fields are cleared before applying the configuration. Cache the sanitized per-port configuration and serialize access with the per-port mutex. Signed-off-by: Crescent Hsieh Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 98 +++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index 96dc85defb0b..319704e64528 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -208,7 +208,8 @@ struct mxuport_port { u8 hold_reason; u8 mcr_state; /* Last MCR state */ u8 msr_state; /* Last MSR state */ - struct mutex mutex; /* Protects mcr_state */ + struct serial_rs485 rs485; + struct mutex mutex; /* Protects per-port control state */ spinlock_t spinlock; /* Protects msr_state */ }; @@ -870,6 +871,100 @@ static int mxuport_tiocmget(struct tty_struct *tty) return result; } +static void mxuport_sanitize_serial_rs485(struct serial_rs485 *rs485) +{ + if (!(rs485->flags & SER_RS485_ENABLED)) { + memset(rs485, 0, sizeof(*rs485)); + return; + } + + if (rs485->flags & SER_RS485_MODE_RS422) + rs485->flags &= SER_RS485_ENABLED | SER_RS485_MODE_RS422; + else + rs485->flags &= SER_RS485_ENABLED | SER_RS485_RX_DURING_TX; + + rs485->delay_rts_before_send = 0; + rs485->delay_rts_after_send = 0; + memset(rs485->padding, 0, sizeof(rs485->padding)); +} + +static int mxuport_rs485_config(struct usb_serial_port *port, + const struct serial_rs485 *rs485) +{ + struct usb_serial *serial = port->serial; + u16 mode = MX_INT_RS232; + + if (rs485->flags & SER_RS485_ENABLED) { + if (rs485->flags & SER_RS485_MODE_RS422) + mode = MX_INT_RS422; + else if (rs485->flags & SER_RS485_RX_DURING_TX) + mode = MX_INT_4W_RS485; + else + mode = MX_INT_2W_RS485; + } + + return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_INTERFACE, mode, + port->port_number); +} + +static int mxuport_get_rs485_config(struct usb_serial_port *port, + struct serial_rs485 __user *argp) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + struct serial_rs485 rs485; + + mutex_lock(&mxport->mutex); + rs485 = mxport->rs485; + mutex_unlock(&mxport->mutex); + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; + + return 0; +} + +static int mxuport_set_rs485_config(struct usb_serial_port *port, + struct serial_rs485 __user *argp) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + struct serial_rs485 rs485; + int err; + + if (copy_from_user(&rs485, argp, sizeof(rs485))) + return -EFAULT; + + mxuport_sanitize_serial_rs485(&rs485); + + mutex_lock(&mxport->mutex); + err = mxuport_rs485_config(port, &rs485); + if (!err) + mxport->rs485 = rs485; + mutex_unlock(&mxport->mutex); + if (err) + return err; + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; + + return 0; +} + +static int mxuport_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case TIOCGRS485: + return mxuport_get_rs485_config(port, argp); + case TIOCSRS485: + return mxuport_set_rs485_config(port, argp); + } + + return -ENOIOCTLCMD; +} + static int mxuport_set_termios_flow(struct tty_struct *tty, const struct ktermios *old_termios, struct usb_serial_port *port, @@ -1503,6 +1598,7 @@ static struct usb_serial_driver mxuport_device = { .set_termios = mxuport_set_termios, .break_ctl = mxuport_break_ctl, .tx_empty = mxuport_tx_empty, + .ioctl = mxuport_ioctl, .tiocmiwait = usb_serial_generic_tiocmiwait, .get_icount = usb_serial_generic_get_icount, .throttle = mxuport_throttle, From d819aef107ca2100e1f822f7ab3b9b6c429c4292 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 15 Oct 2026 12:00:46 -0400 Subject: [PATCH 151/158] USB: Documentation: Update description of completion callback context The existing kerneldoc documentation for URB completion callbacks is incorrect about the context in which the callbacks occur; it is not always in_interrupt. State explicitly that a callback may occur in any context with no guarantees, and likewise for the status of CPU-local interrupts. Documentation update only; no code changes. Signed-off-by: Alan Stern Link: https://patch.msgid.link/de582feb-a0aa-4fc8-9818-890014de32a9@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/urb.c | 8 ++++++++ include/linux/usb.h | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 19e2c21420b8..ae0d61b5081f 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -254,6 +254,14 @@ EXPORT_SYMBOL_GPL(usb_urb_ep_type_check); * request. The completion handler may then immediately free or reuse that * URB. * + * The USB core makes no guarantees at all about the context in which + * the complete() callback is invoked. It may be in process context, + * interrupt context, BH context, or anything else. Also, local interrupts + * may be enabled or disabled. To deal with this, the callback routine + * must always use the _irqsave and _irqrestore variants of things like + * spin_lock(), and it must always use GFP_ATOMIC for its memory + * allocation flags. + * * With few exceptions, USB device drivers should never access URB fields * provided by usbcore or the HCD until its complete() is called. * The exceptions relate to periodic transfer scheduling. For both diff --git a/include/linux/usb.h b/include/linux/usb.h index 4377eeae541e..825f03b29067 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1608,7 +1608,12 @@ typedef void (*usb_complete_t)(struct urb *); * * Completion Callbacks: * - * The completion callback is made in_interrupt(), and one of the first + * The completion callback can occur in any context: process, interrupt, + * BH, or anything else. Also, interrupts may or may not be disabled on + * the local CPU when the callback runs. A completion handler routine + * must not depend on any particular properties of its environment. + * + * One of the first * things that a completion handler should do is check the status field. * The status field is provided for all URBs. It is used to report * unlinked URBs, and status for all non-ISO transfers. It should not From 04ac7b445880b0878c83e6e89d961455cbea0427 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Thu, 15 Oct 2026 15:24:57 +0000 Subject: [PATCH 152/158] MAINTAINERS: add Neill Kapron as USB Gadget maintainer Restore the USB GADGET/PERIPHERAL SUBSYSTEM entry and add myself as maintainer to help review and maintain USB gadget drivers, core framework, headers, and documentation. Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20261015152458.3541767-1-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 65e8a4b5c90b..16560ddf7b7b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28220,6 +28220,28 @@ S: Maintained F: Documentation/usb/ehci.rst F: drivers/usb/host/ehci* +USB GADGET/PERIPHERAL SUBSYSTEM +M: Neill Kapron +L: linux-usb@vger.kernel.org +S: Supported +T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git +F: Documentation/ABI/testing/configfs-usb-gadget* +F: Documentation/ABI/testing/sysfs-devices-platform-_UDC_-gadget +F: Documentation/driver-api/usb/gadget.rst +F: Documentation/usb/functionfs* +F: Documentation/usb/gadget* +F: Documentation/usb/mass-storage.rst +F: drivers/usb/gadget/ +F: include/linux/usb/composite.h +F: include/linux/usb/func_utils.h +F: include/linux/usb/functionfs.h +F: include/linux/usb/g_hid.h +F: include/linux/usb/gadget* +F: include/uapi/linux/usb/functionfs.h +F: include/uapi/linux/usb/g_*.h +F: include/uapi/linux/usb/gadgetfs.h +F: tools/usb/ffs-* + USB HID/HIDBP DRIVERS (USB KEYBOARDS, MICE, REMOTE CONTROLS, ...) M: Jiri Kosina M: Benjamin Tissoires From 9cbbd0c18603a18b01abcdaeee24c133db2e9f2e Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 15 Oct 2026 09:48:31 +0200 Subject: [PATCH 153/158] USB: core: document purpose of interface parent reference USB interfaces hold a reference to their parent USB device to make sure they do not outlive the parent. Add a comment explaining that this reference is specifically needed for asynchronous device resets and the device type need_parent_lock feature (to allow driver core to lock the parent when only holding a reference to the interface). See commits: 524134d42231 ("USB: don't cancel queued resets when unbinding drivers") 0f554506a3fc ("driver core: fix parent lock use-after-free on USB disconnect") for further details. Signed-off-by: Johan Hovold Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/20261015074831.1418677-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 0cd6dd2b6334..8c4f97c6e9f9 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -2208,6 +2208,11 @@ int usb_set_configuration(struct usb_device *dev, int configuration) pm_runtime_no_callbacks(&intf->dev); dev_set_name(&intf->dev, "%d-%s:%d.%d", dev->bus->busnum, dev->devpath, configuration, ifnum); + /* + * Make sure the interface does not outlive its parent (e.g. + * as needed for usb_queue_reset_device() and + * usb_if_device_type->need_parent_lock). + */ usb_get_dev(dev); } kfree(new_interfaces); From cb3b441f2698455a48e9a023d5f7f4e3fcb94c73 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Le Kien Date: Tue, 11 Aug 2026 14:08:10 +0800 Subject: [PATCH 154/158] usb: gadget: fix refcount underflow in gadgetfs_bind() error path gadgetfs_bind() calls get_dev() only on the success path, but gadgetfs_unbind() called from the enomem error label unconditionally calls put_dev(). When bind fails (e.g. ENOMEM), put_dev() fires without a matching get_dev(), leaving the refcount unbalanced. A subsequent close of the ep0 file descriptor calls dev_release() -> put_dev() which hits zero and frees the object; then gadgetfs_kill_sb() calls put_dev(the_device) again on the already-freed pointer, triggering a refcount underflow and use-after-free. Fix by calling get_dev() at the start of gadgetfs_bind(), before any error path that invokes gadgetfs_unbind(), so the reference is always balanced regardless of whether bind succeeds or fails. Reported-by: syzbot+8496ab5e117502750445@syzkaller.appspotmail.com Cc: stable Closes: https://syzkaller.appspot.com/bug?extid=8496ab5e117502750445 Signed-off-by: Nguyen Quang Le Kien Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260811060810.295666-1-khiemtranzo532001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index d5cec67bab17..25fcc4992d8b 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1813,6 +1813,8 @@ static int gadgetfs_bind(struct usb_gadget *gadget, dev->gadget = gadget; gadget->ep0->driver_data = dev; + get_dev (dev); + /* preallocate control response and buffer */ dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); if (!dev->req) @@ -1827,7 +1829,6 @@ static int gadgetfs_bind(struct usb_gadget *gadget, spin_lock_irq(&dev->lock); dev->state = STATE_DEV_UNCONNECTED; spin_unlock_irq(&dev->lock); - get_dev (dev); return 0; enomem: From e68f245bcb1f863aeb8eab617bd06878c078029a Mon Sep 17 00:00:00 2001 From: Igor Skalkin Date: Wed, 12 Aug 2026 10:19:09 +0200 Subject: [PATCH 155/158] usb: gadget: dummy_hcd: fix SuperSpeed ep0 maxpacket For USB_SPEED_SUPER, dummy_hcd sets ep0 maxpacket to 9. That is incorrect for SuperSpeed ep0 and breaks short packet handling in control transfer tests. Set ep0 maxpacket to 512 for SuperSpeed. Fixes: 1cd8fd2887e1 ("usb: gadget: dummy_hcd: add SuperSpeed support") Acked-by: Alan Stern Signed-off-by: Igor Skalkin Link: https://patch.msgid.link/20260812081910.6821-2-igor.skalkin@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 0d30dd67b9ca..d3076a429b40 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -918,7 +918,7 @@ static int dummy_set_selfpowered(struct usb_gadget *_gadget, int value) static void dummy_udc_update_ep0(struct dummy *dum) { if (dum->gadget.speed == USB_SPEED_SUPER) - dum->ep[0].ep.maxpacket = 9; + dum->ep[0].ep.maxpacket = 512; else dum->ep[0].ep.maxpacket = 64; } From 8d22d6da17d9612963a08965c3784a191dc66cec Mon Sep 17 00:00:00 2001 From: Igor Skalkin Date: Wed, 12 Aug 2026 10:19:10 +0200 Subject: [PATCH 156/158] usb: gadget: dummy_hcd: set no_sg_constraint only on the SuperSpeed bus dummy_hcd registers two separate root hubs/buses: a HighSpeed one emulating an EHCI-class controller, and a SuperSpeed one emulating an xHCI-class controller. usb_submit_urb() rejects SG URBs whose non-final segments are not a multiple of the endpoint maxpacket size, unless the bus has no_sg_constraint set. For SuperSpeed bulk endpoints maxpacket is 1024 bytes; usbtest generates SG lists with varying segment sizes (e.g. 512 bytes) that are valid transfers but not maxpacket-aligned, causing usb_submit_urb to return -EINVAL before the URB reaches the host controller. xHCI-class controllers have no such DMA alignment requirement, so set no_sg_constraint on the SS bus to match. Leave the HS bus unchanged: EHCI-class controllers do have this requirement in hardware, and dummy_hcd's HS roothub is meant to emulate that behaviour, so the constraint should stay enforced there. Suggested-by: Alan Stern Assisted-by: OpenCode:claude-sonnet-5 Signed-off-by: Igor Skalkin Acked-by: Alan Stern Link: https://patch.msgid.link/20260812081910.6821-3-igor.skalkin@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index d3076a429b40..27cb3b25e481 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -2596,6 +2596,15 @@ static int dummy_setup(struct usb_hcd *hcd) dum->ss_hcd->dum = dum; hcd->speed = HCD_USB3; hcd->self.root_hub->speed = USB_SPEED_SUPER; + + /* + * xHCI-class SuperSpeed controllers have no maxpacket + * alignment requirement for non-final SG segments. Only + * relax the constraint on the SS bus; the HS bus keeps + * emulating the alignment requirement of EHCI-class + * controllers. + */ + hcd->self.no_sg_constraint = 1; } return 0; } From ab248e0b900ea2e30bec5c71fd56d8f0fa87a67a Mon Sep 17 00:00:00 2001 From: Mikhail Zubenko Date: Mon, 24 Aug 2026 15:54:28 +0000 Subject: [PATCH 157/158] usb: gadget: dummy_hcd: add tick_us module parameter for timer-rate tuning dummy_hcd services URB completions from an hrtimer callback that rearms itself with a fixed 125 us delay (one high-speed microframe) for as long as any URBs are queued. Against a permanently polled HID interrupt endpoint the queue never drains, so the timer runs at ~8 kHz regardless of actual traffic. On timer-rate-sensitive platforms (observed on one consumer AM4 board with a current BIOS and a stock kernel) this timer rate co-existing with a live Bluetooth session on the machine's real xHCI controller resulted in a hard, below-software platform hang: printk, SysRq, NMI and both watchdogs died in the same instant, with no panic, no MCE, and empty pstore. 13 occurrences over 5 days, reliably reproducible with that configuration (hangs within 20 min to 2.5 h of uptime). The same workload with the emulated microframe lengthened to 1000 us (this parameter) ran ~8 hours with zero hangs, and USB-cable sessions (identical gadget traffic, no Bluetooth co-existence) never hung. Note that lengthening the emulated microframe makes the emulation run slower than a physical device would; users of the parameter trade that emulation fidelity for interrupt-rate headroom. The per-frame bandwidth budget in dummy_timer() does not scale with the emulated microframe length; making that accounting microframe- correct is planned as a follow-up patch. Otherwise nothing in the driver's behaviour depends on the emulated microframe length. Default keeps the current 125 us behaviour bit-for-bit (and defaults to 1000 us for full-speed emulation, where the natural unit is the 1-ms frame). Values below 125 are rejected. Signed-off-by: Mikhail Zubenko Reviewed-by: Alan Stern Link: https://patch.msgid.link/178758686883.288478.6637939361965647417@yandex.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 48 +++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 27cb3b25e481..4da008bb39f8 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -51,8 +51,6 @@ #define POWER_BUDGET 500 /* in mA; use 8 for low-power port testing */ #define POWER_BUDGET_3 900 /* in mA */ -#define DUMMY_TIMER_INT_NSECS 125000 /* 1 microframe */ - static const char driver_name[] = "dummy_hcd"; static const char driver_desc[] = "USB Host+Gadget Emulator"; @@ -66,6 +64,31 @@ struct dummy_hcd_module_parameters { bool is_super_speed; bool is_high_speed; unsigned int num; + unsigned int tick_us; +}; + +/* + * Length in microseconds of an emulated microframe. The default (125, + * one high-speed microframe) preserves the historical timer rate; larger + * values slow the emulated controller down and reduce its interrupt-rate + * pressure on the host platform. Values below 125 are rejected. + */ +static int tick_us_set(const char *val, + const struct kernel_param *kp) +{ + unsigned int v; + int ret = kstrtouint(val, 0, &v); + + if (ret < 0 || v < 125) { + pr_err("dummy_hcd: tick_us must be >= 125\n"); + return -EINVAL; + } + return param_set_uint(val, kp); +} + +static const struct kernel_param_ops tick_us_ops = { + .set = tick_us_set, + .get = param_get_uint, }; static struct dummy_hcd_module_parameters mod_data = { @@ -79,6 +102,9 @@ module_param_named(is_high_speed, mod_data.is_high_speed, bool, S_IRUGO); MODULE_PARM_DESC(is_high_speed, "true to simulate HighSpeed connection"); module_param_named(num, mod_data.num, uint, S_IRUGO); MODULE_PARM_DESC(num, "number of emulated controllers"); +module_param_cb(tick_us, &tick_us_ops, &mod_data.tick_us, 0444); +MODULE_PARM_DESC(tick_us, + "Length in microseconds of an emulated (micro)frame (default 125 for high/super-speed emulation, 1000 for full-speed); larger values reduce the interrupt-rate pressure on the host platform"); /*-------------------------------------------------------------------------*/ /* gadget side driver data structures */ @@ -244,6 +270,7 @@ struct dummy_hcd { struct dummy *dum; enum dummy_rh_state rh_state; struct hrtimer timer; + ktime_t timer_interval; /* emulated microframe length */ u32 port_status; u32 old_status; unsigned long re_timeout; @@ -1333,7 +1360,7 @@ static int dummy_urb_enqueue( /* kick the scheduler, it'll do the rest */ if (!dum_hcd->timer_pending) { dum_hcd->timer_pending = 1; - hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS), + hrtimer_start(&dum_hcd->timer, dum_hcd->timer_interval, HRTIMER_MODE_REL_SOFT); } @@ -2033,7 +2060,7 @@ static enum hrtimer_restart dummy_timer(struct hrtimer *t) dum_hcd->rh_state == DUMMY_RH_RUNNING) { /* want a 1 msec delay here */ dum_hcd->timer_pending = 1; - hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS), + hrtimer_start(&dum_hcd->timer, dum_hcd->timer_interval, HRTIMER_MODE_REL_SOFT); } @@ -2513,6 +2540,7 @@ static DEVICE_ATTR_RO(urbs); static int dummy_start_ss(struct dummy_hcd *dum_hcd) { hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + dum_hcd->timer_interval = ns_to_ktime((u64)mod_data.tick_us * NSEC_PER_USEC); dum_hcd->rh_state = DUMMY_RH_RUNNING; dum_hcd->stream_en_ep = 0; INIT_LIST_HEAD(&dum_hcd->urbp_list); @@ -2542,6 +2570,7 @@ static int dummy_start(struct usb_hcd *hcd) spin_lock_init(&dum_hcd->dum->lock); hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + dum_hcd->timer_interval = ns_to_ktime((u64)mod_data.tick_us * NSEC_PER_USEC); dum_hcd->rh_state = DUMMY_RH_RUNNING; INIT_LIST_HEAD(&dum_hcd->urbp_list); @@ -2841,6 +2870,17 @@ static int __init dummy_hcd_init(void) return -EINVAL; } + /* + * The emulated microframe length defaults to one high-speed + * microframe (125 us). When neither is_high_speed nor + * is_super_speed is set the emulation is full-speed, where the + * natural scheduling unit is the 1-ms frame, so default to + * 1000 us instead. + */ + if (!mod_data.tick_us) + mod_data.tick_us = (mod_data.is_super_speed || + mod_data.is_high_speed) ? 125 : 1000; + for (i = 0; i < mod_data.num; i++) { the_hcd_pdev[i] = platform_device_alloc(driver_name, i); if (!the_hcd_pdev[i]) { From bdf12cde30830d46a8ef1ba93986356f1164b4f1 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Wed, 29 Jul 2026 12:25:27 +0000 Subject: [PATCH 158/158] usb: gadget: dummy_hcd: prevent unbind via sysfs The dummy_hcd (Host Controller) and dummy_udc (Device Controller) are implemented as two separate platform drivers that share the same underlying state (struct dummy). They are designed to be created and destroyed together during the module's init and exit phases. If userspace forces the dummy_hcd driver to unbind from the device via sysfs, dummy_hcd_remove() cleans up the host controllers and sets the pointers to NULL (dum->hs_hcd = NULL). However, the dummy_udc driver is still bound to its device, meaning the USB gadget is still registered with the udc-core subsystem. If a new gadget driver is subsequently registered, it binds to the still-registered dummy_udc gadget, invoking the dummy_udc_start() callback. This callback attempts to retrieve the HCD state using gadget_to_dummy_hcd(), which returns NULL because of the unbind. This results in a null-pointer dereference: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] ... RIP: 0010:dummy_udc_start+0xd0/0x1f0 drivers/usb/gadget/udc/dummy_hcd.c:1022 ... Call Trace: usb_gadget_udc_start_locked drivers/usb/gadget/udc/core.c:1237 [inline] gadget_bind_driver+0x3a8/0x9e0 drivers/usb/gadget/udc/core.c:1667 call_driver_probe drivers/base/dd.c:-1 [inline] really_probe+0x254/0xae0 drivers/base/dd.c:706 __driver_probe_device+0x1e8/0x360 drivers/base/dd.c:868 driver_probe_device+0x4f/0x240 drivers/base/dd.c:898 __driver_attach+0x339/0x600 drivers/base/dd.c:1292 bus_for_each_dev+0x23b/0x2c0 drivers/base/bus.c:383 bus_add_driver+0x345/0x670 drivers/base/bus.c:763 driver_register+0x23a/0x320 drivers/base/driver.c:174 usb_gadget_register_driver_owner+0xf9/0x270 drivers/usb/gadget/udc/core.c:1752 raw_ioctl_run drivers/usb/gadget/legacy/raw_gadget.c:596 [inline] raw_ioctl+0x1541/0x41c0 drivers/usb/gadget/legacy/raw_gadget.c:1307 Adding NULL checks to the UDC callbacks is not a robust solution because there is no synchronization between dummy_hcd_remove() and the UDC callbacks, which would result in a use-after-free race condition if the HCD were unbound concurrently. Fix this by setting .suppress_bind_attrs = true in both driver definitions. This prevents userspace from unbinding these drivers dynamically, ensuring their lifecycles remain strictly synchronized with the module's load/unload process. Fixes: d9b762510c18 ("[PATCH] USB dummy_hcd: Use separate pdevs for HC and UDC") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+d270af829aca04e783fc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d270af829aca04e783fc Link: https://syzkaller.appspot.com/ai_job?id=7a763ada-c07c-458f-9fea-f7e913dc1f2f Signed-off-by: Aleksandr Nogikh Acked-by: Alan Stern Link: https://patch.msgid.link/18ec3122-7566-49e5-9964-1028857405b1@mail.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 4da008bb39f8..bbfbd287b8a0 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1211,6 +1211,7 @@ static struct platform_driver dummy_udc_driver = { .resume = dummy_udc_resume, .driver = { .name = gadget_name, + .suppress_bind_attrs = true, }, }; @@ -2844,6 +2845,7 @@ static struct platform_driver dummy_hcd_driver = { .resume = dummy_hcd_resume, .driver = { .name = driver_name, + .suppress_bind_attrs = true, }, };