diff --git a/Documentation/ABI/obsolete/sysfs-bus-usb b/Documentation/ABI/obsolete/sysfs-bus-usb index bd096d33fbc7..067016e62e11 100644 --- a/Documentation/ABI/obsolete/sysfs-bus-usb +++ b/Documentation/ABI/obsolete/sysfs-bus-usb @@ -26,6 +26,6 @@ Description: initializes all non-hub devices in the "on" level. Some drivers may change this setting when they are bound. - This file is deprecated and will be removed after 2010. + This file is deprecated. Use the power/control file instead; it does exactly the same thing. diff --git a/Documentation/ABI/testing/configfs-thunderbolt_stream b/Documentation/ABI/testing/configfs-thunderbolt_stream index 7abc6b73a1e4..cbecb3d8db50 100644 --- a/Documentation/ABI/testing/configfs-thunderbolt_stream +++ b/Documentation/ABI/testing/configfs-thunderbolt_stream @@ -27,6 +27,21 @@ Description: default values. If there is an advertised remote stream with the same name, uses its values as the default. +What: /sys/kernel/config/thunderbolt/stream/./$name/busy_poll +Date: Nov 2026 +KernelVersion: v7.3 +Contact: Mika Westerberg +Description: + Instead of using interrupts for completing the frames in + the TX/RX rings, busy poll them directly from the + read(2) and write(2) calls. This burns more CPU cycles + but provides lower latency for applications that need it. + + This also makes poll(2) return EPOLLERR because + interrupts do not provide wakeup anymore. Likewise a + blocking read(2) without available data busy-spins until + data arrives or a signal is received. + What: /sys/kernel/config/thunderbolt/stream/./$name/index Date: Sep 2026 KernelVersion: v7.2 diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy index b2eaefd9bc49..6a681d219f43 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy @@ -5,8 +5,5 @@ Description: The attributes: audio_buf_size - audio buffer size - fn_cap - capture pcm device file name - fn_cntl - control device file name - fn_play - playback pcm device file name req_buf_size - ISO OUT endpoint request buffer size req_count - ISO OUT endpoint request count diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index af9b653422f1..baf4bf6d32ee 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -293,7 +293,7 @@ Description: Supported values are 0 - 15. More information on how besl values map to microseconds can be found in - USB 2.0 ECN Errata for Link Power Management, section 4.10) + USB 2.0 ECN Errata for Link Power Management, section 4.10 What: /sys/bus/usb/devices/.../rx_lanes Date: March 2018 diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 91a6cb109988..ff25fe853706 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -294,8 +294,8 @@ for the retimers:: This enumerates and adds the on-board retimers. Now retimer NVM can be upgraded in the same way than with cable connected (see previous -section). However, the retimer is not disconnected as we are offline -mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for +section). However, the retimer is not disconnected as we are in offline +mode, so after writing ``1`` to ``nvm_authenticate`` one should wait for 5 or more seconds before running rescan again:: # echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan diff --git a/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml b/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml new file mode 100644 index 000000000000..fff5a200f8c7 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/aspeed,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Aspeed SuperSpeed DWC3 USB SoC controller + +maintainers: + - Ryan Chen + +description: + The common content of the node is defined in snps,dwc3.yaml. + +select: + properties: + compatible: + contains: + const: aspeed,ast2700-xhci + required: + - compatible + +properties: + compatible: + items: + - const: aspeed,ast2700-xhci + - const: snps,dwc3 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Controller bus early clock + - description: PHY reference clock + - description: Controller suspend clock + + clock-names: + items: + - const: bus_early + - const: ref + - const: suspend + + resets: + maxItems: 1 + + phys: + maxItems: 1 + + phy-names: + const: usb3-phy + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - phys + - phy-names + +allOf: + - $ref: snps,dwc3.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + bus { + #address-cells = <2>; + #size-cells = <2>; + + usb@12030000 { + compatible = "aspeed,ast2700-xhci", "snps,dwc3"; + reg = <0x0 0x12030000 0x0 0x10000>; + interrupts = ; + clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>, + <&syscon0 SCU0_CLK_U2PHY_REFCLK>, + <&syscon0 SCU0_CLK_U2PHY_CLK12M>; + clock-names = "bus_early", "ref", "suspend"; + resets = <&syscon0 SCU0_RESET_PORTA_XHCI>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb3axh_default &pinctrl_usb2axh_default>; + phys = <&uphy3a>; + phy-names = "usb3-phy"; + dr_mode = "host"; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index e8082c5c05a2..c14a88aad31e 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -50,6 +50,9 @@ properties: - const: otg - const: wakeup + iommus: + maxItems: 1 + port: $ref: /schemas/graph.yaml#/properties/port description: diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 55a5aa7d7a54..ae9fb70d0212 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -98,6 +98,10 @@ properties: - if a USB DRD channel: first clock should be host and second one should be peripheral + clock-names: + minItems: 1 + maxItems: 4 + power-domains: maxItems: 1 @@ -186,6 +190,9 @@ allOf: required: - clocks - clock-names + else: + properties: + clock-names: false unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index d42f448fa204..322808aaa283 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -83,6 +83,10 @@ properties: - if a USB DRD channel: first clock should be host and second one should be peripheral + clock-names: + minItems: 1 + maxItems: 4 + power-domains: maxItems: 1 @@ -182,6 +186,7 @@ allOf: else: properties: + clock-names: false atmel,vbus-gpio: false atmel,oc-gpio: false diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml index 21fc6bbe954f..d148e938d647 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml @@ -28,6 +28,7 @@ properties: - mediatek,mt8188-mtu3 - mediatek,mt8192-mtu3 - mediatek,mt8195-mtu3 + - mediatek,mt8196-mtu3 - mediatek,mt8365-mtu3 - const: mediatek,mtu3 @@ -200,7 +201,10 @@ properties: 103 - used by mt8195, IP0, specific 1.03; 105 - used by mt8195, IP2, specific 1.05; 106 - used by mt8195, IP3, specific 1.06; - enum: [1, 2, 101, 102, 103, 105, 106] + 107 - used by mt8196, IP0, specific 1.07; + 108 - used by mt8196, IP1, specific 1.08; + 109 - used by mt8196, IP2, specific 1.09; + enum: [1, 2, 101, 102, 103, 105, 106, 107, 108, 109] mediatek,u3p-dis-msk: $ref: /schemas/types.yaml#/definitions/uint32 diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index d99af9f413d0..ea60f7220afe 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -29,6 +29,7 @@ properties: - qcom,glymur-dwc3-mp - qcom,ipq4019-dwc3 - qcom,ipq5018-dwc3 + - qcom,ipq5210-dwc3 - qcom,ipq5332-dwc3 - qcom,ipq5424-dwc3 - qcom,ipq6018-dwc3 @@ -36,6 +37,7 @@ properties: - qcom,ipq8074-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 + - qcom,maili-dwc3 - qcom,milos-dwc3 - qcom,msm8953-dwc3 - qcom,msm8994-dwc3 @@ -60,6 +62,7 @@ properties: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 + - qcom,shikra-dwc3 - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 @@ -203,9 +206,11 @@ allOf: compatible: contains: enum: + - qcom,ipq5210-dwc3 - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 + - qcom,maili-dwc3 - qcom,msm8953-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 @@ -218,6 +223,7 @@ allOf: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 + - qcom,shikra-dwc3 - qcom,sm6350-dwc3 - qcom,sm8750-dwc3 then: @@ -497,6 +503,7 @@ allOf: compatible: contains: enum: + - qcom,ipq5210-dwc3 - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 then: @@ -543,6 +550,7 @@ allOf: - qcom,ipq4019-dwc3 - qcom,ipq8064-dwc3 - qcom,kaanapali-dwc3 + - qcom,maili-dwc3 - qcom,qcs615-dwc3 - qcom,qcs8300-dwc3 - qcom,qdu1000-dwc3 @@ -556,6 +564,7 @@ allOf: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 + - qcom,shikra-dwc3 - qcom,sm6350-dwc3 - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 diff --git a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml index dc74e70f1b92..13715b7c94c8 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml @@ -27,6 +27,7 @@ properties: - renesas,usbhs-r9a07g044 # RZ/G2{L,LC} - renesas,usbhs-r9a07g054 # RZ/V2L - renesas,usbhs-r9a08g045 # RZ/G3S + - renesas,usbhs-r9a08g046 # RZ/G3L - renesas,usbhs-r9a09g047 # RZ/G3E - renesas,usbhs-r9a09g056 # RZ/V2N - renesas,usbhs-r9a09g057 # RZ/V2H(P) diff --git a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml index 1745e28b3110..2c589e9e712e 100644 --- a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml +++ b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml @@ -4,13 +4,14 @@ $id: http://devicetree.org/schemas/usb/ti,tps6598x.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Texas Instruments 6598x Type-C Port Switch and Power Delivery controller +title: Texas Instruments Type-C port switch and USB Power Delivery controllers maintainers: - Bryan O'Donoghue description: | - Texas Instruments 6598x Type-C Port Switch and Power Delivery controller + Texas Instruments 6598x and 66993 Type-C Port Switch and Power Delivery + controller. A variant of this controller known as Apple CD321x or Apple ACE is also present on hardware with Apple SoCs such as the M1. @@ -19,6 +20,7 @@ properties: compatible: enum: - ti,tps6598x + - ti,tps66993 - apple,cd321x - ti,tps25750 diff --git a/Documentation/translations/zh_CN/usb/CREDITS b/Documentation/translations/zh_CN/usb/CREDITS index c133b1a5daff..ccfeced03ea6 100644 --- a/Documentation/translations/zh_CN/usb/CREDITS +++ b/Documentation/translations/zh_CN/usb/CREDITS @@ -10,12 +10,10 @@ :校译: -简易 Linux USB 驱动的致谢名单: +Simple Linux USB 驱动项目致谢名单: -以下人员都为 Linux USB 驱动代码作出了贡献 -(按姓氏字母顺序排列)。我相信这份名单本应 -更长一些,但确实不容易维护。 -如需将自己加入名单,请提交补丁。 +以下人员都为 Linux USB 驱动代码作出过贡献(按姓氏字母顺序排列)。这份名 +单本该更长,只是确实不易维护;如果你也应列名其中,欢迎提交补丁把自己加上。 Georg Acher David Brownell @@ -41,123 +39,124 @@ 特别感谢: Inaky Perez Gonzalez - 感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd - 驱动中的大部分代码。我们从那项工作中学到了很多。 + 感谢他牵头开发 Linux USB 驱动,并编写了 uusbd 驱动的大部分代码,我们 + 从中学到了很多。 NetBSD 和 FreeBSD 的 USB 开发者们 感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。 -附加感谢: - 还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助 - (摘自 Inaky 驱动原始的 THANKS 文件): +另外还要感谢: - 以下公司曾帮助我们开发 Linux USB / UUSBD: + 以下公司和个人在硬件、支持、时间和开发工作上给予了帮助(摘自 Inaky + 驱动原始的 THANKS 文件): - - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面为我 - 提供支持。没想到能得到这么大的帮助。 + 以下公司曾为 Linux USB / UUSBD 的开发提供帮助: - - USAR Systems 向我们提供了他们出色的 USB 评估套件, - 使我们能够测试 Linux USB 驱动对最新 USB 规范的符合性。 - USAR Systems 认识到保持开放操作系统与时俱进的重要性, - 并以硬件支持这个项目。感谢! + - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面提供 + 了大力支持。 + + - USAR Systems 向我们提供了出色的 USB 评估套件,使我们得以测试 + Linux USB 驱动对最新 USB 规范的符合性。USAR Systems 也认识到, + 让开放操作系统跟上时代很重要,因此以硬件支持了这个项目,在此 + 致谢。 - 感谢英特尔提供的宝贵帮助。 - 我们与 Cherry 合作,使 Linux 成为首个内置 USB 支持的操作系统。 Cherry 是全球最大的键盘制造商之一。 - - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI-to-USB - 控制卡,用于测试 OHCI 实现。 + - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI 转 USB 控制卡, + 用于测试 OHCI 实现。 - - 由于他们对我们的支持,Keytronic 可以放心, - 他们的键盘能卖给至少 300 万 Linux 用户中的一部分。 + - 有了他们的支持,Keytronic 可以确信,其键盘能够销售给至少 300 万 + Linux 用户中的一部分。 - - ing büro h doran [http://www.ibhdoran.com]! - 在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的 - (我自己做的那个相当糟糕 :))。现在我知道该去哪里买漂亮的 USB - 配件了! + - 特别感谢 ing büro h doran [http://www.ibhdoran.com]。 + 在欧洲,想给主板配一个 PC 背板 USB 连接器几乎是不可能的(我自己 + 做的那个效果并不好)。现在我知道该去哪里购买合适的 USB 配件了。 - Genius Germany 捐赠了一只 USB 鼠标,用于测试鼠标启动协议; - 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢! + 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro,在此致谢。 - - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB 驱动。 - AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux 在内的 - 所有操作系统平台开放。 + - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB + 驱动。AVM 是主动式和被动式 ISDN 控制器及基于 CAPI 2.0 软件的领先 + 制造商。AVM B1 的主动式设计对包括 Linux 在内的所有操作系统平台 + 开放。 - - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱, - 使我们能够测试批量传输代码。 + - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,使我们能够测试 + 批量传输代码。 - 感谢 Logitech 捐赠了一只三轴 USB 鼠标。 - Logitech 负责设计、制造并销售各种人机接口设备, - 在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的 - 控制设备方面拥有悠久历史和丰富经验。 + Logitech 负责设计、制造并销售各种人机接口设备,在键盘、鼠标、轨迹球、 + 摄像头、扬声器,以及面向游戏和专业用途的控制设备方面拥有悠久历史和 + 丰富经验。 - 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、 - 摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户 - 能在自己喜欢的操作系统上获得支持,并让所有 Linux 用户都能使用 - Logitech 以及其他 USB 硬件。 + 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、摇杆和 + 扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户能在自己偏爱的 + 操作系统上获得支持,并让所有 Linux 用户都能使用 Logitech 及其他 + USB 硬件。 Logitech 也是 1999 年 2 月 11 日维也纳 Linux 大会的官方赞助商, 我们将在会上展示 Linux USB 工作的最新进展。 - - 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI 内部实现中 - 那些不为人知的角落。 + - 感谢 CATC 提供 USB Inspector,帮助我们看到 UHCI 内部实现中的那些 + 隐秘角落。 - 感谢 Entrega 为开发工作提供 PCI 转 USB 卡、集线器和转换器产品。 - - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档, - 让这个驱动得以写成。 + - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,让 + 这个驱动得以写成。 - - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明, - 以及驱动开发过程中的宝贵建议。 + - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,以及驱动 + 开发过程中的宝贵建议。 - 另外还要感谢以下个人(嘿,顺序不分先后 :)) + 另外还要感谢以下个人(排名不分先后): - - Oren Tirosh , - 他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。 + - Oren Tirosh + 他非常耐心地解答我反复提出的各种 USB 问题,并提供了许多有价值的 + 想法。 - - Jochen Karrer , - 指出了致命 bug,并给出了宝贵建议。 + - Jochen Karrer + 指出了严重问题,并给出了宝贵建议。 - - Edmund Humemberger ,他在公共关系与项目管理方面 - 为 Linux-USB 项目付出了巨大的努力。 + - Edmund Humemberger ,他在公共关系与项目管理方面为 + Linux-USB 项目付出了巨大的努力。 - - Alberto Menegazzi 正在着手编写 UUSBD 文档,加油! + - Alberto Menegazzi 正在着手编写 UUSBD 文档。 - - Ric Klaren 编写了很好的入门文档, - 与 Alberto 的作品形成良性竞争:)。 + - Ric Klaren 编写了很好的入门文档,与 + Alberto 的作品形成了良性互补。 - - Christian Groessler ,感谢他在那些棘手细节上的帮助。 + - Christian Groessler ,感谢他在诸多复杂细节上的帮助。 - - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持, - 并提供了大量的改进意见。 + - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,并提供了 + 大量的改进意见。 - - Fernando Herrera - 负责撰写、维护并不断补充那份期待已久、独一无二又精彩的 - UUSBD FAQ!太棒了! + - Fernando Herrera 负责撰写、维护并 + 持续补充那份期待已久、内容翔实的 UUSBD FAQ。 - - Rasca Gmelch 重新启用了 raw 驱动, - 指出了一些错误,并启动了 uusbd-utils 软件包。 + - Rasca Gmelch 重新启用了 raw 驱动,指出了一些错误,并 + 启动了 uusbd-utils 软件包。 - - Peter Dettori ,像疯了一样挖掘 bug, - 还提出了很多很酷的建议,太棒了! + - Peter Dettori ,持续发现问题,并提出了许多 + 有价值的建议。 - - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、 - MIT X 联盟、TeX 社区等等,谢谢你们! + - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、MIT X 联盟、 + TeX 社区等,在此一并致谢。 - - 特别感谢 Richard Stallman 创造了 Emacs! + - 特别感谢 Richard Stallman 创造了 Emacs。 - - 感谢 linux-usb 邮件列表的所有成员,读了那么多邮件——不开玩笑了, - 感谢你们提出的所有建议! + - 感谢 linux-usb 邮件列表的所有成员阅读了大量邮件,并提出了诸多 + 建议。 - 感谢 USB Implementers Forum 成员们的帮助与支持。 - - Nathan Myers ,感谢他的建议! - (希望你喜欢 Cibeles 的派对。) + - Nathan Myers ,感谢他的建议。(也希望你喜欢 + Cibeles 的派对。) - - 感谢 Linus Torvalds 创建、开发并管理 Linux。 + - 感谢 Linus Torvalds 创立、开发并维护 Linux。 - Mike Smith、Craig Keithley、Thierry Giron 和 Janet Schank - 感谢他们让我认识到标准 USB 集线器其实也没那么“标准”, - 这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。 + 感谢他们让我认识到,标准 USB 集线器其实一点也不“标准”;也正因 + 如此,我们才能在标准集线器驱动中加入厂商特定的处理。 diff --git a/Documentation/translations/zh_CN/usb/acm.rst b/Documentation/translations/zh_CN/usb/acm.rst index 51d6eb8f5660..b2e35787af45 100644 --- a/Documentation/translations/zh_CN/usb/acm.rst +++ b/Documentation/translations/zh_CN/usb/acm.rst @@ -20,33 +20,26 @@ Linux ACM 驱动 v0.16 0. 免责声明 ~~~~~~~~~~~ -本程序是自由软件;你可以在自由软件基金会发布的 -GNU 通用公共许可证第 2 版,或者(按你的选择) -任何后续版本的条款下重新发布和/或修改它。 +本程序是自由软件;你可以在自由软件基金会发布的 GNU 通用公共许可证第 2 版, +或者(按你的选择)任何后续版本的条款下重新发布和/或修改它。 -发布本程序是希望它能发挥作用,但它不附带任何担保; -甚至不包括对适销性或特定用途适用性的默示担保。 -详情见 GNU 通用公共许可证。 +发布本程序是希望它能发挥作用,但它不附带任何担保;甚至不包括对适销性或 +特定用途适用性的默示担保。详情见 GNU 通用公共许可证。 -你应该已经随本程序收到了 GNU 通用公共许可证的副本; -如果没有,请致信:Free Software Foundation, Inc., 59 -Temple Place, Suite 330, Boston, MA 02111-1307 USA。 +你应该已经随本程序收到了 GNU 通用公共许可证的副本;如果没有,请参见 +COPYING 文件。 -如需联系作者,可发送电子邮件至 vojtech@suse.cz, -或邮寄至: -Vojtech Pavlik, Ucitelska 1576, Prague 8, -182 00, Czech Republic。 +如需联系作者,可发送电子邮件至 vojtech@suse.cz,或邮寄至: +Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00, Czech Republic。 -为方便起见,软件包中已附带 GNU 通用公共许可证 -第 2 版:见 COPYING 文件。 +为方便起见,软件包中已附带 GNU 通用公共许可证第 2 版:见 COPYING 文件。 1. 使用方法 ~~~~~~~~~~~ -``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB -通信设备类抽象控制模型(USB CDC ACM)规范的 -USB 调制解调器和 USB ISDN 终端适配器。 +``drivers/usb/class/cdc-acm.c`` 驱动适用于符合 USB 通信设备类抽象控制模型 +(USB CDC ACM)规范的 USB 调制解调器和 USB ISDN 终端适配器。 -许多调制解调器支持此驱动,以下是我所知道的一些型号: +已知支持该驱动的调制解调器包括: - 3Com OfficeConnect 56k - 3Com Voice FaxModem Pro @@ -56,17 +49,16 @@ USB 调制解调器和 USB ISDN 终端适配器。 - Compaq 56k FaxModem - ELSA Microlink 56k -我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用: +已知有一款 ISDN 终端适配器可以配合 ACM 驱动使用: - 3Com USR ISDN Pro TA -一些手机也可以通过 USB 连接。 -我知道以下机型可以正常工作: +一些手机也可以通过 USB 连接,已知可用的机型有: - SonyEricsson K800i -遗憾的是,许多调制解调器和大多数 ISDN TA -都使用专有接口,因此无法与此驱动配合工作。 +遗憾的是,很多调制解调器和大多数 ISDN TA 都使用专有接口,因此无法配合该 +驱动工作。 购买前请先确认设备是否符合 ACM 规范。 要使用这些调制解调器,需要加载以下模块:: @@ -75,15 +67,13 @@ USB 调制解调器和 USB ISDN 终端适配器。 uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko cdc-acm.ko -之后就应该可以访问这些调制解调器了。 -应当可以使用 ``minicom``、``ppp`` 和 ``mgetty`` -与它们通信。 +之后就应该能访问这些调制解调器,并用 ``minicom``、``ppp`` 和 +``mgetty`` 与它们通信。 2. 验证驱动是否正常工作 ~~~~~~~~~~~~~~~~~~~~~~~ -第一步是检查 ``/sys/kernel/debug/usb/devices``, -其内容应该类似如下:: +第一步是查看 ``/sys/kernel/debug/usb/devices``,其内容应当类似下面这样:: T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2 B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0 @@ -112,11 +102,10 @@ USB 调制解调器和 USB ISDN 终端适配器。 E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms -这三行的存在很关键(以及 ``Cls=`` 字段里出现的 -``comm`` 和 ``data`` 类);它说明这是一个 ACM -设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。 -如果只看到 ``Cls=ff(vend.)``,那就无能为力了: -这说明你手上的设备使用的是厂商专有接口:: +关键是看这三行,再结合 ``Cls=`` 字段里出现的 ``comm`` 和 ``data`` 类,就 +能判断这是一台 ACM 设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。如果 +只看到 ``Cls=ff(vend.)``,那就说明这台设备使用的是厂商专有接口,ACM 驱动 +无法处理:: D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2 I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm @@ -142,6 +131,5 @@ USB 调制解调器和 USB ISDN 终端适配器。 usb.c: acm driver claimed interface c7b5f3f8 usb.c: acm driver claimed interface c7691fa0 -如果以上都正常,请启动 ``minicom``, -把它配置为连接 ``ttyACM`` 设备,然后 -尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。 +如果这些都正常,请启动 ``minicom``,把它配置为连接到 ``ttyACM`` 设备,然后 +尝试输入 ``at``。如果返回 ``OK``,说明驱动工作正常。 diff --git a/Documentation/translations/zh_CN/usb/authorization.rst b/Documentation/translations/zh_CN/usb/authorization.rst index 2aa311f6b967..e2ff2282bd03 100644 --- a/Documentation/translations/zh_CN/usb/authorization.rst +++ b/Documentation/translations/zh_CN/usb/authorization.rst @@ -10,34 +10,32 @@ :校译: -============================= -授权或禁止 USB 设备连接到系统 -============================= +=========================== +允许或禁止 USB 设备接入系统 +=========================== 版权 (C) 2007 Inaky Perez-Gonzalez 英特尔公司 -此功能允许你控制 USB 设备是否可以在系统中使用。 -借助它,你可以完全通过用户空间实现对 USB 设备的锁定。 +有了这项功能,你就可以控制 USB 设备是否允许在系统中使用,并把 USB 设备锁 +定机制完全放在用户空间实现。 -目前,当插入一个 USB 设备时,系统会对其进行配置, -其接口会立即向用户开放。 -有了这项改动,只有在 root 授权设备完成配置后, -设备才可被使用。 +目前,USB 设备一接入系统就会被立即配置,其接口也会立刻向用户开放。引入 +这项机制后,只有在 root 明确授权后,设备才会完成配置并允许使用。 用法 ==== -授权设备接入:: +授权设备接入系统:: $ echo 1 > /sys/bus/usb/devices/DEVICE/authorized -取消对设备的授权:: +取消设备授权:: $ echo 0 > /sys/bus/usb/devices/DEVICE/authorized -将新连接到 ``hostX`` 的设备默认设为未授权(即锁定):: +将连接到 ``hostX`` 的新设备默认设为未授权(即锁定):: $ echo 0 > /sys/bus/usb/devices/usbX/authorized_default @@ -45,15 +43,14 @@ $ echo 1 > /sys/bus/usb/devices/usbX/authorized_default -默认情况下,所有 USB 设备都是授权的。 -向 ``authorized_default`` 属性写入 ``2`` 会使内核 -默认只授权连接到内部 USB 端口的设备。 +默认情况下,所有 USB 设备都是授权的。向 ``authorized_default`` 属性写入 +``2`` 会使内核默认只授权连接到内部 USB 端口的设备。 -系统锁定示例(比较粗糙) ------------------------- +系统锁定示例(简化版) +---------------------- -假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入 -(例如某台带有外露 USB 端口的自助服务终端):: +假设你想做一个锁定机制,只允许 XYZ 类型的设备接入(例如一台带有外露 USB +端口的自助终端):: 启动系统 rc.local -> @@ -63,21 +60,18 @@ echo 0 > $host/authorized_default done -给 udev 挂一个脚本,用于处理新插入的 USB 设备:: +为 udev 配置一个脚本,用于处理新插入的 USB 设备:: if device_is_my_type $DEV then echo 1 > $device_path/authorized - done + fi -``device_is_my_type()`` 才是锁定方案真正见功夫的 -地方。仅仅检查 class、type 和 protocol 是否匹配 -某个值,是你能做出的最糟糕的安全验证之一; -对想绕过它的人来说,这反而是最容易利用的方案。 -如果你需要真正安全的办法,那就该使用加密、 -证书认证之类的机制。把 USB 存储设备当作 -“钥匙”的一个简单例子可以是:: +锁定方案是否可靠,关键全在 ``device_is_my_type()`` 的实现。仅仅检查 +class、type 和 protocol 是否匹配,几乎是最差的一种安全校验方式;对想绕过 +它的人来说,这种做法反而最容易伪造。如果你真要做安全控制,就该使用加密、 +证书认证之类的机制。把 USB 存储设备当作“钥匙”的一个简单示例可以写成:: function device_is_my_type() { @@ -87,7 +81,7 @@ sum=$(md5sum /mntpoint/.signature) if [ $sum = $(cat /etc/lockdown/keysum) ] then - echo "We are good, connected" + echo "验证通过,已连接" umount /mntpoint # 再做一些额外处理,让其他人也能使用它 else @@ -96,17 +90,16 @@ } -当然,这个例子很粗糙;真正要做的话, -你会想用基于 PKI 的证书校验,这样就不必依赖 -共享密钥之类的东西。不过你应该已经明白意思了。 -任何拿到设备仿真工具包的人都能伪造描述符和设备信息。 -别信这个。 +当然,这个例子仍然比较简化。真正落地时,更合适的做法是使用基于 PKI 的证 +书校验,这样就不必依赖共享密钥之类的机制了。不过意思已经很清楚:任何拿到 +设备仿真工具包的人,都能伪造描述符和设备信息,所以别把这类检查当成真正 +的安全保障。 接口授权 -------- -也有类似的方法用于允许或拒绝特定 USB 接口。 -这使得你可以只阻止某个 USB 设备中的部分接口。 +也可以用类似的方法允许或拒绝特定的 USB 接口。这样一来,你只需要阻止某个 +USB 设备中的部分接口。 授权接口:: @@ -126,14 +119,12 @@ $ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default -默认情况下, -``interface_authorized_default`` 位为 ``1``, -因此所有接口默认都处于已授权状态。 +默认情况下,``interface_authorized_default`` 位为 ``1``,因此所有接口默认 +都会处于授权状态。 注意: - 如果把一个先前未授权的接口改为已授权, - 则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe`` - 来手动触发驱动探测。 + 如果把一个先前未授权的接口改为已授权,则必须通过将 ``INTERFACE`` 写入 + ``/sys/bus/usb/drivers_probe`` 来手动触发驱动探测。 -对于需要多个接口的驱动程序,应先授权所有必需接口, -然后再触发驱动探测。这样做可以避免副作用。 +对于需要多个接口的驱动程序,应先授权所有必需接口,然后再触发驱动探测。 +这样做可以避免副作用。 diff --git a/Documentation/translations/zh_CN/usb/chipidea.rst b/Documentation/translations/zh_CN/usb/chipidea.rst index ea0dc3043189..ee5407a4ce44 100644 --- a/Documentation/translations/zh_CN/usb/chipidea.rst +++ b/Documentation/translations/zh_CN/usb/chipidea.rst @@ -17,18 +17,17 @@ ChipIdea 高速双角色控制器驱动 1. 如何测试 OTG FSM(HNP 和 SRP) --------------------------------- -下面以两块 Freescale i.MX6Q Sabre SD 开发板为例, -说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。 +下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,演示如何通过 sysfs 属性 +测试 OTG 的 HNP 和 SRP 功能。 -1.1 如何使能 OTG FSM +1.1 如何启用 OTG FSM -------------------- 1.1.1 在 ``menuconfig`` 中选择 ``CONFIG_USB_OTG_FSM``,并重新编译内核 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -重新构建内核镜像和模块。如果想查看 OTG FSM 的 -一些内部变量,可以挂载 ``debugfs``;其中有两个文件 -可以显示 OTG FSM 变量以及部分控制器寄存器的值:: +重新构建内核镜像和模块。如果想查看 OTG FSM 的内部变量,可以挂载 +``debugfs``;其中有两个文件,分别显示 OTG FSM 的变量和部分控制器寄存器值:: cat /sys/kernel/debug/ci_hdrc.0/otg cat /sys/kernel/debug/ci_hdrc.0/registers @@ -44,11 +43,10 @@ ChipIdea 高速双角色控制器驱动 1.2 测试步骤 ------------ -1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电, - 并加载 gadget 类驱动(例如 ``g_mass_storage``)。 +1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,并加载 gadget 类驱动(例如 + ``g_mass_storage``)。 -2) 用 USB 线连接两块开发板: - 一端是 micro A 插头,另一端是 micro B 插头。 +2) 用 USB 线连接两块开发板:一端是 micro A 插头,另一端是 micro B 插头。 插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。 @@ -66,32 +64,28 @@ ChipIdea 高速双角色控制器驱动 echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req - 或者,通过引入 HNP 轮询,B 端主机可以知道 - A 端外设希望切换为主机角色,因此这次角色切换 - 也可以通过 A 端外设响应 B 端主机的轮询, - 在 A 侧触发。 - 这可以通过在 A 设备上执行下面的命令来完成:: + 或者,也可以借助 HNP 轮询,让 B 端主机知道 A 端外设希望切回主机角色。 + 因此,这次切换也可以由 A 侧触发,也就是由 A 端外设响应 B 端主机的轮询 + 来完成。可在 A 设备上执行下面的命令:: echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req A 设备应切回主机角色并枚举 B 设备。 -5) 拔掉 B 设备(拔掉 micro B 插头), - 并在 10 秒内重新插入; +5) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒内重新插入。 A 设备应重新枚举 B 设备。 -6) 拔掉 B 设备(拔掉 micro B 插头), - 并在 10 秒后重新插入; +6) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒后重新插入。 A 设备不应重新枚举 B 设备。 - 如果 A 设备希望使用总线: + 如果 A 设备还想继续使用总线: 在 A 设备上执行:: echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req - 如果 B 设备希望使用总线: + 如果 B 设备想使用总线: 在 B 设备上执行:: @@ -111,40 +105,41 @@ ChipIdea 高速双角色控制器驱动 echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req - A 设备应恢复 USB 总线并枚举 B 设备。 + A 设备应恢复 USB 总线,并枚举 B 设备。 1.3 参考文档 ------------ -《On-The-Go and Embedded Host Supplement -to the USB Revision 2.0 Specification +《On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification July 27, 2012 Revision 2.0 version 1.1a》 2. 如何将 USB 用作系统唤醒源 ---------------------------- -下面是在 i.MX6 平台上把 USB 用作系统唤醒源的示例。 +下面给出在 i.MX6 平台上将 USB 用作系统唤醒源的示例。 -2.1 使能核心控制器的唤醒功能:: +2.1 启用核心控制器的唤醒功能:: echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup -2.2 使能 glue 层的唤醒功能:: +2.2 启用 glue 层的唤醒功能:: echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup -2.3 使能 PHY 的唤醒功能(可选):: +2.3 启用 PHY 的唤醒功能(可选):: echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup -2.4 使能根集线器的唤醒功能:: +2.4 启用根集线器的唤醒功能:: echo enabled > /sys/bus/usb/devices/usb1/power/wakeup -2.5 使能相关设备的唤醒功能:: +2.5 启用相关设备的唤醒功能:: echo enabled > /sys/bus/usb/devices/1-1/power/wakeup -如果系统只有一个 USB 端口, -而你希望在该端口上启用 USB 唤醒功能, -可以使用下面的脚本:: +如果系统只有一个 USB 端口,而你希望在该端口上启用 USB 唤醒功能,可以使用 +下面的脚本:: - for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done; + for i in $(find /sys -name wakeup | grep usb) + do + echo enabled > $i + done diff --git a/Documentation/translations/zh_CN/usb/dwc3.rst b/Documentation/translations/zh_CN/usb/dwc3.rst index 3468ce50c5ba..9584cbbf6d03 100644 --- a/Documentation/translations/zh_CN/usb/dwc3.rst +++ b/Documentation/translations/zh_CN/usb/dwc3.rst @@ -18,46 +18,43 @@ DWC3 驱动 待办 ~~~~ -阅读时如果想顺手认领点任务,可以从下面挑一项 :) +如果你愿意接手其中一项任务,可以从下面选择: - 将中断处理程序改为每个端点各自使用线程化 IRQ - 事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。 - 当前代码会一直自旋等待命令完成,这种设计并不好。 + 实践表明,某些 DWC3 命令大约需要 ``~1 ms`` 才能完成。当前代码会一直自旋 + 等待命令完成,这并不是好办法。 实现思路: - - DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。 - 中断号在探测(``probe``)阶段分配,并归属于该设备。 - 如果硬件通过 ``MSI`` 为每个端点提供独立中断, - 那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。 + - DWC 核心实现了一个按端点分发中断的 IRQ 控制器。中断号在探测 + (``probe``)阶段分配,并归属于该设备。如果硬件通过 ``MSI`` 为每个 + 端点提供独立中断,那么这个“虚拟”IRQ 控制器就可以被真实的端点中断 + 取代。 - - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源, - 在调用 ``usb_ep_disable()`` 时释放中断资源。 - 最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。 + - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,在调用 + ``usb_ep_disable()`` 时释放中断资源。最坏情况下需要 32 个中断,最少是 + ``ep0/1`` 的两个中断。 - ``dwc3_send_gadget_ep_cmd()`` 将在 ``wait_for_completion_timeout()`` 中休眠,直到命令完成。 - 中断处理程序分为以下几个部分: - 设备级主中断处理程序 - 遍历每个事件,并对其调用 ``generic_handle_irq()``。 - 从 ``generic_handle_irq()`` 返回后,确认事件计数器,使中断最终消失。 + 遍历每个事件,并调用 ``generic_handle_irq()`` 处理。返回后再确认 + 事件计数器,让中断最终消失。 - 设备级线程化处理程序 无。 - 端点中断的主处理程序 - 读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。 - 事件保存在每个端点的数据结构中。 - 还要注意,一旦把某项工作交给线程处理, - 就不要再在主处理程序里处理它, - 以免出现优先级反转之类的问题。 + 读取事件并尽量处理;凡是需要睡眠的操作都交给线程处理。事件保存在 + 每个端点的数据结构中。一旦某项工作已经交给线程处理,主处理程序里就 + 不要再碰它,以免出现优先级反转之类的问题。 - 端点中断的线程化处理程序 处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。 - 延迟: + 延迟: - 不应增加延迟,因为中断线程具有较高优先级, - 会在普通用户态任务之前运行 + 不应增加额外延迟,因为中断线程优先级较高,会在普通用户任务之前运行 (除非用户更改了调度优先级)。 diff --git a/Documentation/translations/zh_CN/usb/ehci.rst b/Documentation/translations/zh_CN/usb/ehci.rst index e05e493a30d3..c4c52303b13e 100644 --- a/Documentation/translations/zh_CN/usb/ehci.rst +++ b/Documentation/translations/zh_CN/usb/ehci.rst @@ -14,45 +14,37 @@ EHCI 驱动 ========= -2002年12月27日 +2002 年 12 月 27 日 -EHCI 驱动用于通过支持 USB 2.0 的主机控制器 -硬件与高速 USB 2.0 设备通信。USB 2.0 兼容 -USB 1.1 标准,它定义了三种传输速率: +EHCI 驱动用于借助支持 USB 2.0 的主机控制器,与高速 USB 2.0 设备通信。USB +2.0 向下兼容 USB 1.1,并定义了三种传输速率: - “高速”(High Speed)480 Mbit/sec(60 MByte/sec) - “全速”(Full Speed)12 Mbit/sec(1.5 MByte/sec) - “低速”(Low Speed)1.5 Mbit/sec -USB 1.1 仅支持全速与低速。 -高速设备可以在 USB 1.1 系统上使用, -但速度会降到 USB 1.1 的速率。 +USB 1.1 仅支持全速与低速。高速设备可以在 USB 1.1 系统上使用,但速度会 +降到 USB 1.1 的速率。 -USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们 -插入 EHCI 控制器时,会被交由 USB 1.1 的伴随 -(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。 +USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们插入 EHCI 控制器时,会交给 +USB 1.1 的伴随(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。 -当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过 -集线器中的事务转换器(Transaction Translator,TT) -与 EHCI 控制器交互,该转换器将低速或全速事务转换为 -高速分割事务,从而避免浪费传输带宽。 +当 USB 1.1 设备插入 USB 2.0 集线器时,它们会通过集线器里的事务转换器 +(Transaction Translator,TT)与 EHCI 控制器通信。该转换器会把低速或全 +速事务转换为高速分割事务,从而避免浪费传输带宽。 -截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行 -(按字母顺序):Intel、NEC、Philips 和 VIA。 -其他供应商的 EHCI 实现正在陆续问世; -预计该驱动在这些实现上也可正常运行。 +截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行(按字母顺序): +Intel、NEC、Philips 和 VIA。随着其他供应商的 EHCI 实现陆续问世,预计该 +驱动在那些实现上也能正常运行。 -自 2001 年年中起,usb-storage 设备就已可用 -(在 2.4 版该驱动上速度相当不错), -集线器则直到 2001 年底才开始可用,而其他类型的高速设备 -似乎要等到更多系统内置 USB 2.0 后才会出现。 -这类新系统从 2002 年初开始上市, -并在 2002 年下半年变得更加常见。 +自 2001 年年中起,usb-storage 设备就已可用(在 2.4 版该驱动上速度表现相当 +不错),集线器则直到 2001 年底才开始可用。其他类型的高速设备似乎要等到 +更多系统内置 USB 2.0 后才会出现。这类新系统从 2002 年初开始上市,并在 +2002 年下半年变得更加常见。 -注意,USB 2.0 支持并不只是 EHCI 本身。 -它还需要对 Linux-USB 核心 API 作出其他修改, -包括 hub 驱动;不过这些修改并不需要真正改变 -暴露给 USB 设备驱动的基本 ``usbcore`` API。 +注意,USB 2.0 的支持并不只靠 EHCI 本身。它还需要对 Linux-USB 核心 API +做其他修改,包括 hub 驱动;不过这些修改并不需要真正改变向 USB 设备驱动 +暴露的基本 ``usbcore`` API。 - David Brownell @@ -61,58 +53,46 @@ USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们 功能 ==== -该驱动会定期在 x86 硬件上进行测试, -也已在 PPC 硬件上使用,因此大小端问题应当已经解决。 -因此可以认为,它已经处理好了所有必要的 PCI 细节, -所以即便在 DMA 映射有些特殊的系统上, -I/O 也应能正常运行。 +该驱动长期在 x86 硬件上接受测试,也在 PPC 平台上使用过,因此大小端问题应 +该都已解决。再加上各种必要的 PCI 细节都已处理妥当,即便在 DMA 映射较特 +殊的系统上,I/O 也应能正常工作。 传输类型 -------- -截至本文撰写时,该驱动应当已经能够很好地处理 -所有控制传输、批量传输和中断传输, -包括通过 USB 2.0 集线器中的事务转换器 -与 USB 1.1 设备通信;但仍可能存在 bug。 +截至本文撰写时,该驱动应当已经能够稳定处理所有控制传输、批量传输和中断传 +输,包括经由 USB 2.0 集线器里的事务转换器访问 USB 1.1 设备;不过仍可能 +存在 bug。 -高速等时(ISO)传输支持也已可用,但截至本文撰写时, -还没有 Linux 驱动使用这项支持。 +高速等时(ISO)传输支持也已可用,不过截至本文撰写时,还没有 Linux 驱动真 +正使用它。 -目前尚不支持通过事务转换器实现全速等时传输。 -需要注意,ISO 传输的 split transaction 支持 -与高速 ISO 传输几乎无法共用代码, -因为 EHCI 用不同的数据结构表示它们。 -因此,目前大多数 USB 音频和视频设备 -还不能通过高速总线连接使用。 +目前尚不支持通过事务转换器实现全速等时传输。需要注意,ISO 传输的分割 +事务支持与高速 ISO 传输几乎无法共用代码,因为 EHCI 用不同的数据结构表示 +它们。因此,目前大多数 USB 音频和视频设备还无法在高速总线上使用。 驱动行为 -------- -所有类型的传输都可以排队。 -这意味着来自一个接口驱动的控制传输 -(或通过 usbfs 发出的控制传输)不会干扰 -另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期, -而不必担心中断处理开销导致的数据丢失。 +所有类型的传输都可以排队提交。这意味着某个接口驱动发出的控制传输(或经由 +usbfs 提交的控制传输)不会干扰其他驱动的控制传输,而中断传输可以按 1 帧 +周期运行,不必担心中断处理开销导致数据丢失。 -EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 -该驱动不需要了解那些驱动的任何细节; -一个原本就能正常工作的 OHCI 或 UHCI 驱动, -并不会因为 EHCI 驱动也存在而需要更改。 +EHCI 根集线器代码会将 USB 1.1 设备交给其伴随控制器处理。该驱动无需了解 +那些驱动的任何细节;一个原本就能正常工作的 OHCI 或 UHCI 驱动,也不会因为 +EHCI 驱动存在而需要修改。 -电源管理方面还有一些问题; -当前挂起/恢复的行为还不完全正确。 +电源管理方面仍有一些问题;当前挂起/恢复行为还不完全正确。 -此外,在调度周期性事务 -(中断和等时传输)时还采取了一些简化处理。 -这些简化会限制可调度的周期性事务数量, -并且无法使用小于一帧的轮询间隔。 +此外,在调度周期性事务(中断和等时传输)时还采取了一些简化处理。这些 +简化会限制可调度的周期性事务数量,并且无法使用小于一帧的轮询间隔。 使用方式 ======== -假设有一个 EHCI 控制器(位于 PCI 卡或主板上), -并且已将此驱动编译为模块,可这样加载:: +假设系统中有一个 EHCI 控制器(位于 PCI 卡或主板上),并且此驱动是以模块形 +式编译的,那么可以这样加载:: # modprobe ehci-hcd @@ -120,27 +100,24 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 # rmmod ehci-hcd -还应加载一个伴随控制器驱动, -例如 ``ohci-hcd`` 或 ``uhci-hcd``。 -如果 EHCI 驱动出现任何问题,只需卸载它的模块, -随后该伴随控制器驱动就会接手 -此前由 EHCI 驱动处理的所有设备 -(但速度会降低)。 +还应加载一个伴随控制器驱动,例如 ``ohci-hcd`` 或 ``uhci-hcd``。如果 EHCI +驱动出了问题,只要卸载它的模块,伴随控制器驱动就会接管此前由 EHCI 驱动处 +理的全部设备(只是速度会降低)。 模块参数(传给 ``modprobe``)包括: log2_irq_thresh(默认值 0): - 默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧 - (125 微秒)。最大值 6 表示 2^6 = 64 个微帧。 + 默认中断延迟的 log2 值,单位为微帧。默认值 0 表示 1 个微帧 + (125 微秒),最大值 6 表示 2^6 = 64 个微帧。 该值控制 EHCI 控制器发出中断的频率。 -如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持, -则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件: +如果你在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,那么任一 EHCI 控 +制器对应的 ``sysfs`` 目录下都会看到三个文件: ``async`` 转储异步调度,用于控制传输和批量传输。它会显示每个活动的 ``qh`` 以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。 - (可以在 ``usb-storage`` 做磁盘 I/O 时看它;顺便观察请求队列!) + (可以在 ``usb-storage`` 执行磁盘 I/O 时查看;也可顺便观察请求队列。) ``periodic`` 转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。 @@ -151,111 +128,81 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 这些文件的内容有助于定位驱动问题。 -设备驱动通常不需要关心自己是否运行在 EHCI 之上, -但它们可能想检查 -``usb_device->speed == USB_SPEED_HIGH``。 -高速设备能做到全速(或低速)设备做不到的事, -例如高带宽的周期性传输(中断或 ISO 传输)。 -另外,设备描述符中的某些值 -(例如周期性传输的轮询间隔) -在高速模式下使用不同的编码方式。 +设备驱动通常不需要关心自己是否运行在 EHCI 之上,但有时可能会想检查 +``usb_device->speed == USB_SPEED_HIGH``。高速设备能做到全速(或低速)设备 +做不到的事,例如高带宽的周期性传输(中断或 ISO 传输)。另外,设备描述符 +中的某些值(例如周期性传输的轮询间隔)在高速模式下使用不同的编码方式。 -不过,一定要让设备驱动经过 USB 2.0 集线器的测试。 -当使用事务转换器时,这些集线器报告某些故障 -(例如断开连接)的方式会不同; -已经见过一些驱动在遇到与 OHCI 或 UHCI -所报告的不同故障时表现不佳。 +不过,设备驱动一定要在 USB 2.0 集线器后面测一遍。使用事务转换器时,这些 +集线器报告某些故障(例如断开连接)的方式会有所不同;已经见过一些驱动在 +遇到与 OHCI 或 UHCI 不同的故障时表现不佳。 性能 ==== -USB 2.0 吞吐量主要受两个因素制约: -主机控制器处理请求的速度,以及设备响应这些请求的速度。 -480 Mbit/sec 的“原始传输率”对所有设备都成立, -但总吞吐量还会受到诸如单个高速包之间的延迟、 -驱动是否足够聪明,以及系统整体负载等因素的影响。 -延迟也是性能考量因素。 +USB 2.0 的吞吐量主要受两个因素制约:主机控制器处理请求的速度,以及设备响 +应这些请求的速度。480 Mbit/sec 的“原始传输率”对所有设备都一样,但整体吞 +吐量还会受到诸如高速包之间的间隔、驱动实现是否足够高效以及系统总体负载等 +因素影响。延迟同样是需要考虑的性能指标。 -批量传输最常用于关注吞吐量的场景。 -需要记住的是,批量传输总是以 512 字节包为单位, -而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。 -8 个 USB 2.0 微帧构成一个 USB 1.1 帧; -一个微帧的时长是 1 毫秒 / 8 = 125 微秒。 +批量传输通常用于看重吞吐量的场景。需要记住的是,批量传输总是以 512 字节包 +为单位,而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。8 个 USB 2.0 微 +帧构成一个 USB 1.1 帧,因此一个微帧的时长就是 125 微秒。 -因此,只要硬件和设备驱动软件都允许, -批量传输可提供超过 50 MByte/sec 的带宽。 -周期性传输模式(等时和中断)允许使用更大的包大小, -从而可以逼近所宣称的 480 Mbit/sec 传输率。 +因此,只要硬件和驱动实现都足够成熟,批量传输就可以提供 50 MByte/sec 以上 +的带宽。周期性传输模式(等时和中断)允许使用更大的包大小,从而可以逼近所 +宣称的 480 Mbit/sec 传输率。 硬件性能 -------- -截至本文撰写时,单个 USB 2.0 设备的最大传输速率 -通常约为 20 MByte/sec。 -这当然会随着时间改变:一些设备现在更快,一些更慢。 +截至本文撰写时,单个 USB 2.0 设备的最大传输速率通常约为 20 MByte/sec。 +这种情况当然会随时间变化:有些设备现在更快,有些则更慢。 -第一代 NEC EHCI 实现似乎存在 -大约 28 MByte/sec 的硬件瓶颈。 -虽然这对单个 20 MByte/sec 的设备显然已经够用, -但把三个这样的设备挂到同一总线上, -并不能得到 60 MByte/sec。 -问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问, -因此它每个微帧只会尝试 6 次(也许是 7 次) -USB 事务,而不是 13 次。 -(对一个比其他产品早上市一年的芯片来说, -这是个合理的妥协!) +第一代 NEC EHCI 实现似乎存在大约 28 MByte/sec 的硬件瓶颈。虽然这对单个 +20 MByte/sec 的设备显然已经够用,但把三个这样的设备挂到同一总线上,并不 +能得到 60 MByte/sec。问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问, +因此它每个微帧只会尝试 6 次(也许是 7 次)USB 事务,而不是 13 次。 +(对一款比其他产品早上市一年的芯片来说,这样的取舍也算合理。) -预计较新的实现会在这方面做得更好, -通过投入更多芯片面积来解决这个问题, -使新的主板芯片组更接近 60 MByte/sec 的目标。 -这既包括 NEC 的更新实现,也包括其他厂商的芯片。 +预计较新的实现会在这方面做得更好,通过投入更多芯片面积来解决这个问题, +使新的主板芯片组更接近 60 MByte/sec 的目标。这既包括 NEC 的更新实现,也 +包括其他厂商的芯片。 -主机从 EHCI 控制器收到“请求已完成”中断的最小延迟 -为一个微帧(125 微秒)。该延迟可以调节; -驱动提供了一个模块选项。默认情况下, -``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制 -或批量请求时,通常可以在不到 250 微秒内得知它已完成 -(具体取决于传输大小)。 +主机从 EHCI 控制器收到“请求已完成”中断的最小延迟为一个微帧 +(125 微秒)。该延迟可以调节;驱动提供了一个模块选项。 +默认情况下,``ehci-hcd`` 使用最小延迟,这意味着发出控制或批量请求后,通 +常不到 250 微秒就能得知它已经完成(具体取决于传输大小)。 软件性能 -------- -即便只是要达到 20 MByte/sec 的传输速率, -Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。 -这意味着要发出较大的请求, -或者在需要发出一连串小请求时使用批量请求排队。 -如果驱动未做到这一点,那么会直接从性能结果上表现出来。 +即便只是要达到 20 MByte/sec 的传输速率,Linux-USB 设备驱动也必须让 EHCI +队列始终保持满载。这意味着要发出较大的请求,或者在需要发出一连串小请求 +时使用批量请求排队。如果驱动做不到这一点,性能就会明显受影响。 -在典型情况下,使用 ``usb_bulk_msg()`` -以 4 KB 块循环写出, -会浪费超过一半的 USB 2.0 带宽。 -I/O 完成与驱动发出下一次请求之间的延迟, -通常会比一次 I/O 本身耗时更长。 -如果同样的循环改用 16 KB 块,会好一些; -若使用一连串 128 KB 块,则浪费会少得多。 +在典型场景下,如果使用 ``usb_bulk_msg()`` 以 4 KB 块循环写出,会浪费超过 +一半的 USB 2.0 带宽。I/O 完成与驱动发出下一次请求之间的空档,往往比一次 +I/O 本身耗时还长。如果同样的循环改用 16 KB 块,情况会好一些;若使用一连串 +128 KB 块,则浪费会少得多。 + +但与其依赖这么大的 I/O 缓冲区来提升同步 I/O 的效率,不如直接向主机控制器 +排队提交多个(批量)请求,然后等待它们全部完成(或在出错时取消)。这种 +URB 排队方式对所有 USB 1.1 主机控制器驱动同样适用。 -但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效, -不如直接向主机控制器排入多个(批量)请求, -然后等待它们全部完成(或在出错时取消)。 -这种 URB 排队方式对所有 USB 1.1 -主机控制器驱动也同样适用。 - - -在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API; -它们会把 scatterlist 中的所有缓冲区都排入队列。 -它们还使用 scatterlist 的 DMA 映射 -(其中可能应用 IOMMU)并减少中断次数, -这些都有助于让高速传输尽可能快地运行。 +在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;它们会把 scatterlist +中的所有缓冲区都排入队列。它们还使用 scatterlist 的 DMA 映射(其中可能 +应用 IOMMU)并减少中断次数,这些都有助于让高速传输尽可能快地运行。 待办: 中断传输和等时(ISO)传输的性能问题。 - 这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。 + 这些周期性传输都是完全调度的,因此主要问题可能在于如何触发高带宽模式。 待办: - 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数, - 可以分配超过标准 80% 的周期性带宽。 + 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,可以分配超过标准 + 80% 的周期性带宽。 后续将对此进行说明。 diff --git a/Documentation/translations/zh_CN/usb/index.rst b/Documentation/translations/zh_CN/usb/index.rst index eb5aca0c13ec..df99814c6497 100644 --- a/Documentation/translations/zh_CN/usb/index.rst +++ b/Documentation/translations/zh_CN/usb/index.rst @@ -1,4 +1,14 @@ .. SPDX-License-Identifier: GPL-2.0 + +.. only:: subproject and latex + + .. raw:: latex + + \renewcommand{\thesection}{} + \renewcommand{\thesubsection}{} + \kerneldocCJKon + \kerneldocBeginSC{ + .. include:: ../disclaimer-zh_CN.rst :Original: Documentation/usb/index.rst @@ -24,7 +34,7 @@ USB 支持 ehci usbmon -Todolist: +待翻译文档: * functionfs * functionfs-desc @@ -52,3 +62,9 @@ Todolist: ==== * :ref:`genindex` + +.. only:: subproject and latex + + .. raw:: latex + + }\kerneldocEndSC diff --git a/Documentation/translations/zh_CN/usb/usbmon.rst b/Documentation/translations/zh_CN/usb/usbmon.rst index 11b6d5b59dce..db6030cd64a1 100644 --- a/Documentation/translations/zh_CN/usb/usbmon.rst +++ b/Documentation/translations/zh_CN/usb/usbmon.rst @@ -16,67 +16,56 @@ usbmon 简介 ==== -小写形式的 ``usbmon`` 指的是内核中的一项功能, -用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具 -``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。 -类似地,人们希望使用 usbdump 或 USBMon -(首字母大写)之类的工具来检查 -usbmon 生成的原始跟踪数据。 +小写的 ``usbmon`` 指的是内核中的一项功能,用于收集 USB 总线上的 I/O 跟踪 +信息。它类似于网络监控工具 ``tcpdump(1)`` 或 Ethereal 使用的数据包套接 +字。通常会用 usbdump 或 USBMon(首字母大写)之类的工具来查看 usbmon 生成 +的原始跟踪数据。 -usbmon 报告的是各个外设驱动 -向主机控制器驱动(HCD)发出的请求。 -因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息 -可能无法精确对应实际的总线事务。 -这和 tcpdump 的情况是一样的。 +usbmon 记录的是各个设备驱动向主机控制器驱动(HCD)发出的请求。因此,如果 +HCD 自身有 bug,usbmon 输出的跟踪信息就未必能和真实的总线事务一一对应。 +这和 tcpdump 的情况类似。 -目前实现了两种 API: ``text`` 和 ``binary``。 -二进制 API 通过 ``/dev`` 命名空间中的字符设备提供, -并且属于 ABI。文本 API 自内核 2.6.35 起已废弃, -但为了方便仍然可用。 +目前实现了两种 API:``text`` 和 ``binary``。二进制 API 通过 ``/dev`` 下的 +字符设备提供,是 ABI 的一部分。文本 API 自内核 2.6.35 起已废弃,但为了 +兼容和使用方便,至今仍然保留。 如何使用 usbmon 收集原始文本跟踪信息 ==================================== -与数据包套接字不同,usbmon 提供了一种接口, -可以输出文本格式的跟踪信息。这样做有两个目的: -第一,在更完善的格式最终确定之前, -它作为工具间通用的跟踪交换格式; -第二,在不使用工具的情况下,人们也可以直接阅读这些信息。 +与数据包套接字不同,usbmon 还提供了一个输出文本格式跟踪信息的接口。这样 +做主要有两个目的:一是在更完善的格式最终确定之前,将其作为工具间通用的跟 +踪交换格式;二是在没有工具时也能直接阅读这些信息。 -要收集原始文本跟踪信息,请按以下步骤进行操作。 +要收集原始文本跟踪信息,按下面的步骤做即可。 1. 准备 ------- -挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块 -(如果它是作为模块构建的)。如果 usbmon 已经编入内核, -那么第二步可以省略。 +挂载 debugfs(内核配置里必须启用它),并加载 usbmon 模块(如果它是以模块 +方式构建的)。如果 usbmon 已经编译进内核,这一步就可以省略。 命令示例:: - # mount -t debugfs none_debugs /sys/kernel/debug + # mount -t debugfs none /sys/kernel/debug # modprobe usbmon # -确认总线套接字是否存在:: +确认 ``usbmon`` 目录下是否有这些条目:: # ls /sys/kernel/debug/usb/usbmon 0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u # -现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包, -并跳到第 3 步; -也可以先按第 2 步找到目标设备所在的总线。 -这样可以过滤掉那些持续输出数据的烦人设备。 +现在,你可以直接用 ``0u`` 捕获所有总线上的数据包,然后跳到第 3 步;也可 +以先按第 2 步找出目标设备所在的总线。这样可以把那些持续产生流量的设备过 +滤掉。 2. 查找目标设备连接的是哪条总线 ------------------------------- -运行 ``cat /sys/kernel/debug/usb/devices``, -找到对应设备的 T 行。通常可以通过厂商字符串来查找。 -如果有许多类似设备,可以拔掉其中一个, -再比较前后两次 ``/sys/kernel/debug/usb/devices`` -的输出。T 行里会包含总线编号。 +运行 ``cat /sys/kernel/debug/usb/devices``,找到对应设备的 T 行。通常可以通过 +厂商字符串来查找。如果有很多相似设备,可以拔掉其中一个,再比较前后两次 +``/sys/kernel/debug/usb/devices`` 的输出。T 行里会包含总线编号。 示例:: @@ -86,8 +75,8 @@ usbmon 报告的是各个外设驱动 S: Manufacturer=ATEN S: Product=UC100KM V2.00 -``Bus=03`` 表示它位于 3 号总线上。或者, -也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。 +``Bus=03`` 表示它位于 3 号总线上。或者,也可以查看 ``lsusb`` 的输出,并从 +对应条目里找到总线编号。 示例如下:: @@ -97,133 +86,110 @@ usbmon 报告的是各个外设驱动 3. 启动 cat 命令 ---------------- -如果只监听单条总线,可执行:: +如果只监听单条总线,执行:: # cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out -否则,如果要监听所有总线,则执行:: +否则,如果要监听所有总线,执行:: # cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out -此进程会一直读取,直到被终止。 -由于输出通常会很长,因此更推荐将输出重定向到某个位置。 +这个进程会一直运行到被终止为止。由于输出通常会很长,最好把它重定向到文件 +或其他位置。 4. 在 USB 总线上执行期望的操作 ------------------------------ -此处需要执行一些会产生 USB 流量的动作, -比如插入 U 盘、拷贝文件、操作摄像头等。 +这里做一些会产生 USB 流量的操作即可,比如插入 U 盘、拷贝文件、操作摄像头 +等。 5. 停止 cat ----------- -这一步通常通过键盘中断(Control-C)完成。 +这一步通常按下键盘中断(Control-C)即可完成。 -此时输出文件(本例中为 ``/tmp/1.mon.out``) -可以保存、通过电子邮件发送,或使用文本编辑器查看。 -如果使用最后一种方式,请确保文件不会大到编辑器无法打开。 +此时,输出文件(本例中为 ``/tmp/1.mon.out``)可以保存下来,通过电子邮件发 +送,也可以用文本编辑器查看。如果要用文本编辑器查看,请确保文件大小不会 +大到编辑器无法处理。 原始文本数据格式 ================ -目前支持两种格式:原始格式,也就是 ``1t`` 格式, -以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。 -``1u`` 格式增加了一些字段,例如 ISO 帧描述符、 -``interval`` 等。它生成的行会稍长一些, -但在其他方面是 ``1t`` 格式的完整超集。 +目前支持两种格式:原始的 ``1t`` 格式和 ``1u`` 格式。``1t`` 格式在内核 +2.6.21 中已被废弃。``1u`` 格式增加了一些字段,例如 ISO 帧描述符和 +``interval``。它生成的行会稍长一些,但除此之外,它是 ``1t`` 格式的完整 +超集。 -如果程序需要区分上述两种格式, -可以查看 ``address`` 字段(见下文)。 -如果其中有两个冒号,就是 ``1t`` 格式; -否则是 ``1u`` 格式。 +如果程序需要区分上述两种格式,可以查看 ``address`` 字段(见下文)。如果 +其中有两个冒号,就是 ``1t`` 格式;否则是 ``1u`` 格式。 -任何文本格式的数据由一系列事件组成, -如 URB 提交、URB 回调、提交错误等。 -每个事件对应单独的一行文本, -由使用空白符间隔的若干字段组成。 -字段的数量与位置可能取决于事件类型, -但以下字段对所有类型都通用: +任何文本格式的数据都由一系列事件构成,例如 URB 提交、URB 回调和提交错 +误。每个事件占一行,由若干以空白符分隔的字段组成。字段数量和位置会随事件 +类型变化,但下面这些字段对所有类型都通用: -下面按从左到右的顺序列出这些共有字段: +下面按从左到右的顺序说明这些通用字段: -- URB Tag。用于标识 URB,通常是 URB 结构体在内核中的地址 - (以十六进制表示), - 但也可能是序号或其他合理的唯一字符串。 +- URB 标识(URB Tag)。用于标识 URB,通常是 URB 结构体在内核中的地址 + (十六进制),也可能是序号或其他足以唯一标识 URB 的字符串。 -- 时间戳(微秒),十进制数字。 - 时间戳的精度取决于可用时钟, - 因此可能远差于 - 1 微秒(例如实现使用的是 jiffies)。 +- 时间戳(微秒),十进制数字。时间戳的精度取决于可用时钟,所以可能远低于 + 1 微秒(例如实现使用 jiffies 时)。 -- 事件类型。它表示的是事件的格式,而不是 URB 的类型。 - 可用值为:``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。 +- 事件类型。它表示的是这一行事件的格式,而不是 URB 的类型。可用值为: + ``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。 -- ``Address`` 字段(以前称作 ``pipe``)。 - 它包含四个由冒号分隔的字段: - URB 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下: +- ``Address`` 字段(以前称为 ``pipe``)。它包含四个由冒号分隔的字段:URB + 类型及方向、总线号、设备地址和端点号。类型与方向按下面的方式编码: - == == ========================== - Ci Co 控制输入和输出 - Zi Zo 等时输入和输出 - Ii Io 中断输入和输出 - Bi Bo 批量输入和输出 - == == ========================== + == == ==================== + Ci Co 控制输入与输出 + Zi Zo 等时输入与输出 + Ii Io 中断输入与输出 + Bi Bo 批量输入与输出 + == == ==================== - 总线号、设备地址和端点号使用十进制,但可能有前导零。 + 总线号、设备地址和端点号都是十进制数,但可能有前导零,方便人阅读。 -- URB 状态字段。这个字段要么是一个字母, - 要么是几个由冒号分隔的数字: - URB 状态、``interval``、``start frame`` 和 ``error count``。 - 与 ``address`` 字段不同,除了状态外,其余字段都是可选的。 - ``interval`` 只会为中断和等时 URB 打印;``start frame`` 只会为 - 等时 URB 打印;错误计数只会在等时回调事件中打印。 +- URB 状态字段。这个字段要么是一个字母,要么是几个用冒号分隔的数字,依次 + 表示 URB 状态、``interval``、``start frame`` 和 ``error count``。与 + ``address`` 字段不同,除状态外,其余字段都可能省略。``interval`` 只会在 + 中断和等时 URB 中打印;``start frame`` 只会在等时 URB 中打印;错误计数只 + 会在等时回调事件中打印。 - 状态字段是一个十进制数字,有时为负数, - 对应 URB 的 ``status`` 字段。 - 对于提交事件,这个字段本身没有实际意义, - 但为了便于脚本解析,它仍然存在。 - 当发生错误时,该字段包含错误码。 + 状态字段是一个十进制数,有时为负数,对应 URB 的 ``status`` 字段。对于提 + 交事件,这个字段本身并无实际语义,但为了便于脚本解析仍会保留。发生错误 + 时,这里填的是错误码。 - 在提交控制包时,这个字段包含的是 ``Setup Tag``, - 而不是一组数字。 - 判断 ``Setup Tag`` 是否存在很容易,因为它从来不是数字。 - 因此,如果脚本在这个字段里发现的是一组数字, - 就会继续读取数据长度(等时 URB 除外)。 - 如果发现的是其他内容,比如一个字母, - 那么脚本会先读取 ``Setup`` 包,再读取数据长度或等时描述符。 + 如果是控制包的提交事件,这个字段里放的不是一组数字,而是 ``Setup Tag``。 + 这很容易分辨,因为 ``Setup Tag`` 永远不是数字。所以脚本如果在这里读到一 + 组数字,就会继续读取数据长度(等时 URB 除外);如果读到的是字母之类的内 + 容,就要先读取 ``Setup`` 包,再读取数据长度或等时描述符。 -- ``Setup`` 包由 5 个字段组成: - ``bmRequestType``、``bRequest``、``wValue``、 - ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。 - 如果 ``Setup Tag`` 为 ``s``,就可以安全地解码这些字段。 - 否则,说明 Setup 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。 +- ``Setup`` 包由 5 个字段组成:``bmRequestType``、``bRequest``、``wValue``、 + ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。如果 ``Setup Tag`` + 是 ``s``,就可以安全解码这些字段。否则,说明 Setup 包虽然存在,但并未被 + 捕获,此时各字段中会填入占位内容。 - 等时传输帧描述符的数量及其内容: - 如果一个等时传输事件带有一组描述符,首先打印该 URB 中描述符的总数, - 然后为每个描述符打印一个字段,最多打印 5 个字段。 - 每个字段由三个用冒号分隔的十进制数字组成, - 分别表示状态(status)、偏移(offset)和长度(length)。 - 对于提交(submission),报告的是初始长度; - 对于回调(callback),报告的是实际长度。 + 如果某个等时传输事件带有描述符,会先打印该 URB 的描述符总数,再为每个描 + 述符打印一个字段,最多 5 个。每个字段由三个用冒号分隔的十进制数组成,依 + 次表示状态(status)、偏移(offset)和长度(length)。对于提交事件,报 + 告的是初始长度;对于回调事件,报告的是实际长度。 -- 数据长度: - 对于提交,表示请求的长度;对于回调,表示实际传输的长度。 +- 数据长度:对于提交,表示请求的长度;对于回调,表示实际传输的长度。 -- 数据标签: - 即使数据长度非零,usbmon 也不一定会捕获数据。 - 仅当标签为 ``=`` 时,才会有数据字段。 +- 数据标签:即使数据长度非零,usbmon 也不一定会捕获数据。只有标签为 + ``=`` 时,才会有数据字段。 -- 数据字段: - 以大端十六进制格式显示。注意,这些并不是真正的机器字, - 而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含 - 1 到 4 个字节。 - 收集的数据长度是有限的,可能小于数据长度字段中报告的值。 - 因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区, - 所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下, - 收集的数据长度可能大于数据长度字段的值。 +- 数据字段:以大端十六进制格式显示。注意,这些并不是真正的机器字,只是为 + 了便于阅读,把字节流按“字”分组显示。因此最后一个字可能只包含 1 到 4 个 + 字节。捕获的数据长度是有限的,可能小于数据长度字段中报告的值。对于等时 + 输入(Zi)完成事件,如果缓冲区里的接收数据比较稀疏,捕获数据的长度甚至 + 可能大于数据长度字段,因为后者只统计实际接收到的字节,而数据字段展示的 + 是整个传输缓冲区。 @@ -234,18 +200,19 @@ usbmon 报告的是各个外设驱动 d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 < d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000 -向地址为 5 的存储设备发送 -31 字节 Bulk 包装的 SCSI 命令 ``0x28`` -(``READ_10``)的输出批量传输:: +向地址为 5 的存储设备发送一个输出批量传输,其中 31 字节的 Bulk 封装用于承 +载 SCSI 命令 ``0x28``(``READ_10``)。为便于排版,下面的第一条记录按两行 +显示,但实际 usbmon 输出仍是一行:: - dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000 + dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = + 55534243 ad000000 00800000 80010a28 20000000 20000040 + 00000000 000000 dd65f0e8 4128379808 C Bo:1:005:2 0 31 > 原始二进制格式与 API ==================== -API 的整体架构与前文大体相同,只是事件以二进制格式传递。 -每个事件都通过下面的结构发送 -(这个名字是为了叙述方便而虚构的):: +API 的整体架构与前文大体相同,只是事件以二进制格式传递。每个事件都通过 +下面的结构发送(这个结构名只是为了叙述方便而虚构的):: struct usbmon_packet { @@ -275,29 +242,22 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 unsigned int ndesc; /* 60: 实际 ISO 描述符数量 */ }; /* 64 总长度 */ -可以用 ``read(2)``、``ioctl(2)``, -或者通过 ``mmap`` 访问缓冲区, -从字符设备接收这些事件。 -不过,出于兼容性原因,``read(2)`` -只返回前 48 个字节。 +可以用 ``read(2)``、``ioctl(2)``,或者通过 ``mmap`` 访问缓冲区,从字符设 +备接收这些事件。不过,出于兼容性原因,``read(2)`` 只返回前 48 个字节。 -字符设备通常命名为 ``/dev/usbmonN``, -其中 ``N`` 是 USB 总线号。 -编号为零的设备(``/dev/usbmon0``)比较特殊, -表示“所有总线”。 -请注意,具体命名策略由 Linux 发行版决定。 +字符设备通常命名为 ``/dev/usbmonN``,其中 ``N`` 是 USB 总线号。编号为零的 +设备(``/dev/usbmon0``)比较特殊,表示“所有总线”。具体命名策略由 Linux +发行版决定。 -如果你手动创建 ``/dev/usbmon0``, -请确保它归 root 所有,并且权限为 ``0600``。 -否则,非特权用户将能够窃听键盘流量。 +如果你手动创建 ``/dev/usbmon0``,请确保它归 root 所有,并且权限为 ``0600``。 +否则,非特权用户就能窃听键盘输入流量。 以下 ``MON_IOC_MAGIC`` 为 ``0x92`` 的 ioctl 调用可用: ``MON_IOCQ_URB_LEN``,定义为 ``_IO(MON_IOC_MAGIC, 1)`` -该调用返回下一个事件的数据长度。 -注意大多数事件不包含数据, -因此如果该调用返回零,并不意味着没有事件。 +该调用返回下一个事件的数据长度。注意大多数事件不包含数据,因此如果它返回 +零,并不意味着没有事件。 ``MON_IOCG_STATS``,定义为 ``_IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)`` @@ -309,18 +269,16 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 u32 dropped; }; -成员 ``queued`` 表示当前缓冲区中已经排队的事件数量, -而不是自上次重置以来处理过的事件数量。 +成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,而不是自上次重置以来 +处理过的事件数量。 -成员 ``dropped`` 表示自上次调用 -``MON_IOCG_STATS`` 以来丢失的事件数量。 +成员 ``dropped`` 表示自上次调用 ``MON_IOCG_STATS`` 以来丢失的事件数量。 ``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)`` -此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。 -大小可能会向下取整到下一个块(或页)。 -如果请求的大小超出该内核的 [未指定] 范围, -则调用会失败并返回 ``-EINVAL``。 +此调用设置缓冲区大小。参数是以字节为单位的缓冲区大小。大小可能会向下取整 +到下一个块(或页)。如果请求的大小超出当前内核允许的范围,则调用会失败并 +返回 ``-EINVAL``。 ``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)`` @@ -331,9 +289,8 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 ``MON_IOCX_GETX``,定义为 ``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)`` -如果内核缓冲区中没有事件, -这些调用就会一直等待,直到有事件到达, -然后返回第一个事件。 +如果内核缓冲区中没有事件,这些调用就会一直等待,直到有事件到达,然后返回 +第一个事件。 参数是指向以下结构的指针:: struct mon_get_arg { @@ -343,20 +300,18 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 }; -调用前,应填好 ``hdr``、``data`` 和 ``alloc``。 -调用返回后,``hdr`` 指向的区域中包含下一个事件的结构; -如果存在数据,那么数据缓冲区中也会包含相应数据。 -该事件会从内核缓冲区中移除。 +调用前,应填好 ``hdr``、``data`` 和 ``alloc``。调用返回后,``hdr`` 指向的 +内存区域中会写入下一个事件的结构;如果存在数据,数据缓冲区中也会填入相应 +内容。该事件会从内核缓冲区中移除。 -``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域, -``MON_IOCX_GETX`` 会复制 64 字节。 +``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,``MON_IOCX_GETX`` +会复制 64 字节。 ``MON_IOCX_MFETCH``,定义为 ``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)`` -当应用程序通过 ``mmap(2)`` 访问缓冲区时, -主要使用这个 ioctl。 -其参数是指向以下结构的指针:: +应用程序通过 ``mmap(2)`` 访问缓冲区时,主要使用这个 ioctl。其参数是指向 +以下结构的指针:: struct mon_mfetch_arg { uint32_t *offvec; /* 获取的事件偏移向量 */ @@ -365,41 +320,36 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 }; -该 ioctl 的操作分为三个阶段: +这个 ioctl 的流程分为三个阶段: -首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。 -实际丢弃的事件数量会写回 ``nflush``。 +首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。实际丢弃的事件数量会 +写回 ``nflush``。 -其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待, -直到缓冲区中出现事件。 +其次,除非设备以 ``O_NONBLOCK`` 打开,否则会一直等待,直到缓冲区中出现 +事件。 -第三,将最多 ``nfetch`` 个偏移量提取到 mmap -缓冲区,并存入 ``offvec`` 中。 -实际提取到的事件偏移数量会存回 ``nfetch``。 +第三,将最多 ``nfetch`` 个偏移量提取到 mmap 缓冲区,并存入 ``offvec`` 中。 +实际提取到的事件偏移数量会写回 ``nfetch``。 ``MON_IOCH_MFLUSH``,定义为 ``_IO(MON_IOC_MAGIC, 8)`` -此调用从内核缓冲区移除若干事件。 -其参数为要移除的事件数量。 -如果缓冲区中的事件少于请求数量, -则移除所有事件,且不报告错误。 -当没有事件时也可使用。 +此调用从内核缓冲区移除若干事件。其参数是要移除的事件数量。如果缓冲区中的 +事件少于请求数量,则移除全部现有事件,且不报告错误。即使当前没有事件,也 +可以调用。 ``FIONBIO`` 如果有需要,将来可能会实现 ``FIONBIO`` ioctl。 -除了 ``ioctl(2)`` 和 ``read(2)`` 之外, -二进制 API 的特殊文件也可以用 ``select(2)`` 和 -``poll(2)`` 轮询。 -但 ``lseek(2)`` 不起作用。 +除了 ``ioctl(2)`` 和 ``read(2)`` 之外,二进制 API 对应的特殊文件还可以用 +``select(2)`` 和 ``poll(2)`` 轮询,但 ``lseek(2)`` 不可用。 * 二进制 API 的内核缓冲区内存映射访问 -基本思想很简单: +基本思路很简单: -准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。 -然后执行类似下面伪代码的循环:: +准备时,先查询当前大小,再用 ``mmap(2)`` 映射缓冲区。之后运行与下面伪代码 +类似的循环:: struct mon_mfetch_arg fetch; struct usbmon_packet *hdr; @@ -411,7 +361,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 ioctl(fd, MON_IOCX_MFETCH, &fetch); // 同时处理错误 nflush = fetch.nfetch; // 完成后要刷新这么多包 for (i = 0; i < nflush; i++) { - hdr = (struct ubsmon_packet *) &mmap_area[vec[i]]; + hdr = (struct usbmon_packet *) &mmap_area[vec[i]]; if (hdr->type == '@') // 填充包 continue; caddr_t data = &mmap_area[vec[i]] + 64; @@ -421,7 +371,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递 -因此,主要思想是每 N 个事件只执行一次 ioctl。 +因此,这里的核心思路就是每 N 个事件只执行一次 ioctl。 -虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端, -因此上面的伪代码无需任何合并操作。 +虽然缓冲区是环形的,但返回的头部和数据不会跨越缓冲区末端,因此上面的伪代 +码无需做任何拼接。 diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst index f7487b0d8057..b189cf5626ba 100644 --- a/Documentation/usb/functionfs.rst +++ b/Documentation/usb/functionfs.rst @@ -72,6 +72,82 @@ have been written to their ep0's. Conversely, the gadget is unregistered after the first USB function closes its endpoints. +Endpoint IOCTLs +=============== + +FunctionFS supports additional IOCTLs that can be performed on data endpoints +(ie. not ep0). For a full list of these IOCTLs, please refer to the documentation +in ``include/uapi/linux/usb/functionfs.h``. + +One such IOCTL is: + + ``FUNCTIONFS_ENDPOINT_ENABLE_ZLP(__u32 *)`` + Enable or disable automatic zero-length packet (ZLP) appending for the + endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to + enable. When enabled, the kernel will automatically append a ZLP at the end + of a transfer if the payload length is an exact multiple of the endpoint's + max packet size. This is useful for compatibility with legacy protocols + which require automatic ZLP appending to data written from userspace. This + IOCTL can only be used on IN endpoints. It can be called at any time after + the FunctionFS instance is active, even before the host has connected or + enabled the endpoint. Returns zero on success, or a negative errno value on + error: + + * ``-ENODEV``: The FunctionFS instance is not active. + * ``-EINVAL``: The endpoint is not an IN endpoint. + * ``-EFAULT``: Invalid user space pointer for the argument. + +RW Proxy Endpoints +================== + +If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header +(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a +bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair +of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware +endpoints, allowing userspace to use a single file descriptor for both reading +(OUT) and writing (IN). + +This flag requires the total number of hardware endpoints to be an even number. +FunctionFS will automatically walk the provided endpoints and group them into +adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the +second pair). Each pair must consist of exactly one IN endpoint and one OUT +endpoint. + +For each valid pair, a rw_proxy file is created and named after the first +endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are +paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired, +"ep3_rw" is created. + +If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be +named using their physical endpoint address in hexadecimal instead of their +index. RW proxy files will inherit this naming convention. For example, if the +first endpoint of a pair maps to address 0x02, the rw_proxy file will be +named "ep02_rw". + +When this flag is enabled, userspace has the choice of performing data transfers +via the single rw_proxy file descriptor or the two base file descriptors. The +rw_proxy file descriptor acts as a pure VFS alias that proxies all operations +directly to the underlying base file descriptors. + +Because it is a pure proxy, there are no data races or buffer corruptions if +userspace uses both the rw_proxy endpoint and the base endpoints concurrently. +The native mutexes of the base endpoints perfectly serialize all concurrent +transfers. However, userspace should generally pick one method and stick to it +to avoid interleaving its own data stream. + +- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and + will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue + the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base + endpoint file descriptor. +- **Intentional Stalls:** The traditional mechanism for intentionally halting an + endpoint by issuing a reverse-direction data operation (e.g., attempting to + read from an IN endpoint) continues to work, but it must be issued on the + base endpoint. RW proxy endpoints cannot be used to trigger a stall because + they are fully bidirectional. + +Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported +via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF +transfers are required, users must use the standard base endpoints. DMABUF interface ================ @@ -79,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the userspace can attach DMABUF objects (externally created) to an endpoint, and subsequently use them for data transfers. +Note: The DMABUF interface is unsupported on rw_proxy endpoints. See +the RW Proxy Endpoints section for details on using DMABUF alongside +the ``FUNCTIONFS_RW_PROXY_EPS`` flag. + A userspace application can then use this interface to share DMABUF objects between several interfaces, allowing it to transfer data in a zero-copy fashion, for instance between IIO and the USB stack. diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index a6e8292f320a..4921e5307d49 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -714,9 +714,6 @@ The uac1 function provides these attributes in its function directory: =============== ==================================== audio_buf_size audio buffer size - fn_cap capture pcm device file name - fn_cntl control device file name - fn_play playback pcm device file name req_buf_size ISO OUT endpoint request buffer size req_count ISO OUT endpoint request count =============== ==================================== diff --git a/drivers/media/usb/as102/as102_usb_drv.c b/drivers/media/usb/as102/as102_usb_drv.c index a11024451ceb..be2f8be560fa 100644 --- a/drivers/media/usb/as102/as102_usb_drv.c +++ b/drivers/media/usb/as102/as102_usb_drv.c @@ -24,37 +24,35 @@ static void as102_usb_stop_stream(struct as102_dev_t *dev); static int as102_open(struct inode *inode, struct file *file); static int as102_release(struct inode *inode, struct file *file); +struct as102_dev_info { + const char *name; + /* + * eLNA configuration: devices built on the reference design work best + * with 0xA0, while custom designs seem to require 0xC0 + */ + uint8_t elna_cfg; +}; + +#define DRIVER_INFO(dev_name, dev_elna_cfg) \ + .driver_info = (kernel_ulong_t)&(const struct as102_dev_info){ \ + .name = (dev_name), \ + .elna_cfg = (dev_elna_cfg), \ + } + static const struct usb_device_id as102_usb_id_table[] = { - { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) }, - { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) }, - { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) }, - { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) }, - { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) }, + { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001), + DRIVER_INFO(AS102_REFERENCE_DESIGN, 0xA0) }, + { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID), + DRIVER_INFO(AS102_PCTV_74E, 0xC0) }, + { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID), + DRIVER_INFO(AS102_ELGATO_EYETV_DTT_NAME, 0xC0) }, + { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID), + DRIVER_INFO(AS102_NBOX_DVBT_DONGLE_NAME, 0xA0) }, + { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID), + DRIVER_INFO(AS102_SKY_IT_DIGITAL_KEY_NAME, 0xA0) }, { } /* Terminating entry */ }; -/* Note that this table must always have the same number of entries as the - as102_usb_id_table struct */ -static const char * const as102_device_names[] = { - AS102_REFERENCE_DESIGN, - AS102_PCTV_74E, - AS102_ELGATO_EYETV_DTT_NAME, - AS102_NBOX_DVBT_DONGLE_NAME, - AS102_SKY_IT_DIGITAL_KEY_NAME, - NULL /* Terminating entry */ -}; - -/* eLNA configuration: devices built on the reference design work best - with 0xA0, while custom designs seem to require 0xC0 */ -static uint8_t const as102_elna_cfg[] = { - 0xA0, - 0xC0, - 0xC0, - 0xA0, - 0xA0, - 0x00 /* Terminating entry */ -}; - struct usb_driver as102_usb_driver = { .name = DRIVER_FULL_NAME, .probe = as102_usb_probe, @@ -336,29 +334,18 @@ static int as102_usb_probe(struct usb_interface *intf, { int ret; struct as102_dev_t *as102_dev; - int i; - - /* This should never actually happen */ - if (ARRAY_SIZE(as102_usb_id_table) != - (sizeof(as102_device_names) / sizeof(const char *))) { - pr_err("Device names table invalid size"); - return -EINVAL; - } + const struct as102_dev_info *info = (const struct as102_dev_info *)id->driver_info; as102_dev = kzalloc_obj(struct as102_dev_t); if (as102_dev == NULL) return -ENOMEM; - /* Assign the user-friendly device name */ - for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) { - if (id == &as102_usb_id_table[i]) { - as102_dev->name = as102_device_names[i]; - as102_dev->elna_cfg = as102_elna_cfg[i]; - } - } - - if (as102_dev->name == NULL) + if (info) { + as102_dev->name = info->name; + as102_dev->elna_cfg = info->elna_cfg; + } else { as102_dev->name = "Unknown AS102 device"; + } /* set private callback functions */ as102_dev->bus_adap.ops = &as102_priv_ops; diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 2a1728621887..71c1aafca4d3 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev) dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; } -static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id) +static int tbnet_probe(struct tb_service *svc) { struct tb_xdomain *xd = tb_service_parent(svc); struct net_device *dev; @@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume); static const struct tb_service_id tbnet_ids[] = { { TB_SERVICE("network", 1) }, - { }, + { } }; MODULE_DEVICE_TABLE(tbsvc, tbnet_ids); diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index 8700eeb8e22d..aba1a640fc26 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -43,21 +43,12 @@ static bool loopback; static bool mii_mode; static char *devid; -static struct usb_eth_dev usb_dev_id[] = { -#define PEGASUS_DEV(pn, vid, pid, flags) \ - {.name = pn, .vendor = vid, .device = pid, .private = flags}, -#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \ - PEGASUS_DEV(pn, vid, pid, flags) -#include "pegasus.h" -#undef PEGASUS_DEV -#undef PEGASUS_DEV_CLASS - {NULL, 0, 0, 0}, - {NULL, 0, 0, 0} -}; +static struct usb_eth_dev dynamic_id_info = {}; static struct usb_device_id pegasus_ids[] = { #define PEGASUS_DEV(pn, vid, pid, flags) \ - {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid}, + {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid, \ + .driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}}, /* * The Belkin F8T012xx1 bluetooth adaptor has the same vendor and product * IDs as the Belkin F5D5050, so we need to teach the pegasus driver to @@ -66,7 +57,8 @@ static struct usb_device_id pegasus_ids[] = { */ #define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \ {.match_flags = (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS), \ - .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass}, + .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass, \ + .driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}}, #include "pegasus.h" #undef PEGASUS_DEV #undef PEGASUS_DEV_CLASS @@ -402,12 +394,12 @@ static inline int reset_mac(pegasus_t *pegasus) if (i == REG_TIMEOUT) return -ETIMEDOUT; - if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS || - usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) { + if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS || + le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) { set_register(pegasus, Gpio0, 0x24); set_register(pegasus, Gpio0, 0x26); } - if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_ELCON) { + if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_ELCON) { __u16 auxmode; ret = read_mii_word(pegasus, 3, 0x1b, &auxmode); if (ret < 0) @@ -445,9 +437,9 @@ static int enable_net_traffic(struct net_device *dev, struct usb_device *usb) memcpy(pegasus->eth_regs, data, sizeof(data)); ret = set_registers(pegasus, EthCtrl0, 3, data); - if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS || - usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS2 || - usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) { + if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS || + le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS2 || + le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) { u16 auxmode; ret = read_mii_word(pegasus, 0, 0x1b, &auxmode); if (ret < 0) @@ -1153,7 +1145,7 @@ static int pegasus_probe(struct usb_interface *intf, struct usb_device *dev = interface_to_usbdev(intf); struct net_device *net; pegasus_t *pegasus; - int dev_index = id - pegasus_ids; + const struct usb_eth_dev *info = (const struct usb_eth_dev *)id->driver_info; int res = -ENOMEM; static const u8 bulk_ep_addr[] = { PEGASUS_USB_EP_BULK_IN | USB_DIR_IN, @@ -1178,7 +1170,6 @@ static int pegasus_probe(struct usb_interface *intf, goto out; pegasus = netdev_priv(net); - pegasus->dev_index = dev_index; pegasus->intf = intf; res = alloc_urbs(pegasus); @@ -1206,7 +1197,7 @@ static int pegasus_probe(struct usb_interface *intf, pegasus->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK); - pegasus->features = usb_dev_id[dev_index].private; + pegasus->features = info ? info->private : DEFAULT_GPIO_RESET; res = get_interrupt_interval(pegasus); if (res) goto out2; @@ -1235,7 +1226,7 @@ static int pegasus_probe(struct usb_interface *intf, queue_delayed_work(system_long_wq, &pegasus->carrier_check, CARRIER_CHECK_DELAY); dev_info(&intf->dev, "%s, %s, %pM\n", net->name, - usb_dev_id[dev_index].name, net->dev_addr); + info ? info->name : "(unknown)", net->dev_addr); return 0; out3: @@ -1325,8 +1316,9 @@ static struct usb_driver pegasus_driver = { static void __init parse_id(char *id) { - unsigned int vendor_id = 0, device_id = 0, flags = 0, i = 0; + unsigned int vendor_id = 0, device_id = 0, flags = 0; char *token, *name = NULL; + int dyn_id_index = ARRAY_SIZE(pegasus_ids) - 2; token = strsep(&id, ":"); if (token) @@ -1348,14 +1340,12 @@ static void __init parse_id(char *id) if (device_id > 0x10000 || device_id == 0) return; - for (i = 0; usb_dev_id[i].name; i++); - usb_dev_id[i].name = name; - usb_dev_id[i].vendor = vendor_id; - usb_dev_id[i].device = device_id; - usb_dev_id[i].private = flags; - pegasus_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; - pegasus_ids[i].idVendor = vendor_id; - pegasus_ids[i].idProduct = device_id; + dynamic_id_info.name = name; + dynamic_id_info.private = flags; + pegasus_ids[dyn_id_index].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + pegasus_ids[dyn_id_index].idVendor = vendor_id; + pegasus_ids[dyn_id_index].idProduct = device_id; + pegasus_ids[dyn_id_index].driver_info = (kernel_ulong_t)&dynamic_id_info; } static int __init pegasus_init(void) diff --git a/drivers/net/usb/pegasus.h b/drivers/net/usb/pegasus.h index a05b143155ba..ccdedcef52e7 100644 --- a/drivers/net/usb/pegasus.h +++ b/drivers/net/usb/pegasus.h @@ -85,7 +85,6 @@ typedef struct pegasus { unsigned features; u32 msg_enable; u32 wolopts; - int dev_index; int intr_interval; struct tasklet_struct rx_tl; struct delayed_work carrier_check; @@ -102,8 +101,6 @@ typedef struct pegasus { struct usb_eth_dev { char *name; - __u16 vendor; - __u16 device; __u32 private; /* LSB is gpio reset value */ }; diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index 47f904e7e652..d3491ff08e6e 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -1087,7 +1087,7 @@ static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev) } kfree(buf); - if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + if (IS_AR7010_DEVICE(hif_dev->id_info)) firm_offset = AR7010_FIRMWARE_TEXT; else firm_offset = AR9271_FIRMWARE_TEXT; @@ -1182,7 +1182,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev, if (MAJOR_VERSION_REQ == 1 && hif_dev->fw_minor_index == 3) { const char *filename; - if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + if (IS_AR7010_DEVICE(hif_dev->id_info)) filename = FIRMWARE_AR7010_1_1; else filename = FIRMWARE_AR9271; @@ -1198,7 +1198,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev, return -ENOENT; } else { - if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + if (IS_AR7010_DEVICE(hif_dev->id_info)) chip = "7010"; else chip = "9271"; @@ -1255,9 +1255,9 @@ static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context) ret = ath9k_htc_hw_init(hif_dev->htc_handle, &hif_dev->interface->dev, - hif_dev->usb_device_id->idProduct, + le16_to_cpu(hif_dev->udev->descriptor.idProduct), hif_dev->udev->product, - hif_dev->usb_device_id->driver_info); + hif_dev->id_info); if (ret) { ret = -EINVAL; goto err_htc_hw_init; @@ -1369,7 +1369,7 @@ static int ath9k_hif_usb_probe(struct usb_interface *interface, hif_dev->udev = udev; hif_dev->interface = interface; - hif_dev->usb_device_id = id; + hif_dev->id_info = id->driver_info; #ifdef CONFIG_PM udev->reset_resume = 1; #endif diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.h b/drivers/net/wireless/ath/ath9k/hif_usb.h index dc0b0fa5c325..b3e7b0fb54b8 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.h +++ b/drivers/net/wireless/ath/ath9k/hif_usb.h @@ -115,7 +115,7 @@ struct cmd_buf { struct hif_device_usb { struct usb_device *udev; struct usb_interface *interface; - const struct usb_device_id *usb_device_id; + int id_info; const void *fw_data; size_t fw_size; struct completion fw_done; diff --git a/drivers/target/target_core_fabric_configfs.c b/drivers/target/target_core_fabric_configfs.c index 166dbf4c4061..ab8f81650710 100644 --- a/drivers/target/target_core_fabric_configfs.c +++ b/drivers/target/target_core_fabric_configfs.c @@ -690,6 +690,14 @@ static void target_fabric_port_unlink( } core_dev_del_lun(se_tpg, lun); + + if (tf->tf_ops->fabric_post_unlink) { + /* + * Allow fabrics to release state that must remain valid until + * core_dev_del_lun() has drained all active LUN references. + */ + tf->tf_ops->fabric_post_unlink(se_tpg, lun); + } } static void target_fabric_port_release(struct config_item *item) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index f5cf0e177f40..6e9080e7bcec 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf, if (!access_ok(user_buf, *count)) return ERR_PTR(-EFAULT); - buf = (void *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); nbytes = min_t(size_t, *count, PAGE_SIZE); if (copy_from_user(buf, user_buf, nbytes)) { - free_page((unsigned long)buf); + kfree(buf); return ERR_PTR(-EFAULT); } @@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } @@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } @@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file, out: pm_runtime_mark_last_busy(&rt->dev); pm_runtime_put_autosuspend(&rt->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } @@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf, margining->ber_level = val; out_free: - free_page((unsigned long)buf); + kfree(buf); out_unlock: mutex_unlock(&tb->lock); @@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf, } } - free_page((unsigned long)buf); + kfree(buf); if (lane == -1) return -EINVAL; @@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, else goto err_free; - free_page((unsigned long)buf); + kfree(buf); scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { if (!margining->software) @@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, return count; err_free: - free_page((unsigned long)buf); + kfree(buf); return -EINVAL; } @@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file, mutex_unlock(&tb->lock); out_free: - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file, mutex_unlock(&tb->lock); out_free: - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file, mutex_unlock(&tb->lock); out_free: - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file, ret = -EINVAL; } - free_page((unsigned long)buf); + kfree(buf); return ret ? ret : count; } @@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf, out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); - free_page((unsigned long)buf); + kfree(buf); return ret < 0 ? ret : count; } diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index 7877319b1b03..519c67678b08 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc) debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops); } -static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) +static int dma_test_probe(struct tb_service *svc) { struct tb_xdomain *xd = tb_service_parent(svc); struct dma_test *dt; @@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = { static const struct tb_service_id dma_test_ids[] = { { TB_SERVICE("dma_test", 1) }, - { }, + { } }; MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 479fa4d265c2..12c88509a54f 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev) { struct tb_service *svc = tb_to_service(dev); struct tb_service_driver *driver; - const struct tb_service_id *id; driver = container_of(dev->driver, struct tb_service_driver, driver); - id = __tb_service_match(dev, &driver->driver); - return driver->probe(svc, id); + return driver->probe(svc); } static void tb_service_remove(struct device *dev) @@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, transmit_ring, receive_path, receive_ring); } +static void tb_domain_reset_interface(struct tb *tb) +{ + struct tb_nhi *nhi = tb->nhi; + + if (!nhi->ops->reset_interface) + return; + + guard(mutex)(&tb->lock); + + /* The reset clears the ring state so stop the control channel */ + tb_ctl_stop(tb->ctl); + nhi->ops->reset_interface(nhi); + tb_ctl_start(tb->ctl); +} + /** * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain * @tb: Domain disabling the DMA paths @@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { + int ret; + if (!tb->cm_ops->disconnect_xdomain_paths) return -ENOTSUPP; - return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, transmit_ring, receive_path, receive_ring); + if (ret) + return ret; + + if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) + tb_domain_reset_interface(tb); + + return 0; } static int disconnect_xdomain(struct device *dev, void *data) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 35e3c119d5ee..5809809f64d4 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) { struct ring_frame *frame, *n; struct ring_desc *descriptor; + u32 flags; + + flags = RING_DESC_POSTED; + if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) + flags |= RING_DESC_INTERRUPT; + list_for_each_entry_safe(frame, n, &ring->queue, list) { if (ring_full(ring)) break; @@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) descriptor = &ring->descriptors[ring->head]; descriptor->phys = frame->buffer_phy; descriptor->time = 0; - descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; + descriptor->flags = flags; if (ring->is_tx) { descriptor->length = frame->size; descriptor->eof = frame->eof; @@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); * @ring: Ring to poll * * This function can be called when @start_poll callback of the @ring - * has been called. It will read one completed frame from the ring and - * return it to the caller. + * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. + * It will read one completed frame from the ring and return it to the + * caller. * * Return: Pointer to &struct ring_frame, %NULL if there is no more * completed frames. @@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", transmit ? "TX" : "RX", hop, size); + if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { + dev_WARN(nhi->dev, + "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); + return NULL; + } + ring = kzalloc_obj(*ring); if (!ring) return NULL; @@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, if (!ring->descriptors) goto err_free_ring; - if (nhi->ops->request_ring_irq) { + if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) goto err_free_descs; } @@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) ring_iowrite32options(ring, flags, 0); } - ring_interrupt_active(ring, true); + if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) + ring_interrupt_active(ring, true); ring->running = true; err: spin_unlock(&ring->lock); @@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) RING_TYPE(ring), ring->hop); goto err; } - ring_interrupt_active(ring, false); + if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) + ring_interrupt_active(ring, false); ring_iowrite32options(ring, 0, 0); ring_iowrite64desc(ring, 0, 0); @@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi) dev_warn(nhi->dev, "timeout resetting host router\n"); } +/** + * nhi_reset_interface() - Reset the host interface + * @nhi: Host interface to reset + * + * Brings the registers in the memory BAR back to their default state and + * clears the End-to-End Flow Control state. The caller is responsible for + * stopping the control channel over the reset because it clears the ring + * state as well. + */ +void nhi_reset_interface(struct tb_nhi *nhi) +{ + u32 val; + + val = ioread32(nhi->iobase + REG_CAPS); + /* Only v1 host interfaces implement the reset */ + if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) + return; + + dev_dbg(nhi->dev, "issuing host interface reset\n"); + + iowrite32(REG_HOST_INTERFACE_RESET_RST, + nhi->iobase + REG_HOST_INTERFACE_RESET); + /* Wait for tHIReset (10 ms) to complete */ + usleep_range(10000, 20000); +} + static struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; @@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi) dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + nhi->host_reset = host_reset; + res = tb_domain_add(tb, host_reset); if (res) { /* diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index d488eadadfce..f72d6b274501 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data); irqreturn_t ring_msix(int irq, void *data); int nhi_probe(struct tb_nhi *nhi); void nhi_shutdown(struct tb_nhi *nhi); +void nhi_reset_interface(struct tb_nhi *nhi); + extern const struct dev_pm_ops nhi_pm_ops; /** @@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops; * @release_ring_irq: NHI specific interrupt release hook * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook + * @reset_interface: Resets the host interface */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -66,6 +69,7 @@ struct tb_nhi_ops { void (*release_ring_irq)(struct tb_ring *ring); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); + void (*reset_interface)(struct tb_nhi *nhi); }; /* @@ -116,11 +120,24 @@ struct tb_nhi_ops { #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 +#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 +#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 +#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b +#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c +#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 +#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 +#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 +#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c +#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d +#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d +#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 /* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) +#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index d6a197fabc74..99df60b6db36 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -115,6 +115,10 @@ struct ring_desc { #define REG_CAPS_VERSION_MASK GENMASK(23, 16) #define REG_CAPS_VERSION_2 0x40 +/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ +#define REG_HOST_INTERFACE_RESET 0x39858 +#define REG_HOST_INTERFACE_RESET_RST BIT(0) + #define REG_DMA_MISC 0x39864 #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index bbd186c29ef7..99333729f3c2 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) nhi->quirks |= QUIRK_E2E; break; } + } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { + switch (pdev->device) { + case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: + case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: + case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: + case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: + /* + * These AMD hosts may hang the Tx ring when the + * DMA paths are torn down so they need the host + * interface reset after each teardown. + */ + nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; + break; + } } } @@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) { struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); struct pci_dev *pdev = to_pci_dev(nhi->dev); - struct device *dev = &pdev->dev; int res, irq, nvec; ida_init(&nhi_pci->msix_ida); @@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) res = devm_request_irq(&pdev->dev, irq, nhi_msi, IRQF_NO_SUSPEND, "thunderbolt", nhi); if (res) - return dev_err_probe(dev, res, "request_irq failed, aborting\n"); + return res; } return 0; @@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring) ring->irq = 0; } -static void nhi_pci_shutdown(struct tb_nhi *nhi) +static void nhi_pci_release_irq(struct tb_nhi *nhi) { struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); struct pci_dev *pdev = to_pci_dev(nhi->dev); @@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { .post_nvm_auth = nhi_pci_complete_dma_port, .request_ring_irq = nhi_pci_ring_request_msix, .release_ring_irq = nhi_pci_ring_release_msix, - .shutdown = nhi_pci_shutdown, + .shutdown = nhi_pci_release_irq, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, + .reset_interface = nhi_reset_interface, }; /* Ice Lake specific NHI operations */ @@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi) static void icl_nhi_shutdown(struct tb_nhi *nhi) { - nhi_pci_shutdown(nhi); + nhi_pci_release_irq(nhi); icl_nhi_force_power(nhi, false); } @@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = { .release_ring_irq = nhi_pci_ring_release_msix, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, + .reset_interface = nhi_reset_interface, }; static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) @@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) return nhi_probe(&nhi_pci->nhi); } -static void nhi_pci_remove(struct pci_dev *pdev) +static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset) { struct tb *tb = pci_get_drvdata(pdev); struct tb_nhi *nhi = tb->nhi; + /* + * On system shutdown/reboot force a host router reset so the + * connection manager asserts DPR on connected Thunderbolt 3 devices + * before the router tree is removed (see tb_stop()). + */ + if (reset) + nhi->host_reset = true; + pm_runtime_get_sync(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_forbid(&pdev->dev); @@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev) nhi_shutdown(nhi); } +static void nhi_pci_remove(struct pci_dev *pdev) +{ + nhi_pci_do_remove(pdev, false); +} + +static void nhi_pci_shutdown(struct pci_dev *pdev) +{ + nhi_pci_do_remove(pdev, true); +} + static struct pci_device_id nhi_ids[] = { /* * We have to specify class, the TB bridges use the same device and @@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = { .id_table = nhi_ids, .probe = nhi_pci_probe, .remove = nhi_pci_remove, - .shutdown = nhi_pci_remove, + .shutdown = nhi_pci_shutdown, .driver.pm = &nhi_pm_ops, }; diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index 4cc86d8d6491..c737dd0ca6e7 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -9,10 +9,12 @@ #define pr_fmt(fmt) "tbstream: " fmt +#include #include #include #include #include +#include #include #include #include @@ -128,6 +130,7 @@ struct tbstream_ring { * @out_hopid: Out HopID * @ring_size: Size of the rings * @throttling: Interrupt throttling rate in ns + * @busy_poll: Instead of interrupts, busy poll the rings * @users: Number of times @cdev has been opened * @closed: CLOSE packet was received * @removed: Userspace removed the ConfigFS group underneath. @@ -147,6 +150,7 @@ struct tbstream_dev { int out_hopid; unsigned int ring_size; unsigned int throttling; + bool busy_poll; int users; bool closed; bool removed; @@ -512,8 +516,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, DMA_TO_DEVICE); if (pdf == TBSTREAM_DATA) { - if (copy_page_from_iter(sf->page, 0, size, from) != size) + if (copy_page_from_iter(sf->page, 0, size, from) != size) { + sdev->tx_ring.cons--; return ERR_PTR(-EFAULT); + } } else { memset(page_address(sf->page), 0, size); } @@ -534,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); } +static void +tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) +{ + struct ring_frame *frame; + + if (!sdev->busy_poll) + return; + + while ((frame = tb_ring_poll(ring->ring))) + frame->callback(ring->ring, frame, false); +} + static int tbstream_dev_send_close(struct tbstream_dev *sdev) { struct tbstream_frame *sf; + if (sdev->busy_poll) { + /* + * When busy polling it's the write(2) path that + * advances the completions so it is possible that the + * ring is full at this point. Advance the ring here so + * that there is room for the CLOSE packet to be sent. + */ + ktime_t timeout = ktime_add_ms(ktime_get(), 500); + + do { + if (tbstream_ring_available(&sdev->tx_ring)) + break; + tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + fsleep(15); + } while (ktime_before(ktime_get(), timeout)); + } + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); if (IS_ERR(sf)) return PTR_ERR(sf); @@ -547,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) static int tbstream_dev_start(struct tbstream_dev *sdev) { struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; u16 sof_mask, eof_mask; struct tb_ring *ring; int ret, e2e_tx_hop; - ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, - RING_FLAG_FRAME | RING_FLAG_E2E); + if (sdev->busy_poll) + flags |= RING_FLAG_NO_INTERRUPT; + + ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); if (!ring) return -ENOMEM; sdev->tx_ring.ring = ring; @@ -565,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) sof_mask = BIT(TBSTREAM_FRAME_START); eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); - ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, - RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, - sof_mask, eof_mask, NULL, NULL); + ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, + e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); if (!ring) { ret = -ENOMEM; goto err_free_tx_buffers; @@ -605,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) return ret; } +static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) +{ + const struct tbstream_ring *ring = &sdev->tx_ring; + + /* + * Everything is completed when number of free TX slots is back + * to the maximum. + */ + return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; +} + static void tbstream_dev_stop(struct tbstream_dev *sdev) { struct tb_xdomain *xd; - /* Wait for the ring to complete any outstanding frames */ - tb_ring_flush(sdev->tx_ring.ring, 500); - tb_ring_stop(sdev->tx_ring.ring); - tb_ring_flush(sdev->rx_ring.ring, 500); - tb_ring_stop(sdev->rx_ring.ring); + if (sdev->busy_poll) { + /* + * When busy polling we must advance the ring ourselves + * to push all outstanding frames on the wire. + */ + ktime_t timeout = ktime_add_ms(ktime_get(), 500); + + do { + if (tbstream_dev_tx_drained(sdev)) + break; + tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + fsleep(15); + } while (ktime_before(ktime_get(), timeout)); + + tb_ring_stop(sdev->tx_ring.ring); + tb_ring_stop(sdev->rx_ring.ring); + } else { + tb_ring_flush(sdev->tx_ring.ring, 500); + tb_ring_stop(sdev->tx_ring.ring); + tb_ring_flush(sdev->rx_ring.ring, 500); + tb_ring_stop(sdev->rx_ring.ring); + } xd = tbstream_dev_xdomain(sdev); if (xd) { @@ -631,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) sdev->tx_ring.ring = NULL; } +/* Use only with read_iter/write_iter() to handle nowait */ +static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) +{ + if (nowait) { + if (!mutex_trylock(&sdev->lock)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + } + return 0; +} + static ssize_t tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) { struct file *file = kiocb->ki_filp; + bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); size_t nbytes; int ret; @@ -643,35 +722,54 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (ret) return ret; - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; + ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; - while (!tbstream_ring_available(&sdev->rx_ring)) { - mutex_unlock(&sdev->lock); - - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; - ret = wait_event_interruptible(sdev->wait, - tbstream_ring_available(&sdev->rx_ring) || - tbstream_dev_valid(sdev) != 0 || - tbstream_dev_closed(sdev) || - tbstream_dev_removed(sdev)); - if (ret) - return ret; + for (;;) { + /* When busy polling, advance any completions manually */ + tbstream_dev_poll_ring(sdev, &sdev->rx_ring); ret = tbstream_dev_valid(sdev); + if (ret) { + mutex_unlock(&sdev->lock); + return ret; + } + + if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { + mutex_unlock(&sdev->lock); + return 0; + } + + if (tbstream_ring_available(&sdev->rx_ring)) + break; + + mutex_unlock(&sdev->lock); + + if (nowait) + return -EAGAIN; + + if (sdev->busy_poll) { + if (signal_pending(current)) + return -ERESTARTSYS; + cond_resched(); + } else { + ret = wait_event_interruptible(sdev->wait, + tbstream_ring_available(&sdev->rx_ring) || + tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || + tbstream_dev_removed(sdev)); + if (ret) + return ret; + } + + ret = tbstream_dev_lock(sdev, nowait); if (ret) return ret; - - if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) - return 0; - - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; } nbytes = 0; - while (nbytes < iov_iter_count(to)) { + while (iov_iter_count(to)) { struct tbstream_frame *sf; size_t size, sf_size; @@ -693,7 +791,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) } sf_size = tb_ring_frame_size(&sf->frame); - size = min(iov_iter_count(to) - nbytes, sf_size); + size = min(iov_iter_count(to), sf_size); if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { ret = -EFAULT; @@ -727,6 +825,7 @@ static ssize_t tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) { struct file *file = kiocb->ki_filp; + bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); size_t nbytes; int ret; @@ -735,38 +834,56 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) if (ret) return ret; - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; + ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; - while (!tbstream_ring_available(&sdev->tx_ring)) { - mutex_unlock(&sdev->lock); - - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; - ret = wait_event_interruptible(sdev->wait, - tbstream_ring_available(&sdev->tx_ring) || - tbstream_dev_valid(sdev) != 0 || - tbstream_dev_closed(sdev) || - tbstream_dev_removed(sdev)); - if (ret) - return ret; + for (;;) { + tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ret = tbstream_dev_valid(sdev); + if (ret) { + mutex_unlock(&sdev->lock); + return ret; + } + + if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { + mutex_unlock(&sdev->lock); + return -ENXIO; + } + + if (tbstream_ring_available(&sdev->tx_ring)) + break; + + mutex_unlock(&sdev->lock); + + if (nowait) + return -EAGAIN; + + if (sdev->busy_poll) { + if (signal_pending(current)) + return -ERESTARTSYS; + cond_resched(); + } else { + ret = wait_event_interruptible(sdev->wait, + tbstream_ring_available(&sdev->tx_ring) || + tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || + tbstream_dev_removed(sdev)); + if (ret) + return ret; + } + + ret = tbstream_dev_lock(sdev, nowait); if (ret) return ret; - - if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) - return -ENXIO; - - if (mutex_lock_interruptible(&sdev->lock)) - return -ERESTARTSYS; } nbytes = 0; - while (nbytes < iov_iter_count(from)) { + while (iov_iter_count(from)) { size_t size; - size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); + size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE); ret = tbstream_dev_send_data(sdev, from, size); if (ret) { /* @@ -793,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); __poll_t mask = 0; + /* + * Without interrupts there is nothing that can wake us up so + * return failure instead. + */ + if (sdev->busy_poll) + return EPOLLERR; + poll_wait(file, &sdev->wait, wait); guard(mutex)(&sdev->lock); if (tbstream_dev_valid(sdev) != 0) { @@ -902,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) return container_of(group, struct tbstream_dev, group); } +static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%u\n", sdev->busy_poll); +} + +static ssize_t +tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, + size_t count) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + bool busy_poll; + int ret; + + ret = kstrtobool(buf, &busy_poll); + if (ret) + return ret; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + sdev->busy_poll = busy_poll; + return count; +} +CONFIGFS_ATTR(tbstream_dev_, busy_poll); + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) { struct config_group *group = to_config_group(item); @@ -1208,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, CONFIGFS_ATTR(tbstream_dev_, throttling); static struct configfs_attribute *tbstream_dev_attrs[] = { + &tbstream_dev_attr_busy_poll, &tbstream_dev_attr_index, &tbstream_dev_attr_in_hopid, &tbstream_dev_attr_out_hopid, @@ -1540,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream) config_group_put(&sg->group); } -static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) +static int tbstream_probe(struct tb_service *svc) { struct tbstream *stream; @@ -1630,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = { static const struct tb_service_id tbstream_ids[] = { { TB_SERVICE("stream", 1) }, - { }, + { } }; MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index a830c82bb905..404c0693df50 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port) return __tb_port_enable(port, false); } -static int tb_port_reset(struct tb_port *port) +/** + * tb_port_reset() - Reset the port + * @port: Port to reset + * + * Resets @port. For USB4 ports this issues a USB4 port reset and for + * legacy ports the link controller port is reset. + * + * Return: %0 on success, negative errno otherwise. + */ +int tb_port_reset(struct tb_port *port) { if (tb_switch_is_usb4(port->sw)) return port->cap_usb4 ? usb4_port_reset(port) : 0; diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index f43f2d952372..47753a5c0f2e 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, static void tb_stop(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); + struct tb_nhi *nhi = tb->nhi; struct tb_tunnel *tunnel; + struct tb_port *port; struct tb_tunnel *n; cancel_delayed_work(&tcm->remove_work); @@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb) tb_tunnel_deactivate(tunnel); tb_tunnel_put(tunnel); } + /* + * Signal disconnect to connected devices before the router tree is + * removed below. A Thunderbolt 3 device directly connected to a USB4 + * host otherwise never receives a disconnect indication, leaving + * firmware to poll the dead link for up to ~60 s which on some + * platforms turns the shutdown into a warm reset. Asserting + * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device + * detects SBRX low and goes to Uninitialized Unplugged immediately. + */ + if (nhi->host_reset) { + tb_switch_for_each_port(tb->root_switch, port) { + if (!tb_port_is_null(port) || !tb_port_has_remote(port)) + continue; + if (tb_switch_is_usb4(port->remote->sw)) + continue; + if (tb_port_reset(port)) + tb_port_dbg(port, "downstream port reset failed, continuing\n"); + } + } tb_switch_remove(tb->root_switch); tb->root_switch = NULL; tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index ec9192b61bc0..4373336d9425 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter); int tb_port_unlock(struct tb_port *port); int tb_port_enable(struct tb_port *port); int tb_port_disable(struct tb_port *port); +int tb_port_reset(struct tb_port *port); int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); void tb_port_release_in_hopid(struct tb_port *port, int hopid); int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index b7f32305f14a..7e8284575dff 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -48,6 +48,9 @@ #define TB_DP_AUX_PRIORITY 2 #define TB_DP_AUX_WEIGHT 1 +/* struct tb_regs_hop::initial_credits is 7 bits wide */ +#define TB_MAX_CREDITS 127 + /* Minimum number of credits needed for PCIe path */ #define TB_MIN_PCIE_CREDITS 6U /* @@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) if (available < TB_MIN_DMA_CREDITS) return -ENOSPC; - while (credits > available) - credits--; + credits = min(credits, available); tb_port_dbg(port, "reserving %u credits for DMA path\n", credits); @@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_tunnel *tunnel; size_t npaths = 0, i = 0; struct tb_path *path; - int credits; + unsigned int credits; /* Ring 0 is reserved for control channel */ if (WARN_ON(!receive_ring || !transmit_ring)) @@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, tunnel->destroy = tb_dma_destroy; credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); + if (credits > TB_MAX_CREDITS) { + tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n", + credits, TB_MAX_CREDITS); + credits = TB_MAX_CREDITS; + } if (receive_ring > 0) { path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 86b2f7474670..c179bd751fe4 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, */ int tb_register_service_driver(struct tb_service_driver *drv) { + if (!drv->probe) + return -EINVAL; + drv->driver.bus = &tb_bus_type; return driver_register(&drv->driver); } @@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work) tb_xdomain_failed(xd); } else { xd->state = XDOMAIN_STATE_ENUMERATED; + tb_xdomain_queue_properties_changed(xd); } break; diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c index 0befbf63d1cc..5c1e1f521555 100644 --- a/drivers/usb/atm/xusbatm.c +++ b/drivers/usb/atm/xusbatm.c @@ -79,7 +79,7 @@ static int xusbatm_bind(struct usbatm_data *usbatm, struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *usb_dev = interface_to_usbdev(intf); - int drv_ix = id - xusbatm_usb_ids; + int drv_ix = id->driver_info; int rx_alt = rx_altsetting[drv_ix]; int tx_alt = tx_altsetting[drv_ix]; struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]); @@ -168,7 +168,8 @@ static struct usb_driver xusbatm_usb_driver = { .name = xusbatm_driver_name, .probe = xusbatm_usb_probe, .disconnect = usbatm_usb_disconnect, - .id_table = xusbatm_usb_ids + .id_table = xusbatm_usb_ids, + .no_dynamic_id = 1, }; static int __init xusbatm_init(void) @@ -190,6 +191,7 @@ static int __init xusbatm_init(void) xusbatm_usb_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; xusbatm_usb_ids[i].idVendor = vendor[i]; xusbatm_usb_ids[i].idProduct = product[i]; + xusbatm_usb_ids[i].driver_info = i; xusbatm_drivers[i].driver_name = xusbatm_driver_name; xusbatm_drivers[i].bind = xusbatm_bind; diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c index a832f5696f2a..ae6c94f41cb3 100644 --- a/drivers/usb/c67x00/c67x00-sched.c +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -761,13 +761,13 @@ static int c67x00_add_iso_urb(struct c67x00_hcd *c67x00, struct urb *urb) ret); urb->iso_frame_desc[urbp->cnt].actual_length = 0; urb->iso_frame_desc[urbp->cnt].status = ret; - if (urbp->cnt + 1 == urb->number_of_packets) - c67x00_giveback_urb(c67x00, urb, 0); } urbp->ep_data->next_frame = frame_add(urbp->ep_data->next_frame, urb->interval); urbp->cnt++; + if (ret && urbp->cnt == urb->number_of_packets) + c67x00_giveback_urb(c67x00, urb, 0); } return 0; } diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index bb5405460035..e3f32c3e9535 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); if (IS_ERR(cdns->usb2_phy)) return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), - "Failed to get cdn3,usb2-phy\n"); + "Failed to get cdns3,usb2-phy\n"); cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); if (IS_ERR(cdns->usb3_phy)) return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), - "Failed to get cdn3,usb3-phy\n"); + "Failed to get cdns3,usb3-phy\n"); ret = phy_init(cdns->usb2_phy); if (ret) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 56d2ba824a0b..282314eea7fc 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -528,7 +528,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) if (data->wakeup_irq > 0) { irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name); if (!irq_name) { - dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); + ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); goto err_clk; } diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index af9ae55dae14..51cd9320a736 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -71,7 +71,6 @@ struct usbtmc_dev_capabilities { * allocated for each USBTMC device in the driver's probe function. */ struct usbtmc_device_data { - const struct usb_device_id *id; struct usb_device *usb_dev; struct usb_interface *intf; struct list_head file_list; @@ -2394,7 +2393,6 @@ static int usbtmc_probe(struct usb_interface *intf, return -ENOMEM; data->intf = intf; - data->id = id; data->usb_dev = usb_get_dev(interface_to_usbdev(intf)); usb_set_intfdata(intf, data); kref_init(&data->kref); diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 346a2faa9bb8..0e79b7f8a73d 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, usb_endpoint_xfer_int(&ep->desc)) && desc->bmAttributes != 0) { dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", - usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", + usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", desc->bmAttributes, cfgno, inum, asnum, ep->desc.bEndpointAddress); ep->ss_ep_comp.bmAttributes = 0; diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index a247da73f34d..6f0354aba38b 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -37,6 +37,7 @@ */ #include +#include #include #include #include @@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, return 0; /* allocate 2^1 pages = 8K (on i386); * should be more than enough for one device */ - pages_start = (char *)__get_free_pages(GFP_NOIO, 1); + pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO); if (!pages_start) return -ENOMEM; @@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, if (length > *nbytes) length = *nbytes; if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { - free_pages((unsigned long)pages_start, 1); + kfree(pages_start); return -EFAULT; } *nbytes -= length; @@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, } else *skip_bytes -= length; - free_pages((unsigned long)pages_start, 1); + kfree(pages_start); /* Now look at all of this device's children. */ usb_hub_for_each_child(usbdev, chix, childdev) { diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index e191934623c7..101cb9425480 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1113,7 +1113,6 @@ static int usbdev_release(struct inode *inode, struct file *file) if (!ps->suspend_allowed) usb_autosuspend_device(dev); usb_unlock_device(dev); - usb_put_dev(dev); put_pid(ps->disc_pid); put_cred(ps->cred); @@ -1122,6 +1121,7 @@ static int usbdev_release(struct inode *inode, struct file *file) free_async(as); as = async_getcompleted(ps); } + usb_put_dev(dev); kfree(ps); return 0; @@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) if (!connected(ps)) return -ENODEV; + if (ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + + intf = usb_ifnum_to_if(ps->dev, ctl->ifno); + if (!intf) + return -EINVAL; + /* alloc buffer */ size = _IOC_SIZE(ctl->ioctl_code); if (size > 0) { @@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) } } - if (ps->dev->state != USB_STATE_CONFIGURED) - retval = -EHOSTUNREACH; - else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) - retval = -EINVAL; - else switch (ctl->ioctl_code) { + switch (ctl->ioctl_code) { /* disconnect kernel driver from interface */ case USBDEVFS_DISCONNECT: diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f63004417058..7f33fe5ba03b 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -228,14 +228,16 @@ static void usb_free_dynids(struct usb_driver *usb_drv) } static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf, - const struct usb_driver *drv) + const struct usb_driver *drv, + struct usb_device_id *id_copy) { struct usb_dynid *dynid; guard(mutex)(&usb_dynids_lock); list_for_each_entry(dynid, &drv->dynids.list, node) { if (usb_match_one_id(intf, &dynid->id)) { - return &dynid->id; + *id_copy = dynid->id; + return id_copy; } } return NULL; @@ -321,6 +323,7 @@ static int usb_probe_interface(struct device *dev) struct usb_interface *intf = to_usb_interface(dev); struct usb_device *udev = interface_to_usbdev(intf); const struct usb_device_id *id; + struct usb_device_id id_copy; int error = -ENODEV; int lpm_disable_error = -ENODEV; @@ -340,7 +343,7 @@ static int usb_probe_interface(struct device *dev) return error; } - id = usb_match_dynamic_id(intf, driver); + id = usb_match_dynamic_id(intf, driver, &id_copy); if (!id) id = usb_match_id(intf, driver->id_table); if (!id) @@ -892,6 +895,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv) struct usb_interface *intf; const struct usb_driver *usb_drv; const struct usb_device_id *id; + struct usb_device_id id_copy; /* device drivers never match interfaces */ if (is_usb_device_driver(drv)) @@ -904,7 +908,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv) if (id) return 1; - id = usb_match_dynamic_id(intf, usb_drv); + id = usb_match_dynamic_id(intf, usb_drv, &id_copy); if (id) return 1; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index d92bf887739d..3345b3298daf 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, mutex_lock(&hub->status_mutex); ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); if (ret < len) { - if (ret != -ENODEV) - dev_err(hub->intfdev, - "%s failed (err = %d)\n", __func__, ret); if (ret >= 0) ret = -EIO; + if (ret != -ENODEV) + dev_dbg(hub->intfdev, + "get_port_status failed: err = %d\n", ret); } else { *status = le16_to_cpu(hub->status->port.wPortStatus); *change = le16_to_cpu(hub->status->port.wPortChange); @@ -753,10 +753,12 @@ void usb_wakeup_notification(struct usb_device *hdev, { struct usb_hub *hub; struct usb_port *port_dev; + unsigned long flags; if (!hdev) return; + spin_lock_irqsave(&device_state_lock, flags); hub = usb_hub_to_struct_hub(hdev); if (hub) { port_dev = hub->ports[portnum - 1]; @@ -766,6 +768,7 @@ void usb_wakeup_notification(struct usb_device *hdev, set_bit(portnum, hub->wakeup_bits); kick_hub_wq(hub); } + spin_unlock_irqrestore(&device_state_lock, flags); } EXPORT_SYMBOL_GPL(usb_wakeup_notification); @@ -991,10 +994,12 @@ static int hub_hub_status(struct usb_hub *hub, mutex_lock(&hub->status_mutex); ret = get_hub_status(hub->hdev, &hub->status->hub); - if (ret < 0) { + if (ret < (int)sizeof(hub->status->hub)) { + if (ret >= 0) + ret = -EIO; if (ret != -ENODEV) - dev_err(hub->intfdev, - "%s failed (err = %d)\n", __func__, ret); + dev_dbg(hub->intfdev, + "get_hub_status failed: err = %d\n", ret); } else { *status = le16_to_cpu(hub->status->hub.wHubStatus); *change = le16_to_cpu(hub->status->hub.wHubChange); diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 7d3e641806f8..9565e6a52725 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) /** * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state - * * @hsotg: Programming view of DWC_otg controller - * + * @remotewakeup: Whether this is a remote wakeup */ void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) { diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c8b02c27d27d..102169b40419 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -4680,6 +4680,7 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) { struct dwc2_hsotg *hsotg = to_hsotg(gadget); unsigned long flags; + int ret = 0; dev_dbg(hsotg->dev, "%s: is_on: %d op_state: %d\n", __func__, is_on, hsotg->op_state); @@ -4691,6 +4692,13 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) } spin_lock_irqsave(&hsotg->lock, flags); + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) { + dev_err(hsotg->dev, "exit partial_power_down failed\n"); + goto exit; + } + } if (is_on) { hsotg->enabled = 1; dwc2_hsotg_core_init_disconnected(hsotg, false); @@ -4704,9 +4712,10 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) } hsotg->gadget.speed = USB_SPEED_UNKNOWN; +exit: spin_unlock_irqrestore(&hsotg->lock, flags); - return 0; + return ret; } static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index ceb49f2f8004..fd5c2cd36c59 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) ret, dwc->current_limit); } -static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) +static int dwc3_psy_notifier(struct notifier_block *nb, + unsigned long event, void *data) { - struct power_supply *usb_psy; - const char *usb_psy_name; + struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); + struct power_supply *psy = data; + unsigned long flags; + + if (dwc->usb_psy) + return NOTIFY_DONE; + + if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) + return NOTIFY_DONE; + + /* Explicitly get the reference for this psy */ + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return NOTIFY_DONE; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * The USB power_supply may already be set. This can happen if notifier + * callbacks for the USB power_supply race, or if a previous notifier + * callback has already successfully fetched and associated the instance. + * In such cases, release the newly acquired reference and ignore + * subsequent notifications until the notifier is unregistered. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return NOTIFY_DONE; + } + + dwc->usb_psy = psy; + if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) + schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); + + return NOTIFY_OK; +} + +static void dwc3_get_usb_power_supply(struct dwc3 *dwc) +{ + struct power_supply *psy; + unsigned long flags; int ret; - ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); + ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); if (ret < 0) - return NULL; - - usb_psy = power_supply_get_by_name(usb_psy_name); - if (!usb_psy) - return ERR_PTR(-EPROBE_DEFER); + return; INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); - return usb_psy; + + dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; + dwc->psy_nb.notifier_call = dwc3_psy_notifier; + ret = power_supply_reg_notifier(&dwc->psy_nb); + if (ret) { + dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); + dwc->psy_nb.notifier_call = NULL; + return; + } + + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return; + + /* Unregister the notifier now that we have the power supply */ + power_supply_unreg_notifier(&dwc->psy_nb); + dwc->psy_nb.notifier_call = NULL; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * It is possible that the notifier callback ran before we reached here + * and successfully fetched the power supply. In that case we need to + * release the above reference. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return; + } + + dwc->usb_psy = psy; + spin_unlock_irqrestore(&dwc->lock, flags); } int dwc3_core_probe(const struct dwc3_probe_data *data) @@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc3_get_software_properties(dwc, &data->properties); - dwc->usb_psy = dwc3_get_usb_power_supply(dwc); - if (IS_ERR(dwc->usb_psy)) - return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); + spin_lock_init(&dwc->lock); + + dwc3_get_usb_power_supply(dwc); if (!data->ignore_clocks_and_resets) { dwc->reset = devm_reset_control_array_get_optional_shared(dev); @@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc->num_usb3_ports = 1; } - spin_lock_init(&dwc->lock); mutex_init(&dwc->mutex); pm_runtime_get_noresume(dev); @@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) err_assert_reset: reset_control_assert(dwc->reset); err_put_psy: + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); if (dwc->usb_psy) power_supply_put(dwc->usb_psy); @@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) dwc3_free_event_buffers(dwc); + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) { cancel_work_sync(&dwc->vbus_draw_work); power_supply_put(dwc->usb_psy); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index e0dee9d28740..608daeb7ef10 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -722,7 +722,6 @@ struct dwc3_event_buffer { * @cancelled_list: list of cancelled requests for this endpoint * @pending_list: list of pending requests for this endpoint * @started_list: list of started requests on this endpoint - * @regs: pointer to first endpoint register * @trb_pool: array of transaction buffers * @trb_pool_dma: dma address of @trb_pool * @trb_enqueue: enqueue 'pointer' into TRB array @@ -1059,6 +1058,8 @@ struct dwc3_glue_ops { * @role_switch_default_mode: default operation mode of controller while * usb role is USB_ROLE_NONE. * @usb_psy: pointer to power supply interface. + * @usb_psy_name: name of the USB power supply + * @psy_nb: power supply notifier block * @vbus_draw_work: Work to set the vbus drawing limit * @current_limit: How much current to draw from vbus, in milliAmperes. * @usb2_phy: pointer to USB2 PHY @@ -1251,9 +1252,13 @@ struct dwc3 { enum usb_dr_mode role_switch_default_mode; struct power_supply *usb_psy; + const char *usb_psy_name; + struct notifier_block psy_nb; struct work_struct vbus_draw_work; unsigned int current_limit; +#define DWC3_CURRENT_UNSPECIFIED UINT_MAX + u32 fladj; u32 ref_clk_per; u32 irq_gadget; diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index e11d7643f966..632634d6e81e 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62) dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); - clk_prepare_enable(am62->usb2_refclk); + ret = clk_prepare_enable(am62->usb2_refclk); + if (ret) + return ret; /* Set mode valid bit to indicate role is valid */ reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); @@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev) { struct dwc3_am62 *am62 = dev_get_drvdata(dev); u32 reg; + int ret; reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); if (reg != USBSS_DEBUG_CFG_DISABLED) { /* lost power/context */ - dwc3_ti_init(am62); + ret = dwc3_ti_init(am62); + if (ret) + return ret; } else { dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); - clk_prepare_enable(am62->usb2_refclk); + ret = clk_prepare_enable(am62->usb2_refclk); + if (ret) + return ret; } if (device_may_wakeup(dev)) { diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index f43f73ac36ff..ac68b4218b56 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) pm_runtime_mark_last_busy(qcom->dev); } -struct dwc3_glue_ops dwc3_qcom_glue_ops = { +static struct dwc3_glue_ops dwc3_qcom_glue_ops = { .pre_set_role = dwc3_qcom_set_role_notifier, .pre_run_stop = dwc3_qcom_run_stop_notifier, }; diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c index 9b9525592a85..b832505e1b04 100644 --- a/drivers/usb/dwc3/dwc3-xilinx.c +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) dwc3_xlnx_mask_phy_rst(priv_data, false); - /* Assert and De-assert reset */ - ret = reset_control_assert(crst); - if (ret < 0) { - dev_err_probe(dev, ret, "failed to assert Reset\n"); - return ret; - } - - ret = reset_control_deassert(crst); - if (ret < 0) { - dev_err_probe(dev, ret, "failed to De-assert Reset\n"); - return ret; - } + /* assert and deassert reset */ + ret = reset_control_reset(crst); + if (ret) + return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); dwc3_xlnx_mask_phy_rst(priv_data, true); dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); @@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) } if (priv_data->usb3_phy) { - /* Set PIPE Power Present signal in FPD Power Present Register*/ + /* Set PIPE Power Present signal in FPD Power Present Register */ writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 1082e9c9afaa..fa944856f956 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3124,15 +3124,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) { struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; if (dwc->usb2_phy) return usb_phy_set_power(dwc->usb2_phy, mA); - if (!dwc->usb_psy) - return -EOPNOTSUPP; + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc->usb_psy) { + if (!dwc->psy_nb.notifier_call) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -EOPNOTSUPP; + } + dwc->current_limit = mA; + spin_unlock_irqrestore(&dwc->lock, flags); + dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); + return 0; + } dwc->current_limit = mA; schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); return 0; } @@ -3497,6 +3508,7 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) } dwc3_debugfs_remove_endpoint_dir(dep); + cancel_delayed_work_sync(&dep->nostream_work); kfree(dep); } } diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 96b588bd08cd..c5674161b2b0 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "../host/xhci-port.h" #include "../host/xhci-ext-caps.h" @@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) return; } - op_regs_base = HC_LENGTH(readl(xhci_regs)); + op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); reg = readl(xhci_regs + XHCI_HCSPARAMS1); - port_num = HCS_MAX_PORTS(reg); + port_num = FIELD_GET(HCS_MAX_PORTS, reg); for (i = 1; i <= port_num; i++) { offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 183a25f65ac8..51df6d1d1487 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy) if (copy) { str = copy; } else { - str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); if (!str) return -ENOMEM; } - strcpy(str, s); + memcpy(str, s, ret + 1); if (str[ret - 1] == '\n') str[ret - 1] = '\0'; *s_copy = str; @@ -1177,7 +1177,7 @@ static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) struct gadget_info *gi = os_desc_item_to_gadget_info(item); int res; - res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN, + res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN / 2, UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1); page[res++] = '\n'; @@ -1199,7 +1199,7 @@ static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page, mutex_lock(&gi->lock); res = utf8s_to_utf16s(page, l, UTF16_LITTLE_ENDIAN, (wchar_t *) gi->qw_sign, - OS_STRING_QW_SIGN_LEN); + OS_STRING_QW_SIGN_LEN / 2); if (res > 0) res = len; mutex_unlock(&gi->lock); diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 44218be1e676..43962e05eacf 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -159,7 +159,9 @@ struct ffs_epfile { struct mutex mutex; struct ffs_data *ffs; - struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ /* * Buffer for holding data from partial reads which may happen since @@ -219,12 +221,13 @@ struct ffs_epfile { struct ffs_buffer *read_buffer; #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) - char name[5]; + char name[8]; unsigned char in; /* P: ffs->eps_lock */ unsigned char isoc; /* P: ffs->eps_lock */ - unsigned char _pad; + u8 zlp_enabled; /* P: ffs->eps_lock */ + bool is_rw_proxy; /* Protects dmabufs */ struct mutex dmabufs_mutex; @@ -548,6 +551,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; +retry: /* Acquire mutex */ ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); if (ret < 0) @@ -582,10 +586,15 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, break; } - if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq, - ffs->ev.count)) { - ret = -EINTR; - break; + if (!ffs->ev.count) { + spin_unlock_irq(&ffs->ev.waitq.lock); + mutex_unlock(&ffs->mutex); + + if (wait_event_interruptible_exclusive(ffs->ev.waitq, + ffs->ev.count)) + return -EINTR; + + goto retry; } /* unlocks spinlock */ @@ -867,9 +876,15 @@ static void ffs_user_copy_worker(struct work_struct *work) bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; if (io_data->read && ret > 0) { - kthread_use_mm(io_data->mm); - ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); - kthread_unuse_mm(io_data->mm); + if (mmget_not_zero(io_data->mm)) { + kthread_use_mm(io_data->mm); + ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); + kthread_unuse_mm(io_data->mm); + mmput(io_data->mm); + } else { + ret = -EFAULT; + } + mmdrop(io_data->mm); } io_data->kiocb->ki_complete(io_data->kiocb, ret); @@ -979,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, return ret; } -static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) +static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) { - struct ffs_epfile *epfile = file->private_data; struct ffs_ep *ep; int ret; @@ -1008,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) char *data = NULL; ssize_t ret, data_len = -EINVAL; int halt; + bool is_rw_proxy = epfile->is_rw_proxy; /* Are we still active? */ if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; - ep = ffs_epfile_wait_ep(file); + /* Proxy to base endpoint if rw_proxy */ + if (is_rw_proxy) + epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; + + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) return PTR_ERR(ep); /* Do we halt? */ - halt = (!io_data->read == !epfile->in); + halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); if (halt && epfile->isoc) return -EINVAL; @@ -1115,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->buf = data; req->num_sgs = 0; } + + req->zero = !io_data->read ? epfile->zlp_enabled : 0; req->length = data_len; io_data->buf = data; @@ -1166,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->buf = data; req->num_sgs = 0; } + + req->zero = !io_data->read ? epfile->zlp_enabled : 0; req->length = data_len; io_data->buf = data; @@ -1264,16 +1287,22 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) kiocb->private = p; - if (p->aio) + if (p->aio) { + mmgrab(p->mm); kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } res = ffs_epfile_io(kiocb->ki_filp, p); if (res == -EIOCBQUEUED) return res; - if (p->aio) + if (p->aio) { + kiocb->ki_complete(kiocb, res); + mmdrop(p->mm); kfree(p); - else + return -EIOCBQUEUED; + } else { *from = p->data; + } return res; } @@ -1308,16 +1337,21 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) kiocb->private = p; - if (p->aio) + if (p->aio) { + mmgrab(p->mm); kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } res = ffs_epfile_io(kiocb->ki_filp, p); if (res == -EIOCBQUEUED) return res; if (p->aio) { + kiocb->ki_complete(kiocb, res); + mmdrop(p->mm); kfree(p->to_free); kfree(p); + return -EIOCBQUEUED; } else { *to = p->data; } @@ -1643,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file, priv = attach->importer_priv; - ep = ffs_epfile_wait_ep(file); + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) { ret = PTR_ERR(ep); goto err_attachment_put; @@ -1682,13 +1716,13 @@ static int ffs_dmabuf_transfer(struct file *file, /* In the meantime, endpoint got disabled or changed. */ if (epfile->ep != ep) { ret = -ESHUTDOWN; - goto err_fence_put; + goto err_fence_free; } usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); if (!usb_req) { ret = -ENOMEM; - goto err_fence_put; + goto err_fence_free; } /* @@ -1709,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file, /* Now that the dma_fence is in place, queue the transfer. */ + usb_req->zero = epfile->zlp_enabled; usb_req->length = req->length; usb_req->buf = NULL; usb_req->sg = priv->sgt->sgl; @@ -1737,9 +1772,9 @@ static int ffs_dmabuf_transfer(struct file *file, return ret; -err_fence_put: +err_fence_free: spin_unlock_irq(&epfile->ffs->eps_lock); - dma_fence_put(&fence->base); + kfree(fence); err_resv_unlock: dma_resv_unlock(dmabuf->resv); err_attachment_put: @@ -1756,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, struct ffs_epfile *epfile = file->private_data; struct ffs_ep *ep; int ret; + __u32 enable_zlp = 0; if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; + if (epfile->is_rw_proxy) + return -ENOTTY; + switch (code) { case FUNCTIONFS_DMABUF_ATTACH: { @@ -1788,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, return ffs_dmabuf_transfer(file, &req); } + /* + * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace + * to configure ZLP behavior immediately without blocking indefinitely + * while waiting for the USB host to connect and enable the endpoint. + */ + case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: + if (!epfile->in) + return -EINVAL; + + if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) + return -EFAULT; + + spin_lock_irq(&epfile->ffs->eps_lock); + epfile->zlp_enabled = !!enable_zlp; + spin_unlock_irq(&epfile->ffs->eps_lock); + + return 0; default: break; } /* Wait for endpoint to be enabled */ - ep = ffs_epfile_wait_ep(file); + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) return PTR_ERR(ep); @@ -2191,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs) if (epfiles) ffs_epfiles_destroy(ffs->sb, epfiles, - ffs->eps_count); + ffs->epfiles_count); if (ffs->setup_state == FFS_SETUP_PENDING) __ffs_ep0_stall(ffs); @@ -2250,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs) * copy of epfile will save us from use-after-free. */ if (epfiles) { - ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); ffs->epfiles = NULL; } @@ -2348,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs) static int ffs_epfiles_create(struct ffs_data *ffs) { struct ffs_epfile *epfile, *epfiles; - unsigned i, count; + unsigned int i, count, epfiles_count; int err; count = ffs->eps_count; - epfiles = kzalloc_objs(*epfiles, count); + epfiles_count = count; + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) + epfiles_count += count / 2; + ffs->epfiles_count = epfiles_count; + + epfiles = kzalloc_objs(*epfiles, epfiles_count); if (!epfiles) return -ENOMEM; @@ -2375,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) } } + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + struct ffs_epfile *comp = epfiles + count; + + for (i = 0; i < count; i += 2, ++comp) { + struct ffs_epfile *ep1 = &epfiles[i]; + struct ffs_epfile *ep2 = &epfiles[i + 1]; + bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; + + comp->ffs = ffs; + comp->is_rw_proxy = true; + comp->epfile_in = ep1_in ? ep1 : ep2; + comp->epfile_out = ep1_in ? ep2 : ep1; + mutex_init(&comp->mutex); + mutex_init(&comp->dmabufs_mutex); + INIT_LIST_HEAD(&comp->dmabufs); + snprintf(comp->name, sizeof(comp->name), "%s_rw", + epfiles[i].name); + err = ffs_sb_create_file(ffs->sb, comp->name, + comp, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); + return err; + } + } + } + ffs->epfiles = epfiles; return 0; } @@ -2952,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, FUNCTIONFS_VIRTUAL_ADDR | FUNCTIONFS_EVENTFD | FUNCTIONFS_ALL_CTRL_RECIP | - FUNCTIONFS_CONFIG0_SETUP)) { + FUNCTIONFS_CONFIG0_SETUP | + FUNCTIONFS_RW_PROXY_EPS)) { ret = -ENOSYS; goto error; } @@ -3040,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, goto error; } + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + if (ffs->eps_count % 2) { + ret = -EINVAL; + goto error; + } + + for (i = 1; i < ffs->eps_count; i += 2) { + if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == + (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { + ret = -EINVAL; + goto error; + } + } + } + ffs->raw_descs_data = _data; ffs->raw_descs = raw_descs; ffs->raw_descs_length = data - raw_descs; @@ -3335,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, struct usb_request *req; struct usb_ep *ep; u8 bEndpointAddress; - u16 wMaxPacketSize; + __le16 wMaxPacketSize; /* * We back up bEndpointAddress because autoconfig overwrites diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index b7b06cb79ff5..a50743caf083 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -203,7 +203,6 @@ /*------------------------------------------------------------------------*/ #define FSG_DRIVER_DESC "Mass Storage Function" -#define FSG_DRIVER_VERSION "2009/09/11" static const char fsg_string_interface[] = "Mass Storage"; @@ -3513,8 +3512,6 @@ static struct usb_function_instance *fsg_alloc_inst(void) if (rc) goto release_common; - pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); - memset(&config, 0, sizeof(config)); config.removable = true; rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index 19fdac024343..a4b72a6fad8a 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -2473,6 +2473,7 @@ static void f_midi2_ep_opts_release(struct config_item *item) { struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); + configfs_remove_default_groups(&opts->group); kfree(opts->info.ep_name); kfree(opts->info.product_id); kfree(opts); @@ -2639,6 +2640,7 @@ static void f_midi2_free_inst(struct usb_function_instance *f) opts = container_of(f, struct f_midi2_opts, func_inst); + configfs_remove_default_groups(&opts->func_inst.group); kfree(opts->info.iface_name); kfree(opts); } diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index b3fa5a17fd2d..9e6d4f39900a 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -1675,19 +1675,25 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts, func_inst); - mutex_lock(&opts->dep_lock); - if (!opts->ready) - goto unlock_dep; + if (!READ_ONCE(opts->ready)) + goto unlock_inst; if (opts->has_dep) { if (!try_module_get(opts->dependent)) - goto unlock_dep; + goto unlock_inst; } else { + /* + * configfs_depend_item_unlocked() may acquire the configfs + * root inode lock when the target belongs to a different + * subsystem. Calling it under dep_lock would create a + * circular dependency: + * dep_lock -> configfs inode lock -> su_mutex -> dep_lock + */ ret = configfs_depend_item_unlocked( wwn->wwn_group.cg_subsys, &opts->func_inst.group.cg_item); if (ret) - goto unlock_dep; + goto unlock_inst; } tpg = kzalloc_obj(struct usbg_tpg); @@ -1714,7 +1720,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, tpg_instances[i].tpg = tpg; tpg->fi = tpg_instances[i].func_inst; - mutex_unlock(&opts->dep_lock); mutex_unlock(&tpg_instances_lock); return &tpg->se_tpg; @@ -1727,8 +1732,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, module_put(opts->dependent); else configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item); -unlock_dep: - mutex_unlock(&opts->dep_lock); unlock_inst: mutex_unlock(&tpg_instances_lock); @@ -2024,7 +2027,7 @@ static const struct target_core_fabric_ops usbg_ops = { .fabric_enable_tpg = usbg_enable_tpg, .fabric_drop_tpg = usbg_drop_tpg, .fabric_post_link = usbg_port_link, - .fabric_pre_unlink = usbg_port_unlink, + .fabric_post_unlink = usbg_port_unlink, .fabric_init_nodeacl = usbg_init_nodeacl, .tfc_wwn_attrs = usbg_wwn_attrs, @@ -2666,9 +2669,7 @@ static int tcm_set_name(struct usb_function_instance *f, const char *name) pr_debug("tcm: Activating %s\n", name); - mutex_lock(&opts->dep_lock); - opts->ready = true; - mutex_unlock(&opts->dep_lock); + WRITE_ONCE(opts->ready, true); return 0; } diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 85c502e98f57..7a81cd176abd 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -1594,7 +1594,8 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - char *split_page = NULL; \ + char *buf = NULL; \ + char *split_page; \ int ret = -EINVAL; \ char *token; \ u32 num; \ @@ -1608,18 +1609,22 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ \ i = 0; \ memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ - split_page = kstrdup(page, GFP_KERNEL); \ + buf = kstrdup(page, GFP_KERNEL); \ + split_page = buf; \ while ((token = strsep(&split_page, ",")) != NULL) { \ ret = kstrtou32(token, 0, &num); \ if (ret) \ goto end; \ - \ + if (i >= UAC_MAX_RATES) { \ + ret = -EINVAL; \ + goto end; \ + } \ opts->name##s[i++] = num; \ ret = len; \ }; \ \ end: \ - kfree(split_page); \ + kfree(buf); \ mutex_unlock(&opts->lock); \ return ret; \ } \ diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 5d201a2e30e7..3f52099a4fdd 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -888,60 +888,10 @@ UAC1_INT_ATTRIBUTE(req_buf_size); UAC1_INT_ATTRIBUTE(req_count); UAC1_INT_ATTRIBUTE(audio_buf_size); -#define UAC1_STR_ATTRIBUTE(name) \ -static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ - char *page) \ -{ \ - struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ - int result; \ - \ - mutex_lock(&opts->lock); \ - result = sprintf(page, "%s\n", opts->name); \ - mutex_unlock(&opts->lock); \ - \ - return result; \ -} \ - \ -static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ - const char *page, size_t len) \ -{ \ - struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ - int ret = -EBUSY; \ - char *tmp; \ - \ - mutex_lock(&opts->lock); \ - if (opts->refcnt) \ - goto end; \ - \ - tmp = kstrndup(page, len, GFP_KERNEL); \ - if (tmp) { \ - ret = -ENOMEM; \ - goto end; \ - } \ - if (opts->name##_alloc) \ - kfree(opts->name); \ - opts->name##_alloc = true; \ - opts->name = tmp; \ - ret = len; \ - \ -end: \ - mutex_unlock(&opts->lock); \ - return ret; \ -} \ - \ -CONFIGFS_ATTR(f_uac1_opts_, name) - -UAC1_STR_ATTRIBUTE(fn_play); -UAC1_STR_ATTRIBUTE(fn_cap); -UAC1_STR_ATTRIBUTE(fn_cntl); - static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_req_buf_size, &f_uac1_opts_attr_req_count, &f_uac1_opts_attr_audio_buf_size, - &f_uac1_opts_attr_fn_play, - &f_uac1_opts_attr_fn_cap, - &f_uac1_opts_attr_fn_cntl, NULL, }; @@ -956,12 +906,6 @@ static void f_audio_free_inst(struct usb_function_instance *f) struct f_uac1_legacy_opts *opts; opts = container_of(f, struct f_uac1_legacy_opts, func_inst); - if (opts->fn_play_alloc) - kfree(opts->fn_play); - if (opts->fn_cap_alloc) - kfree(opts->fn_cap); - if (opts->fn_cntl_alloc) - kfree(opts->fn_cntl); kfree(opts); } diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 897787d0803c..d8cf710085a0 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -2012,7 +2012,8 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uac2_opts *opts = to_f_uac2_opts(item); \ - char *split_page = NULL; \ + char *buf = NULL; \ + char *split_page; \ int ret = -EINVAL; \ char *token; \ u32 num; \ @@ -2026,18 +2027,22 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ \ i = 0; \ memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ - split_page = kstrdup(page, GFP_KERNEL); \ + buf = kstrdup(page, GFP_KERNEL); \ + split_page = buf; \ while ((token = strsep(&split_page, ",")) != NULL) { \ ret = kstrtou32(token, 0, &num); \ if (ret) \ goto end; \ - \ + if (i >= UAC_MAX_RATES) { \ + ret = -EINVAL; \ + goto end; \ + } \ opts->name##s[i++] = num; \ ret = len; \ }; \ \ end: \ - kfree(split_page); \ + kfree(buf); \ mutex_unlock(&opts->lock); \ return ret; \ } \ diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 73dc7e42875f..d1bf3ea75197 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -889,9 +889,12 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) v4l2_error: v4l2_device_unregister(&uvc->v4l2_dev); error: - if (uvc->control_req) + if (uvc->control_req) { usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + uvc->control_req = NULL; + } kfree(uvc->control_buf); + uvc->control_buf = NULL; usb_free_all_descriptors(f); return ret; @@ -1075,7 +1078,9 @@ static void uvc_function_unbind(struct usb_configuration *c, uvc->vdev_release_done = NULL; usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + uvc->control_req = NULL; kfree(uvc->control_buf); + uvc->control_buf = NULL; usb_free_all_descriptors(f); } diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index e53f2927b539..f0ee83abcebc 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev) ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); if (ret < 0) { dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); - return ret; // TODO: Clean up out_ep + goto err_out_ep; } - prm->fb_ep_enabled = true; ret = usb_ep_enable(ep_fback); if (ret < 0) { dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); - return ret; // TODO: Clean up out_ep + goto err_out_ep; } + prm->fb_ep_enabled = true; req_len = ep_fback->maxpacket; req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); @@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return 0; + +err_out_ep: + set_active(prm, false); + free_ep(prm, ep); + + return ret; } EXPORT_SYMBOL_GPL(u_audio_start_capture); @@ -1177,6 +1183,20 @@ static struct snd_kcontrol_new u_audio_controls[] = { }, }; +static void u_audio_card_free(struct snd_card *card) +{ + struct snd_uac_chip *uac = card->private_data; + + if (!uac) + return; + + kfree(uac->p_prm.reqs); + kfree(uac->c_prm.reqs); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); +} + int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, const char *card_name) { @@ -1256,6 +1276,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, goto fail; uac->card = card; + card->private_data = uac; + card->private_free = u_audio_card_free; /* * Create first PCM device @@ -1424,6 +1446,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_fail: snd_card_free(card); + return err; + fail: kfree(uac->p_prm.reqs); kfree(uac->c_prm.reqs); @@ -1449,12 +1473,6 @@ void g_audio_cleanup(struct g_audio *g_audio) card = uac->card; if (card) snd_card_free_when_closed(card); - - kfree(uac->p_prm.reqs); - kfree(uac->c_prm.reqs); - kfree(uac->p_prm.rbuf); - kfree(uac->c_prm.rbuf); - kfree(uac); } EXPORT_SYMBOL_GPL(g_audio_cleanup); diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 6a80182aadd7..c280c495fbd2 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -252,8 +252,14 @@ struct ffs_data { unsigned short strings_count; unsigned short interfaces_count; + + /* + * eps_count tracks the number of underlying hardware endpoints. + * epfiles_count tracks the total number of VFS endpoint files. + * When companion endpoints are active, epfiles_count > eps_count. + */ unsigned short eps_count; - unsigned short _pad1; + unsigned short epfiles_count; /* filled by __ffs_data_got_strings() */ /* ids in stringtabs are set in functionfs_bind() */ diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h index b5df9bcbbeba..b9ddae550ff3 100644 --- a/drivers/usb/gadget/function/u_uac1_legacy.h +++ b/drivers/usb/gadget/function/u_uac1_legacy.h @@ -62,9 +62,6 @@ struct f_uac1_legacy_opts { char *fn_cap; char *fn_cntl; unsigned bound:1; - unsigned fn_play_alloc:1; - unsigned fn_cap_alloc:1; - unsigned fn_cntl_alloc:1; struct mutex lock; int refcnt; }; diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 2f9700b3f1b6..9ba09118bb74 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -821,7 +821,7 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) /* Allocate a kthread for asynchronous hw submit handler. */ video->kworker = kthread_run_worker(0, "UVCG"); if (IS_ERR(video->kworker)) { - uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n"); + uvcg_err(&uvc->func, "failed to create UVCG kworker\n"); return PTR_ERR(video->kworker); } diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index d87a8ab51510..db961aaa3740 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) struct kiocb *iocb = priv->iocb; size_t ret; - kthread_use_mm(mm); - ret = copy_to_iter(priv->buf, priv->actual, &priv->to); - kthread_unuse_mm(mm); - if (!ret) + if (mmget_not_zero(mm)) { + kthread_use_mm(mm); + ret = copy_to_iter(priv->buf, priv->actual, &priv->to); + kthread_unuse_mm(mm); + mmput(mm); + if (!ret) + ret = -EFAULT; + } else { ret = -EFAULT; + } + mmdrop(mm); /* completing the iocb can drop the ctx and mm, don't touch mm after */ iocb->ki_complete(iocb, ret); @@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) * complete the aio request immediately. */ if (priv->to_free == NULL || unlikely(req->actual == 0)) { + mmdrop(priv->mm); kfree(req->buf); kfree(priv->to_free); kfree(priv); @@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb, priv->epdata = epdata; priv->actual = 0; priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ + mmgrab(priv->mm); /* each kiocb is coupled to one usb_request, but we can't * allocate or submit those if the host disconnected. @@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, fail: spin_unlock_irq(&epdata->dev->lock); + mmdrop(priv->mm); kfree(priv->to_free); kfree(priv); put_ep(epdata); diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c index 2e15f9a32ce9..396605a84cd1 100644 --- a/drivers/usb/gadget/legacy/nokia.c +++ b/drivers/usb/gadget/legacy/nokia.c @@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; */ #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS -#endif /* CONFIG_USB_DEBUG */ +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 26460340fbc9..7ddbc2606686 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -90,7 +90,7 @@ config USB_BCM63XX_UDC config USB_FSL_USB2 tristate "Freescale Highspeed USB DR Peripheral Controller" - depends on FSL_SOC + depends on FSL_SOC || COMPILE_TEST help Some of Freescale PowerPC and i.MX processors have a High Speed Dual-Role(DR) USB controller, which supports device mode. diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 75f9c831b21a..54f81e668009 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -1431,25 +1431,12 @@ static void ast_udc_init_hw(struct ast_udc_dev *udc) ast_udc_write(udc, 0, AST_UDC_EP0_CTRL); } -static void ast_udc_remove(struct platform_device *pdev) +static void ast_udc_cleanup(struct platform_device *pdev) { struct ast_udc_dev *udc = platform_get_drvdata(pdev); unsigned long flags; u32 ctrl; - usb_del_gadget_udc(&udc->gadget); - if (udc->driver) { - /* - * This is broken as only some cleanup is skipped, *udev is - * freed and the register mapping goes away. Any further usage - * probably crashes. Also the device is unbound, so the skipped - * cleanup is never catched up later. - */ - dev_alert(&pdev->dev, - "Driver is busy and still going away. Fasten your seat belts!\n"); - return; - } - spin_lock_irqsave(&udc->lock, flags); /* Disable upstream port connection */ @@ -1469,6 +1456,26 @@ static void ast_udc_remove(struct platform_device *pdev) udc->ep0_buf = NULL; } +static void ast_udc_remove(struct platform_device *pdev) +{ + struct ast_udc_dev *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) { + /* + * This is broken as only some cleanup is skipped, *udev is + * freed and the register mapping goes away. Any further usage + * probably crashes. Also the device is unbound, so the skipped + * cleanup is never catched up later. + */ + dev_alert(&pdev->dev, + "Driver is busy and still going away. Fasten your seat belts!\n"); + return; + } + + ast_udc_cleanup(pdev); +} + static int ast_udc_probe(struct platform_device *pdev) { enum usb_device_speed max_speed; @@ -1521,6 +1528,12 @@ static int ast_udc_probe(struct platform_device *pdev) AST_UDC_NUM_ENDPOINTS, &udc->ep0_buf_dma, GFP_KERNEL); + if (!udc->ep0_buf) { + clk_disable_unprepare(udc->clk); + rc = -ENOMEM; + goto err; + } + udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.max_speed = USB_SPEED_HIGH; udc->creq = udc->reg + AST_UDC_SETUP0; @@ -1550,20 +1563,20 @@ static int ast_udc_probe(struct platform_device *pdev) udc->irq = platform_get_irq(pdev, 0); if (udc->irq < 0) { rc = udc->irq; - goto err; + goto err_cleanup; } rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0, KBUILD_MODNAME, udc); if (rc) { dev_err(&pdev->dev, "Failed to request interrupt\n"); - goto err; + goto err_cleanup; } rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget); if (rc) { dev_err(&pdev->dev, "Failed to add gadget udc\n"); - goto err; + goto err_cleanup; } dev_info(&pdev->dev, "Initialized udc in USB%s mode\n", @@ -1571,9 +1584,10 @@ static int ast_udc_probe(struct platform_device *pdev) return 0; +err_cleanup: + ast_udc_cleanup(pdev); err: dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc); - ast_udc_remove(pdev); return rc; } diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index 5aa360ba4f03..8e5d7fb71549 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -1794,6 +1794,19 @@ static void at91udc_of_init(struct at91_udc *udc, struct device_node *np) udc->caps = match->data; } +/* + * The work handler re-arms this timer, so shut the timer down before + * draining the work; otherwise it restarts the polling cycle. + */ +static void at91_udc_shutdown_vbus_timer(struct at91_udc *udc) +{ + if (!(udc->board.vbus_pin && udc->board.vbus_polled)) + return; + + timer_shutdown_sync(&udc->vbus_timer); + cancel_work_sync(&udc->vbus_timer_work); +} + static int at91udc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1907,7 +1920,7 @@ static int at91udc_probe(struct platform_device *pdev) } retval = usb_add_gadget_udc(dev, &udc->gadget); if (retval) - goto err_unprepare_iclk; + goto err_shutdown_vbus; dev_set_drvdata(dev, udc); device_init_wakeup(dev, 1); create_debug_file(udc); @@ -1915,6 +1928,8 @@ static int at91udc_probe(struct platform_device *pdev) INFO("%s version %s\n", driver_name, DRIVER_VERSION); return 0; +err_shutdown_vbus: + at91_udc_shutdown_vbus_timer(udc); err_unprepare_iclk: clk_unprepare(udc->iclk); err_unprepare_fclk: @@ -1933,6 +1948,9 @@ static void at91udc_remove(struct platform_device *pdev) DBG("remove\n"); usb_del_gadget_udc(&udc->gadget); + + at91_udc_shutdown_vbus_timer(udc); + if (udc->driver) { dev_err(&pdev->dev, "Driver still in use but removing anyhow\n"); diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c index bf87285ad13c..603c77ff129f 100644 --- a/drivers/usb/gadget/udc/fsl_qe_udc.c +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, u16 index, u16 length) { u16 usb_status = 0; + struct usb_request *usb_req; struct qe_req *req; struct qe_ep *ep; int status = 0; @@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, } } - req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), - struct qe_req, req); + usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); + if (!usb_req) + goto stall; + + req = container_of(usb_req, struct qe_req, req); req->req.length = 2; req->req.buf = udc->statusbuf; *(u16 *)req->req.buf = cpu_to_le16(usb_status); diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 0a6886428739..99b3ce28210f 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -320,7 +320,6 @@ struct pch_vbus_gpio_data { * @lock: protects all state * @stall: stall requested * @prot_stall: protcol stall requested - * @registered: driver registered with system * @suspended: driver in suspended state * @connected: gadget driver associated * @vbus_session: required vbus_session state diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index e7a5d8553c0e..d190e16d43fc 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1951,7 +1951,6 @@ static int r8a66597_probe(struct platform_device *pdev) return 0; err_add_udc: - r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req); clean_up2: if (r8a66597->pdata->on_chip) clk_disable_unprepare(r8a66597->clk); diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c index db842a6de643..0ee3ed185d9c 100644 --- a/drivers/usb/gadget/udc/snps_udc_plat.c +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -159,10 +159,9 @@ static int udc_plat_probe(struct platform_device *pdev) if (of_property_present(dev->of_node, "extcon")) { udc->edev = extcon_get_edev_by_phandle(dev, 0); if (IS_ERR(udc->edev)) { - if (PTR_ERR(udc->edev) == -EPROBE_DEFER) - return -EPROBE_DEFER; - dev_err(dev, "Invalid or missing extcon\n"); ret = PTR_ERR(udc->edev); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Invalid or missing extcon\n"); goto exit_phy; } diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c index edfb63543029..7d46586e2e21 100644 --- a/drivers/usb/host/fsl-mph-dr-of.c +++ b/drivers/usb/host/fsl-mph-dr-of.c @@ -15,6 +15,7 @@ #include #include #include +#include struct fsl_usb2_dev_data { char *dr_mode; /* controller mode */ @@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) const struct of_device_id *match; const unsigned char *prop; static unsigned int idx; - int i; + int i, err; if (!of_device_is_available(np)) return -ENODEV; @@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) } } + err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); + if (err) + return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { if (!dev_data->drivers[i]) continue; diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 9e0e06bbc570..23dc9eddc06c 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf) int ret; if (!buf->page) - buf->page = (char *)get_zeroed_page(GFP_KERNEL); + buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf->page) { ret = -ENOMEM; @@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file) { struct debug_buffer *buf = file->private_data; - if (buf) { - if (buf->page) - free_page((unsigned long)buf->page); - kfree(buf); - } + kfree(buf->page); + kfree(buf); return 0; } diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h index 2f59b6ab1e45..68c27831e07e 100644 --- a/drivers/usb/host/xhci-caps.h +++ b/drivers/usb/host/xhci-caps.h @@ -5,22 +5,23 @@ */ #include +#include /* hc_capbase - bitmasks */ /* bits 7:0 - Capability Registers Length */ -#define HC_LENGTH(p) ((p) & 0xff) +#define HC_LENGTH GENMASK(7, 0) /* bits 15:8 - Rsvd */ /* bits 31:16 - Host Controller Interface Version Number */ -#define HC_VERSION(p) (((p) >> 16) & 0xffff) +#define HC_VERSION GENMASK(31, 16) /* HCSPARAMS1 - hcs_params1 - bitmasks */ /* bits 7:0 - Number of Device Slots */ -#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) -#define HCS_SLOTS_MASK 0xff -/* bits 18:8 - Number of Interrupters, max values is 1024 */ -#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) -/* bits 31:24, Max Ports - max value is 255 */ -#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff) +#define HCS_SLOTS_MASK GENMASK(7, 0) +/* bits 18:8 - Number of Interrupters, max values is 1024 */ +#define HCS_MAX_INTRS GENMASK(18, 8) +/* bits 23:19 - Rsvd */ +/* bits 31:24 - Max Ports, max values is 255 */ +#define HCS_MAX_PORTS GENMASK(31, 24) /* HCSPARAMS2 - hcs_params2 - bitmasks */ /* @@ -33,24 +34,25 @@ * Note: 1 Frame = 8 Microframes * xHCI specification section 5.3.4. */ -#define HCS_IST_VALUE(p) ((p) & 0x7) +#define HCS_IST_VALUE GENMASK(2, 0) #define HCS_IST_UNIT BIT(3) /* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ -#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +#define HCS_ERST_MAX GENMASK(7, 4) /* bits 20:8 - Rsvd */ /* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ -#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f) +#define HCS_MAX_SP_HI GENMASK(25, 21) /* bit 26 - Scratchpad restore, for save/restore HW state */ /* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ -#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f) -#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p)) +#define HCS_MAX_SP_LO GENMASK(31, 27) +#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ + FIELD_GET(HCS_MAX_SP_LO, (p))) /* HCSPARAMS3 - hcs_params3 - bitmasks */ /* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ -#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +#define HCS_U1_LATENCY GENMASK(7, 0) /* bits 15:8 - Rsvd */ /* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ -#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) +#define HCS_U2_LATENCY GENMASK(31, 16) /* HCCPARAMS1 - hcc_params - bitmasks */ /* bit 0 - 64-bit Addressing Capability */ @@ -77,19 +79,20 @@ /* bit 11 - Contiguous Frame ID Capability */ #define HCC_CFC BIT(11) /* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ -#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) +#define HCC_MAX_PSA GENMASK(15, 12) +#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) /* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ -#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) +#define HCC_EXT_CAPS GENMASK(31, 16) /* DBOFF - db_off - bitmasks */ /* bits 1:0 - Rsvd */ /* bits 31:2 - Doorbell Array Offset */ -#define DBOFF_MASK (0xfffffffc) +#define DBOFF_MASK GENMASK(31, 2) /* RTSOFF - run_regs_off - bitmasks */ /* bits 4:0 - Rsvd */ /* bits 31:5 - Runtime Register Space Offse */ -#define RTSOFF_MASK (~0x1f) +#define RTSOFF_MASK GENMASK(31, 5) /* HCCPARAMS2 - hcc_params2 - bitmasks */ /* bit 0 - U3 Entry Capability */ diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 2e7384c6b6ec..3d51e8d82659 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -628,8 +628,8 @@ int dbc_tty_init(void) dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); if (IS_ERR(dbc_tty_driver)) { - idr_destroy(&dbc_tty_minors); - return PTR_ERR(dbc_tty_driver); + ret = PTR_ERR(dbc_tty_driver); + goto fail; } dbc_tty_driver->driver_name = "dbc_serial"; @@ -649,20 +649,27 @@ int dbc_tty_init(void) ret = tty_register_driver(dbc_tty_driver); if (ret) { pr_err("Can't register dbc tty driver\n"); - tty_driver_kref_put(dbc_tty_driver); - idr_destroy(&dbc_tty_minors); + goto fail_put; } + return ret; + +fail_put: + tty_driver_kref_put(dbc_tty_driver); +fail: + idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; + return ret; } void dbc_tty_exit(void) { - if (dbc_tty_driver) { - tty_unregister_driver(dbc_tty_driver); - tty_driver_kref_put(dbc_tty_driver); - dbc_tty_driver = NULL; - } + if (IS_ERR_OR_NULL(dbc_tty_driver)) + return; + tty_unregister_driver(dbc_tty_driver); + tty_driver_kref_put(dbc_tty_driver); idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; } diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index d07276192256..2aa01d99f23d 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -9,6 +9,7 @@ #include #include +#include #include "xhci.h" #include "xhci-debugfs.h" @@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) xhci->debugfs_root, "reg-cap"); xhci_debugfs_regset(xhci, - HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), + FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), xhci_op_regs, ARRAY_SIZE(xhci_op_regs), xhci->debugfs_root, "reg-op"); diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c index 02396c8721dc..fddb43bf6323 100644 --- a/drivers/usb/host/xhci-histb.c +++ b/drivers/usb/host/xhci-histb.c @@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev) if (ret) goto put_usb3_hcd; - if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) xhci->shared_hcd->can_do_streams = 1; ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index b0264bd8577a..470bafe1802b 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, if ((xhci->quirks & XHCI_LPM_SUPPORT)) { reg = readl(&xhci->cap_regs->hcs_params3); - ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); - ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); + ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); + ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); } if (wLength < le16_to_cpu(bos->wTotalLength)) @@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } /* In spec software should not attempt to suspend * a port unless the port reports that it is in the - * enabled (PED = ‘1’,PLS < ‘3’) state. + * enabled (PED = '1',PLS < '3') state. */ portsc = xhci_portsc_readl(port); if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index a5e7f363922f..7a21ac81f9c8 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, for (i = 0; i < 31; i++) { dev->eps[i].ep_index = i; dev->eps[i].vdev = dev; - dev->eps[i].xhci = xhci; INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); } @@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, return -ENOMEM; virt_dev->eps[ep_index].skip = false; + virt_dev->eps[ep_index].next_uframe = -1; ep_ring = virt_dev->eps[ep_index].new_ring; xhci_ring_init(xhci, ep_ring); @@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, addr, port_offset, port_count, major_revision); /* Port count includes the current port offset */ if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) - /* WTF? "Valid values are ‘1’ to MaxPorts" */ + /* WTF? "Valid values are '1' to MaxPorts" */ return; port_cap = &xhci->port_caps[xhci->num_port_caps++]; @@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) if (!segs) segs = ERST_DEFAULT_SEGS; - max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2)); + max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; segs = min(segs, max_segs); ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index d9b865546a67..60af5fe29bd4 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, * and it's 3 when support it. */ - if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) + if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) xhci->quirks |= XHCI_BROKEN_STREAMS; } @@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) } usb3_hcd = xhci_get_usb3_hcd(xhci); - if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && + if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && !(xhci->quirks & XHCI_BROKEN_STREAMS)) usb3_hcd->can_do_streams = 1; diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 6b3fcba44b08..a8889081ae82 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) } usb3_hcd = xhci_get_usb3_hcd(xhci); - if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) + if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && + GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) usb3_hcd->can_do_streams = 1; /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 074d9c731639..6fd595f81a30 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s } usb3_hcd = xhci_get_usb3_hcd(xhci); - if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && + if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && !(xhci->quirks & XHCI_BROKEN_STREAMS)) usb3_hcd->can_do_streams = 1; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 4f98d8269625..97a1b53c18ef 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -57,6 +57,8 @@ #include #include #include +#include + #include "xhci.h" #include "xhci-trace.h" @@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, * pointer command pending because the device can choose to start any * stream once the endpoint is on the HW schedule. */ - if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) || - (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT)) + if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | + EP_CLEARING_TT | EP_DROP_PENDING)) return; trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); @@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, } /* Ring the doorbell for any rings with pending URBs */ -static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, - unsigned int slot_id, - unsigned int ep_index) +void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index) { unsigned int stream_id; struct xhci_virt_ep *ep; @@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, } } -void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, - unsigned int slot_id, - unsigned int ep_index) -{ - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); -} - static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index) @@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, return le64_to_cpu(ep_ctx->deq); } -static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, - unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_td *td) +/* + * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or + * to enqueue if no TDs are queued (empty ring) + * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list + * before calling this function + */ +static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, + unsigned int stream_id) { - struct xhci_virt_device *dev = xhci->devs[slot_id]; - struct xhci_virt_ep *ep = &dev->eps[ep_index]; - struct xhci_ring *ep_ring; struct xhci_command *cmd; - struct xhci_segment *new_seg; - union xhci_trb *new_deq; - int new_cycle; + struct xhci_ring *ring; + struct xhci_td *td; dma_addr_t addr; - u64 hw_dequeue; - bool hw_dequeue_found = false; - bool td_last_trb_found = false; + int new_cycle; u32 trb_sct = 0; - int ret; + int ret = 0; - ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, - ep_index, stream_id); - if (!ep_ring) { + ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); + if (!ring) { xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", stream_id); return -ENODEV; } - hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; - new_seg = ep_ring->deq_seg; - new_deq = ep_ring->dequeue; - new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; - - /* - * Walk the ring until both the next TRB and hw_dequeue are found (don't - * move hw_dequeue back if it went forward due to a HW bug). Cycle state - * is loaded from a known good TRB, track later toggles to maintain it. - */ - do { - if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) - == (dma_addr_t)hw_dequeue) { - hw_dequeue_found = true; - if (td_last_trb_found) - break; - } - if (new_deq == td->end_trb) - td_last_trb_found = true; - - if (td_last_trb_found && trb_is_link(new_deq) && - link_trb_toggles_cycle(new_deq)) - new_cycle ^= 0x1; - - next_trb(&new_seg, &new_deq); - - /* Search wrapped around, bail out */ - if (new_deq == ep->ring->dequeue) { - xhci_err(xhci, "Error: Failed finding new dequeue state\n"); - return -EINVAL; - } - - } while (!hw_dequeue_found || !td_last_trb_found); - - /* Don't update the ring cycle state for the producer (us). */ - addr = xhci_trb_virt_to_dma(new_seg, new_deq); - if (addr == 0) { - xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); - xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); - return -EINVAL; - } - if ((ep->ep_state & SET_DEQ_PENDING)) { - xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", - &addr); + xhci_warn(xhci, "Set TR Deq already pending\n"); return -EBUSY; } /* This function gets called from contexts where it cannot sleep */ cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); if (!cmd) { - xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); + xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); return -ENOMEM; } + /* + * Move dequeue to the beginning of next td, or to enqueue if ring is + * empty. Avoid moving dequeue to a link trb (empty ring) as it causes + * issues on some hosts. In that case advance the enqueue to next segment + * before moving dequeue to it + */ + + if (list_empty(&ring->td_list)) { + if (trb_is_link(ring->enqueue)) + inc_enq_past_link(xhci, ring, 0); + ep->queued_deq_seg = ring->enq_seg; + ep->queued_deq_ptr = ring->enqueue; + new_cycle = ring->cycle_state; + } else { + td = list_first_entry(&ring->td_list, struct xhci_td, td_list); + ep->queued_deq_seg = td->start_seg; + ep->queued_deq_ptr = td->start_trb; + new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; + } + + addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); + if (addr == 0) { + xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", + ep->queued_deq_seg, ep->queued_deq_ptr); + ret = -EINVAL; + goto err_out; + } + if (stream_id) trb_sct = SCT_FOR_TRB(SCT_PRI_TR); ret = queue_command(xhci, cmd, lower_32_bits(addr) | trb_sct | new_cycle, upper_32_bits(addr), - STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | - EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); - if (ret < 0) { - xhci_free_command(xhci, cmd); - return ret; - } - ep->queued_deq_seg = new_seg; - ep->queued_deq_ptr = new_deq; + STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | + EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); + if (ret < 0) + goto err_out; xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); - /* Stop the TD queueing code from ringing the doorbell until - * this command completes. The HC won't set the dequeue pointer - * if the ring is running, and ringing the doorbell starts the - * ring running. + /* + * Stop the TD queueing code from ringing the doorbell until this + * command completes. The HC won't set the dequeue pointer if the ring + * is running, and ringing the doorbell starts the ring. */ ep->ep_state |= SET_DEQ_PENDING; xhci_ring_cmd_db(xhci); + return 0; + +err_out: + xhci_free_command(xhci, cmd); + ep->queued_deq_seg = NULL; + ep->queued_deq_ptr = NULL; + + return ret; } /* flip_cycle means flip the cycle bit of all but the first and last TRB. @@ -934,7 +916,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh } /* Complete the cancelled URBs we unlinked from td_list. */ -static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) +static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { struct xhci_ring *ring; struct xhci_td *td, *tmp_td; @@ -942,17 +924,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { - ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); + ring = xhci_urb_to_transfer_ring(xhci, td->urb); if (td->cancel_status == TD_CLEARED) { - xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", + xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", __func__, td->urb); - xhci_td_cleanup(ep->xhci, td, ring, td->status); + xhci_td_cleanup(xhci, td, ring, td->status); } else { - xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", __func__, td->urb, td->cancel_status); } - if (ep->xhci->xhc_state & XHCI_STATE_DYING) + if (xhci->xhc_state & XHCI_STATE_DYING) return; } } @@ -986,15 +968,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, struct xhci_td *td, enum xhci_ep_reset_type reset_type) { + struct xhci_port *rhub_port = ep->vdev->rhub_port; unsigned int slot_id = ep->vdev->slot_id; int err; /* - * Avoid resetting endpoint if link is inactive. Can cause host hang. - * Device will be reset soon to recover the link so don't do anything + * Avoid resetting endpoint if link is inactive or device disonnected. + * Can cause host hang. + * Device will be reset to recover an inactive link, so don't do anything */ - if (ep->vdev->flags & VDEV_PORT_ERROR) + if (rhub_port->link_inactive || !rhub_port->connected) { + ep->ep_state |= EP_DROP_PENDING; return -ENODEV; + } /* add td to cancelled list and let reset ep handler take care of it */ if (reset_type == EP_HARD_RESET) { @@ -1031,15 +1017,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, * only call this when ring is not in a running state */ -static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) +static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { - struct xhci_hcd *xhci; struct xhci_td *td = NULL; struct xhci_td *tmp_td = NULL; struct xhci_td *cached_td = NULL; struct xhci_ring *ring; u64 hw_deq; - unsigned int slot_id = ep->vdev->slot_id; int err; /* @@ -1049,8 +1033,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) if (ep->ep_state & SET_DEQ_PENDING) return 0; - xhci = ep->xhci; - list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", @@ -1064,6 +1046,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) td->urb, td->urb->stream_id); continue; } + + /* device disconnected or link error, ep will be dropped */ + if (ep->ep_state & EP_DROP_PENDING) { + td->cancel_status = TD_CLEARED; + continue; + } + /* * If a ring stopped on the TD we need to cancel then we have to * move the xHC endpoint ring dequeue pointer past this TD. @@ -1115,9 +1104,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) if (!cached_td) return 0; - err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, - cached_td->urb->stream_id, - cached_td); + err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); + if (err) { /* Failed to move past cached td, just set cached TDs to no-op */ list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { @@ -1145,23 +1133,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) * * Call under xhci->lock on a stopped endpoint. */ -void xhci_process_cancelled_tds(struct xhci_virt_ep *ep) +void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { - xhci_invalidate_cancelled_tds(ep); - xhci_giveback_invalidated_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); + xhci_giveback_invalidated_tds(xhci, ep); } /* * Returns the TD the endpoint ring halted on. * Only call for non-running rings without streams. */ -static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) +static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { struct xhci_td *td; u64 hw_deq; if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ - hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); + hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); hw_deq &= TR_DEQ_PTR_MASK; td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); if (trb_in_td(td, hw_deq)) @@ -1236,7 +1224,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, reset_type = EP_SOFT_RESET; } else { reset_type = EP_HARD_RESET; - td = find_halted_td(ep); + td = find_halted_td(xhci, ep); if (td) td->status = -EPROTO; } @@ -1294,13 +1282,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, } } + /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ + if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) + ep->ep_state |= EP_DROP_PENDING; + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ - xhci_invalidate_cancelled_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); ep->ep_state &= ~EP_STOP_CMD_PENDING; /* Otherwise ring the doorbell(s) to restart queued transfers */ - xhci_giveback_invalidated_tds(ep); - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_giveback_invalidated_tds(xhci, ep); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) @@ -1521,14 +1513,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, /* HW cached TDs cleared from cache, give them back */ list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { - ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); + ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); if (td->cancel_status == TD_CLEARING_CACHE) { td->cancel_status = TD_CLEARED; - xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", + xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", __func__, td->urb); - xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); + xhci_td_cleanup(xhci, td, ep_ring, td->status); } else { - xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", __func__, td->urb, td->cancel_status); } } @@ -1539,17 +1531,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, /* Check for deferred or newly cancelled TDs */ if (!list_empty(&ep->cancelled_td_list)) { - xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", __func__); - xhci_invalidate_cancelled_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); /* Try to restart the endpoint if all is done */ - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); /* Start giving back any TDs invalidated above */ - xhci_giveback_invalidated_tds(ep); + xhci_giveback_invalidated_tds(xhci, ep); } else { /* Restart any rings with pending URBs */ - xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } } @@ -1575,16 +1567,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, "Ignoring reset ep completion code of %u", cmd_comp_code); /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ - xhci_invalidate_cancelled_tds(ep); + xhci_invalidate_cancelled_tds(xhci, ep); /* Clear our internal halted state */ ep->ep_state &= ~EP_HALTED; - xhci_giveback_invalidated_tds(ep); + xhci_giveback_invalidated_tds(xhci, ep); /* if this was a soft reset, then restart */ if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, @@ -1992,13 +1984,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) { struct xhci_virt_device *vdev = NULL; - struct usb_hcd *hcd; - u32 port_id; - u32 portsc, cmd_reg; - unsigned int hcd_portnum; struct xhci_bus_state *bus_state; - bool bogus_port_status = false; struct xhci_port *port; + struct usb_hcd *hcd; + bool bogus_port_status = false; + unsigned int hcd_portnum; + u32 cmd_reg; + u32 port_id; + u32 portsc; + u32 pls; /* Port status change events always have a successful completion code */ if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) @@ -2025,7 +2019,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) vdev = xhci->devs[port->slot_id]; /* We might get interrupts after shared_hcd is removed */ - if (port->rhub == &xhci->usb3_rhub && xhci->shared_hcd == NULL) { + if (port->rhub == &xhci->usb3_rhub && xhci_get_usb3_hcd(xhci) == NULL) { xhci_dbg(xhci, "ignore port event for removed USB3 hcd\n"); bogus_port_status = true; goto cleanup; @@ -2035,6 +2029,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) bus_state = &port->rhub->bus_state; hcd_portnum = port->hcd_portnum; portsc = xhci_portsc_readl(port); + pls = portsc & PORT_PLS_MASK; xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", hcd->self.busnum, hcd_portnum + 1, port_id, portsc); @@ -2046,11 +2041,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) usb_hcd_resume_root_hub(hcd); } - if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { - if (!(portsc & PORT_RESET)) - vdev->flags |= VDEV_PORT_ERROR; - } else if (vdev && portsc & PORT_RC) { - vdev->flags &= ~VDEV_PORT_ERROR; + /* + * Tag broken links to avoid retries while hub driver sorts it out. + * Link status is not relible while port is in reset. + */ + if (!(portsc & PORT_RESET)) { + port->link_inactive = (pls == XDEV_INACTIVE); + port->connected = !!(portsc & PORT_CONNECT); } if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { @@ -2600,6 +2597,17 @@ static bool xhci_spurious_success_tx_event(struct xhci_hcd *xhci, } } +static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, dma_addr_t dma) +{ + struct xhci_td *td; + + if (dma) + list_for_each_entry(td, &ep_ring->td_list, td_list) + if (trb_in_td(td, dma)) + return td; + return NULL; +} + /* * If this function returns an error condition, it means it got a Transfer * event with a corrupted Slot ID, Endpoint ID, or TRB DMA address. @@ -2794,8 +2802,11 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_dequeue_td(xhci, td, ep_ring, td->status); } - /* If the TRB pointer is NULL, missed TDs will be skipped on the next event */ - if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !ep_trb_dma) + /* + * We don't know how many TDs were missed when ep_trb_dma is zero (as permitted by + * xHCI 1.0) or bogus. Bail out leaving ep->skip set, next event will sort it out. + */ + if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !find_td_by_dma(ep_ring, ep_trb_dma)) return 0; if (list_empty(&ep_ring->td_list)) { @@ -3948,88 +3959,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, /* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ static int xhci_ist_microframes(struct xhci_hcd *xhci) { - int ist = HCS_IST_VALUE(xhci->hcs_params2); + int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); if (xhci->hcs_params2 & HCS_IST_UNIT) ist *= 8; return ist; } -/* - * Calculates Frame ID field of the isochronous TRB identifies the - * target frame that the Interval associated with this Isochronous - * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec. - * - * Returns actual frame id on success, negative value on error. - */ -static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, - struct urb *urb, int index) + +static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_virt_ep *ep, int i) { - int start_frame, ist, ret = 0; - int start_frame_id, end_frame_id, current_frame_id; + if (urb->transfer_flags & URB_ISO_ASAP) + return false; - if (urb->dev->speed == USB_SPEED_LOW || - urb->dev->speed == USB_SPEED_FULL) - start_frame = urb->start_frame + index * urb->interval; - else - start_frame = (urb->start_frame + index * urb->interval) >> 3; + if (xhci->hcc_params & HCC_CFC) + return true; + /* set frame id for first TD of first URB in stream */ + if (ep->next_uframe == -1 && i == 0) + return true; + + return false; +} + +/* + * Check if frame is in the valid frame window, including start and end. + * If start > end then assume window wrapped around at a limit the frame + * value won't exceed. + */ +static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) +{ + /* frame window end wrapped around */ + if (start > end) + return frame >= start || frame <= end; + + return frame >= start && frame <= end; +} + +/* + * Set the urb->start_frame of the URB. + * + * Returns microframe index of first TD + */ +static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_virt_ep *ep) +{ + u32 curr_frame, start_uframe; + u32 urb_start, urb_end; + u32 win_start, win_end; + bool frame_unit; + int uinterval; + u32 mfindex; + int ist; + + /* check if urb uses frame units instead of microframes */ + frame_unit = (urb->dev->speed == USB_SPEED_FULL || + urb->dev->speed == USB_SPEED_LOW); + + uinterval = urb->interval; + if (frame_unit) + uinterval *= 8; + + /* get current microframe index and isoc scheduling threshold */ + mfindex = readl(&xhci->run_regs->microframe_index); ist = xhci_ist_microframes(xhci); - /* Software shall not schedule an Isoch TD with a Frame ID value that - * is less than the Start Frame ID or greater than the End Frame ID, - * where: - * - * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048 - * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048 - * - * Both the End Frame ID and Start Frame ID values are calculated - * in microframes. When software determines the valid Frame ID value; - * The End Frame ID value should be rounded down to the nearest Frame - * boundary, and the Start Frame ID value should be rounded up to the - * nearest Frame boundary. - */ - current_frame_id = readl(&xhci->run_regs->microframe_index); - start_frame_id = roundup(current_frame_id + ist + 1, 8); - end_frame_id = rounddown(current_frame_id + 895 * 8, 8); + /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ + curr_frame = MFINDEX_TO_FRAME(mfindex); + win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; + win_end = (curr_frame + 895) % MAX_FRAMES; - start_frame &= 0x7ff; - start_frame_id = (start_frame_id >> 3) & 0x7ff; - end_frame_id = (end_frame_id >> 3) & 0x7ff; - - if (start_frame_id < end_frame_id) { - if (start_frame > end_frame_id || - start_frame < start_frame_id) - ret = -EINVAL; - } else if (start_frame_id > end_frame_id) { - if ((start_frame > end_frame_id && - start_frame < start_frame_id)) - ret = -EINVAL; + /* Is this the first URB starting the whole isoc data flow? */ + if (ep->next_uframe < 0) { + /* align first URB to next interval boundary, or at last to full frame */ + start_uframe = mfindex + ist + XHCI_CFC_DELAY; + start_uframe = roundup(start_uframe, 8); + start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; } else { - ret = -EINVAL; - } + /* URB is mid stream and expected to handle the next frame */ + start_uframe = ep->next_uframe; + urb_start = start_uframe / 8; + urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; + urb_end %= MAX_FRAMES; - if (index == 0) { - if (ret == -EINVAL || start_frame == start_frame_id) { - start_frame = start_frame_id + 1; - if (urb->dev->speed == USB_SPEED_LOW || - urb->dev->speed == USB_SPEED_FULL) - urb->start_frame = start_frame; - else - urb->start_frame = start_frame << 3; - ret = 0; - } - } + if (!xhci_frame_in_range(urb_start, win_start, win_end)) + xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", + urb, urb_start, win_start, win_end); - if (ret) { - xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n", - start_frame, current_frame_id, index, - start_frame_id, end_frame_id); - xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n"); - return ret; + if (!xhci_frame_in_range(urb_end, win_start, win_end)) + xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", + urb, urb_start, win_start, win_end); } + /* set urb->start_frame */ + urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; - return start_frame; + return start_uframe; } /* Check if we should generate event interrupt for a TD in an isoc URB */ @@ -4069,7 +4094,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, int i, j; bool more_trbs_coming; struct xhci_virt_ep *xep; - int frame_id; + int uinterval = urb->interval; + int start_uframe; xep = &xhci->devs[slot_id]->eps[ep_index]; ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; @@ -4085,6 +4111,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, start_cycle = ep_ring->cycle_state; urb_priv = urb->hcpriv; + + if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) + uinterval = urb->interval * 8; + + start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); + /* Queue the TRBs for each TD, even if they are zero-length */ for (i = 0; i < num_tds; i++) { unsigned int total_pkt_count, max_pkt; @@ -4116,14 +4148,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, goto cleanup; } td = &urb_priv->td[i]; - /* use SIA as default, if frame id is used overwrite it */ - sia_frame_id = TRB_SIA; - if (!(urb->transfer_flags & URB_ISO_ASAP) && - (xhci->hcc_params & HCC_CFC)) { - frame_id = xhci_get_isoc_frame_id(xhci, urb, i); - if (frame_id >= 0) - sia_frame_id = TRB_FRAME_ID(frame_id); + + + /* Choose SIA or frame ID based scheduling for this TD */ + if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { + sia_frame_id = (start_uframe + i * uinterval) / 8; + sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); + } else { + sia_frame_id = TRB_SIA; } + /* * Set isoc specific data for the first TRB in a TD. * Prevent HW from getting the TRBs by keeping the cycle state @@ -4202,9 +4236,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } } - /* store the next frame id */ - if (xhci->hcc_params & HCC_CFC) - xep->next_frame_id = urb->start_frame + num_tds * urb->interval; + xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { if (xhci->quirks & XHCI_AMD_PLL_FIX) @@ -4251,11 +4283,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, struct xhci_virt_device *xdev; struct xhci_ring *ep_ring; struct xhci_ep_ctx *ep_ctx; - int start_frame; + struct xhci_virt_ep *xep; int num_tds, num_trbs, i; int ret; - struct xhci_virt_ep *xep; - int ist; xdev = xhci->devs[slot_id]; xep = &xhci->devs[slot_id]->eps[ep_index]; @@ -4281,38 +4311,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, */ check_interval(urb, ep_ctx); - /* Calculate the start frame and put it in urb->start_frame. */ - if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) { - if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) { - urb->start_frame = xep->next_frame_id; - goto skip_start_over; - } - } - - start_frame = readl(&xhci->run_regs->microframe_index); - start_frame &= 0x3fff; /* - * Round up to the next frame and consider the time before trb really - * gets scheduled by hardare. + * Check if this starts the isoc data flow. Relies on hw setting ep ctx + * state after doorbell ring. Consider adding list_empty(td_list) check */ - ist = xhci_ist_microframes(xhci); - start_frame += ist + XHCI_CFC_DELAY; - start_frame = roundup(start_frame, 8); - - /* - * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT - * is greate than 8 microframes. - */ - if (urb->dev->speed == USB_SPEED_LOW || - urb->dev->speed == USB_SPEED_FULL) { - start_frame = roundup(start_frame, urb->interval << 3); - urb->start_frame = start_frame >> 3; - } else { - start_frame = roundup(start_frame, urb->interval); - urb->start_frame = start_frame; - } - -skip_start_over: + if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) + xep->next_uframe = -1; return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); } diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index e7e6d569f1db..6f235d1e117e 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -6,6 +6,7 @@ * Copyright (C) 2014 Google, Inc. */ +#include #include #include #include @@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) u32 value; cap_regs = tegra->regs; - op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); + op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); @@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto remove_usb2; } - if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) xhci->shared_hcd->can_do_streams = 1; err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 091c82ca8ee2..a9e47e178c28 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-trace.h" @@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag goto free_priv; } - if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { + if (xhci->devs[slot_id]->rhub_port->link_inactive) { xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); ret = -ENODEV; goto free_priv; @@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) } /* In this case no commands are pending but the endpoint is stopped */ - if (ep->ep_state & EP_CLEARING_TT) { + if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { /* and cancelled TDs can be given back right away */ xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", urb->dev->slot_id, ep_index, ep->ep_state); - xhci_process_cancelled_tds(ep); + xhci_process_cancelled_tds(xhci, ep); } else { /* Otherwise, queue a new Stop Endpoint command */ command = xhci_alloc_command(xhci, false, GFP_ATOMIC); @@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, * level page entries), but that's an optional feature for xHCI host * controllers. xHCs must support at least 4 stream IDs. */ - max_streams = HCC_MAX_PSA(xhci->hcc_params); + max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params); if (*num_stream_ctxs > max_streams) { xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", max_streams); @@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, /* MaxPSASize value 0 (2 streams) means streams are not supported */ if ((xhci->quirks & XHCI_BROKEN_STREAMS) || - HCC_MAX_PSA(xhci->hcc_params) < 4) { + GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { xhci_dbg(xhci, "xHCI controller does not support streams.\n"); return -ENOSYS; } @@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, xhci_get_slot_state(xhci, virt_dev->out_ctx)); xhci_dbg(xhci, "Not freeing device rings.\n"); /* Don't treat this as an error. May change my mind later. */ - virt_dev->flags = 0; ret = 0; goto command_cleanup; case COMP_SUCCESS: @@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, } /* If necessary, update the number of active TTs on this root port */ xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); - virt_dev->flags = 0; ret = 0; command_cleanup: + for (i = 0; i < EP_CTX_PER_DEV; i++) + virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; + xhci_free_command(xhci, reset_device_cmd); return ret; } @@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, int besl_device = 0; u32 field; - u2del = HCS_U2_LATENCY(xhci->hcs_params3); + u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3); field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); if (field & USB_BESL_SUPPORT) { @@ -5433,6 +5435,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) struct device *dev = hcd->self.sysdev; int retval; u32 hcs_params1; + u32 hc_capbase; /* Accept arbitrarily long scatter-gather lists */ hcd->self.sg_tablesize = ~0; @@ -5453,27 +5456,33 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) mutex_init(&xhci->mutex); xhci->main_hcd = hcd; xhci->cap_regs = hcd->regs; - xhci->op_regs = hcd->regs + - HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)); + hc_capbase = readl(&xhci->cap_regs->hc_capbase); + if (hc_capbase == U32_MAX) { + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } + xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); + xhci->run_regs = hcd->regs + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); /* Cache read-only capability registers */ hcs_params1 = readl(&xhci->cap_regs->hcs_params1); xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); - xhci->hci_version = HC_VERSION(readl(&xhci->cap_regs->hc_capbase)); + xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); xhci->dma_mask_bits = 64; - xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); - xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); + xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); + xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); + /* xhci-plat or xhci-pci might have set max_interrupters already */ if (!xhci->max_interrupters) - xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS); - else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1)) - xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1); + xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); + else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) + xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); xhci->quirks |= quirks; @@ -5514,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) * DMA_BIT_MASK(32)) in this xhci_gen_setup(). */ if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) - xhci->hcc_params &= ~BIT(0); + xhci->hcc_params &= ~HCC_64BIT_ADDR; /* * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 708e3ccc5d87..c7bfa7f028d3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -290,6 +290,11 @@ struct xhci_run_regs { struct xhci_intr_reg ir_set[1024]; }; +/* Bits [13:3] of the microframe index equals the 1ms frame index */ +#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) +#define MAX_FRAMES 2048 +#define MAX_UFRAMES (MAX_FRAMES * 8) + /** * struct doorbell_array * @@ -677,9 +682,9 @@ struct xhci_virt_ep { #define EP_SOFT_CLEAR_TOGGLE BIT(7) /* usb_hub_clear_tt_buffer is in progress */ #define EP_CLEARING_TT BIT(8) +#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; - struct xhci_hcd *xhci; /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue * command. We'll need to update the ring's dequeue segment and dequeue * pointer after the command completes. @@ -699,7 +704,7 @@ struct xhci_virt_ep { struct list_head bw_endpoint_list; unsigned long stop_time; /* Isoch Frame ID checking storage */ - int next_frame_id; + int next_uframe; /* Use new Isoch TRB layout needed for extended TBC support */ bool use_extended_tbc; /* set if this endpoint is controlled via sideband access*/ @@ -753,14 +758,6 @@ struct xhci_virt_device { struct xhci_port *rhub_port; struct xhci_interval_bw_table *bw_table; struct xhci_tt_bw_info *tt_info; - /* - * flags for state tracking based on events and issued commands. - * Software can not rely on states from output contexts because of - * latency between events and xHC updating output context values. - * See xhci 1.1 section 4.8.3 for more details - */ - unsigned long flags; -#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */ /* The current max exit latency for the enabled USB3 link states. */ u16 current_mel; @@ -1480,6 +1477,8 @@ struct xhci_port { int hcd_portnum; struct xhci_hub *rhub; struct xhci_port_cap *port_cap; + unsigned int link_inactive:1; + unsigned int connected:1; unsigned int lpm_incapable:1; unsigned long resume_timestamp; bool rexit_active; @@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len); unsigned int xhci_num_trbs_free(struct xhci_ring *ring); int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, gfp_t gfp_flags); -void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); +void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool clear_ehb); diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c index 4a7f955ba85b..ecc81c93c6ce 100644 --- a/drivers/usb/misc/cypress_cy7c63.c +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request, address, data, iobuf, CYPRESS_MAX_REQSIZE, USB_CTRL_GET_TIMEOUT); /* we must not process garbage */ - if (retval < 2) + if (retval < 2) { + if (retval >= 0) + retval = -EIO; goto err_buf; + } /* store returned data (more READs to be added) */ switch (request) { @@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr, dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); - dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); + if (result < 0) + return result; return sprintf(buf, "%d", cyp->port[port_num]); } diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c index 78e94dd89da5..fcb627b49eac 100644 --- a/drivers/usb/misc/usb-ljca.c +++ b/drivers/usb/misc/usb-ljca.c @@ -595,6 +595,9 @@ static int ljca_enumerate_gpio(struct ljca_adapter *adap) if (gpio_num > LJCA_MAX_GPIO_NUM) return -EINVAL; + if (desc->bank_num > ARRAY_SIZE(valid_pin)) + return -EINVAL; + /* construct platform data */ gpio_info = kzalloc_obj(*gpio_info); if (!gpio_info) diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 98071b25ac07..8759df49be28 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -3054,6 +3054,7 @@ static struct usb_driver usbtest_driver = { .disconnect = usbtest_disconnect, .suspend = usbtest_suspend, .resume = usbtest_resume, + .no_dynamic_id = 1, }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index 1ce48f5832d7..bd0ad7c3261d 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha } printk(KERN_WARNING "get_1284_register timeout\n"); kill_all_async_requests_priv(priv); + kref_put(&rq->ref_count, destroy_async); return -EIO; } diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index 66dbfe1705d5..a40bf5bad2d5 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -1037,9 +1037,14 @@ int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) if (!mtu->gadget_driver) return 0; - if (mtu->connected) + /* Prevent runtime suspend when active connection exists */ + if (mtu->connected && PMSG_IS_AUTO(msg)) return -EBUSY; + /* Perform soft disconnect for system suspend */ + if (mtu->softconnect && !PMSG_IS_AUTO(msg)) + mtu3_dev_on_off(mtu, 0); + mtu3_dev_suspend(mtu); synchronize_irq(mtu->irq); @@ -1055,5 +1060,9 @@ int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) mtu3_dev_resume(mtu); + /* Restore soft connect for system resume */ + if (mtu->softconnect && !PMSG_IS_AUTO(msg)) + mtu3_dev_on_off(mtu, 1); + return 0; } diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 7c657ea2dabd..8138b3f3096a 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -46,6 +46,14 @@ #define WC1_IS_P_95 BIT(12) #define WC1_IS_EN_P0_95 BIT(6) +/* mt8196 */ +#define PERI_WK_CTRL0_8196 0x08 +#define WC0_IS_EN_P0_96 BIT(0) +#define WC0_IS_EN_P1_96 BIT(7) + +#define PERI_WK_CTRL1_8196 0x10 +#define WC1_IS_EN_P2_96 BIT(0) + /* mt2712 etc */ #define PERI_SSUSB_SPM_CTRL 0x0 #define SSC_IP_SLEEP_EN BIT(4) @@ -59,6 +67,9 @@ enum ssusb_uwk_vers { SSUSB_UWK_V1_3, /* mt8195 IP0 */ SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ SSUSB_UWK_V1_6, /* mt8195 IP3 */ + SSUSB_UWK_V1_7, /* mt8196 IP0 */ + SSUSB_UWK_V1_8, /* mt8196 IP1 */ + SSUSB_UWK_V1_9, /* mt8196 IP2 */ }; /* @@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; break; + case SSUSB_UWK_V1_7: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; + msk = WC0_IS_EN_P0_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_8: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; + msk = WC0_IS_EN_P1_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_9: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; + msk = WC1_IS_EN_P2_96; + val = enable ? msk : 0; + break; case SSUSB_UWK_V2: reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index cc8a864dbd63..b4f4c776adb9 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) ssusb_phy_power_off(ssusb); clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); - ssusb_wakeup_set(ssusb, true); + if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) + ssusb_wakeup_set(ssusb, true); return 0; sleep_err: diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index f56929267eaa..1b0d27d8e02f 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC tristate "Microchip PolarFire SoC platforms" depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST depends on NOP_USB_XCEIV - select USB_MUSB_DUAL_ROLE help Say Y here to enable support for USB on Microchip's PolarFire SoC. diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 8efd2fa472f9..31b061248991 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) status = -EPIPE; } else if (rx_csr & MUSB_RXCSR_H_ERROR) { - dev_err(musb->controller, "ep%d RX three-strikes error", epnum); + dev_err_ratelimited(musb->controller, + "ep%d RX three-strikes error\n", epnum); /* * The three-strikes error could only happen when the USB @@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum) rx_csr &= ~MUSB_RXCSR_H_ERROR; musb_writew(epio, MUSB_RXCSR, rx_csr); + /* + * Unplugging a USB-Ethernet adapter while it is busy can make + * the controller keep re-asserting the three-strikes error for + * this endpoint before the disconnect is processed. That floods + * the log and can wedge the host port until reboot. Drop the + * stale pending RX interrupt on platforms that support it (e.g. + * AM335x/DSPS) to break the storm; the transfer is still + * aborted below via the fault path. + */ + musb_platform_clear_ep_rxintr(musb, epnum); + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { if (USB_ENDPOINT_XFER_ISOC != qh->type) { diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index 35d79f11b03d..1986f7e9d9be 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -46,7 +46,7 @@ static const char driver_name[] = "fsl-usb2-otg"; -const pm_message_t otg_suspend_state = { +static const pm_message_t otg_suspend_state = { .event = 1, }; @@ -57,11 +57,11 @@ static struct fsl_otg *fsl_otg_dev; static int srp_wait_done; /* FSM timers */ -struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, +static struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr; /* Driver specific timers */ -struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, +static struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, *b_srp_wait_tmr, *a_wait_enum_tmr; static struct list_head active_timers; @@ -102,7 +102,7 @@ static void (*_fsl_writel)(u32 v, unsigned __iomem *p); #define fsl_writel(val, addr) writel(val, addr) #endif /* CONFIG_PPC32 */ -int write_ulpi(u8 addr, u8 data) +static int write_ulpi(u8 addr, u8 data) { u32 temp; @@ -115,7 +115,7 @@ int write_ulpi(u8 addr, u8 data) /* Operations that will be called from OTG Finite State Machine */ /* Charge vbus for vbus pulsing in SRP */ -void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on) +static void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on) { u32 tmp; @@ -133,7 +133,7 @@ void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on) } /* Discharge vbus through a resistor to ground */ -void fsl_otg_dischrg_vbus(int on) +static void fsl_otg_dischrg_vbus(int on) { u32 tmp; @@ -151,7 +151,7 @@ void fsl_otg_dischrg_vbus(int on) } /* A-device driver vbus, controlled through PP bit in PORTSC */ -void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on) +static void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on) { u32 tmp; @@ -169,7 +169,7 @@ void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on) * Pull-up D+, signalling connect by periperal. Also used in * data-line pulsing in SRP */ -void fsl_otg_loc_conn(struct otg_fsm *fsm, int on) +static void fsl_otg_loc_conn(struct otg_fsm *fsm, int on) { u32 tmp; @@ -188,7 +188,7 @@ void fsl_otg_loc_conn(struct otg_fsm *fsm, int on) * port. In host mode, controller will automatically send SOF. * Suspend will block the data on the port. */ -void fsl_otg_loc_sof(struct otg_fsm *fsm, int on) +static void fsl_otg_loc_sof(struct otg_fsm *fsm, int on) { u32 tmp; @@ -203,7 +203,7 @@ void fsl_otg_loc_sof(struct otg_fsm *fsm, int on) } /* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ -void fsl_otg_start_pulse(struct otg_fsm *fsm) +static void fsl_otg_start_pulse(struct otg_fsm *fsm) { u32 tmp; @@ -219,7 +219,7 @@ void fsl_otg_start_pulse(struct otg_fsm *fsm) fsl_otg_add_timer(fsm, b_data_pulse_tmr); } -void b_data_pulse_end(unsigned long foo) +static void b_data_pulse_end(unsigned long foo) { #ifdef HA_DATA_PULSE #else @@ -230,7 +230,7 @@ void b_data_pulse_end(unsigned long foo) fsl_otg_pulse_vbus(); } -void fsl_otg_pulse_vbus(void) +static void fsl_otg_pulse_vbus(void) { srp_wait_done = 0; fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 1); @@ -238,7 +238,7 @@ void fsl_otg_pulse_vbus(void) fsl_otg_add_timer(&fsl_otg_dev->fsm, b_vbus_pulse_tmr); } -void b_vbus_pulse_end(unsigned long foo) +static void b_vbus_pulse_end(unsigned long foo) { fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 0); @@ -251,7 +251,7 @@ void b_vbus_pulse_end(unsigned long foo) fsl_otg_add_timer(&fsl_otg_dev->fsm, b_srp_wait_tmr); } -void b_srp_end(unsigned long foo) +static void b_srp_end(unsigned long foo) { fsl_otg_dischrg_vbus(0); srp_wait_done = 1; @@ -266,7 +266,7 @@ void b_srp_end(unsigned long foo) * a_host will start by SRP. It needs to set b_hnp_enable before * actually suspending to start HNP */ -void a_wait_enum(unsigned long foo) +static void a_wait_enum(unsigned long foo) { VDBG("a_wait_enum timeout\n"); if (!fsl_otg_dev->phy.otg->host->b_hnp_enable) @@ -276,13 +276,13 @@ void a_wait_enum(unsigned long foo) } /* The timeout callback function to set time out bit */ -void set_tmout(unsigned long indicator) +static void set_tmout(unsigned long indicator) { *(int *)indicator = 1; } /* Initialize timers */ -int fsl_otg_init_timers(struct otg_fsm *fsm) +static int fsl_otg_init_timers(struct otg_fsm *fsm) { /* FSM used timers */ a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, @@ -339,7 +339,7 @@ int fsl_otg_init_timers(struct otg_fsm *fsm) } /* Uninitialize timers */ -void fsl_otg_uninit_timers(void) +static void fsl_otg_uninit_timers(void) { /* FSM used timers */ kfree(a_wait_vrise_tmr); @@ -391,7 +391,7 @@ static struct fsl_otg_timer *fsl_otg_get_timer(enum otg_fsm_timer t) } /* Add timer to timer list */ -void fsl_otg_add_timer(struct otg_fsm *fsm, void *gtimer) +static void fsl_otg_add_timer(struct otg_fsm *fsm, void *gtimer) { struct fsl_otg_timer *timer = gtimer; struct fsl_otg_timer *tmp_timer; @@ -421,7 +421,7 @@ static void fsl_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) } /* Remove timer from the timer list; clear timeout status */ -void fsl_otg_del_timer(struct otg_fsm *fsm, void *gtimer) +static void fsl_otg_del_timer(struct otg_fsm *fsm, void *gtimer) { struct fsl_otg_timer *timer = gtimer; struct fsl_otg_timer *tmp_timer, *del_tmp; @@ -443,7 +443,7 @@ static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) } /* Reset controller, not reset the bus */ -void otg_reset_controller(void) +static void otg_reset_controller(void) { u32 command; @@ -455,7 +455,7 @@ void otg_reset_controller(void) } /* Call suspend/resume routines in host driver */ -int fsl_otg_start_host(struct otg_fsm *fsm, int on) +static int fsl_otg_start_host(struct otg_fsm *fsm, int on) { struct usb_otg *otg = fsm->otg; struct device *dev; @@ -522,7 +522,7 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on) * Call suspend and resume function in udc driver * to stop and start udc driver. */ -int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +static int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) { struct usb_otg *otg = fsm->otg; struct device *dev; @@ -704,7 +704,7 @@ static int fsl_otg_start_hnp(struct usb_otg *otg) * intact. It needs to have knowledge of some USB interrupts * such as port change. */ -irqreturn_t fsl_otg_isr(int irq, void *dev_id) +static irqreturn_t fsl_otg_isr(int irq, void *dev_id) { struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; struct usb_otg *otg = ((struct fsl_otg *)dev_id)->phy.otg; @@ -830,7 +830,7 @@ static int fsl_otg_conf(struct platform_device *pdev) } /* OTG Initialization */ -int usb_otg_start(struct platform_device *pdev) +static int usb_otg_start(struct platform_device *pdev) { struct fsl_otg *p_otg; struct usb_phy *otg_trans = usb_get_phy(USB_PHY_TYPE_USB2); @@ -1002,7 +1002,7 @@ static void fsl_otg_remove(struct platform_device *pdev) pdata->exit(pdev); } -struct platform_driver fsl_otg_driver = { +static struct platform_driver fsl_otg_driver = { .probe = fsl_otg_probe, .remove = fsl_otg_remove, .driver = { diff --git a/drivers/usb/phy/phy-fsl-usb.h b/drivers/usb/phy/phy-fsl-usb.h index 95bfe7f1b83a..b754077875c3 100644 --- a/drivers/usb/phy/phy-fsl-usb.h +++ b/drivers/usb/phy/phy-fsl-usb.h @@ -373,6 +373,6 @@ struct fsl_otg_config { #define FSL_OTG_NAME "fsl-usb2-otg" -void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer); -void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer); -void fsl_otg_pulse_vbus(void); +static void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer); +static void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer); +static void fsl_otg_pulse_vbus(void); diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 8c93bde4b816..51d3035f82be 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -813,9 +813,6 @@ static void usbhs_remove(struct platform_device *pdev) flush_delayed_work(&priv->notify_hotplug_work); - usbhs_platform_call(priv, hardware_exit, pdev); - reset_control_assert(priv->rsts); - /* * Explicitly free the IRQ to ensure the interrupt handler is * disabled and synchronized before freeing resources. @@ -832,6 +829,9 @@ static void usbhs_remove(struct platform_device *pdev) if (!usbhs_get_dparam(priv, runtime_pwctrl)) usbhsc_power_ctrl(priv, 0); + usbhs_platform_call(priv, hardware_exit, pdev); + reset_control_assert(priv->rsts); + usbhsc_clk_put(priv); pm_runtime_disable(&pdev->dev); } diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index dea039163661..12db0589ee27 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -32,10 +32,6 @@ /* so we can be sure to send the full buffer in one urb */ #define DIGI_OUT_BUF_SIZE 8 -/* port input buffer length -- must be >= transfer buffer length - 3 */ -/* so we can be sure to hold at least one full buffer from one urb */ -#define DIGI_IN_BUF_SIZE 64 - /* retry timeout while sleeping */ #define DIGI_RETRY_TIMEOUT (HZ/10) @@ -176,10 +172,10 @@ /* Structures */ struct digi_serial { - spinlock_t ds_serial_lock; + struct mutex open_mutex; struct usb_serial_port *ds_oob_port; /* out-of-band port */ int ds_oob_port_num; /* index of out-of-band port */ - int ds_device_started; + int open_count; }; struct digi_port { @@ -194,7 +190,6 @@ struct digi_port { int dp_throttled; int dp_throttle_restart; wait_queue_head_t dp_flush_wait; - wait_queue_head_t dp_close_wait; /* wait queue for close */ wait_queue_head_t write_wait; struct usb_serial_port *dp_port; }; @@ -227,9 +222,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); static void digi_close(struct usb_serial_port *port); static void digi_dtr_rts(struct usb_serial_port *port, int on); -static int digi_startup_device(struct usb_serial *serial); static int digi_startup(struct usb_serial *serial); -static void digi_disconnect(struct usb_serial *serial); static void digi_release(struct usb_serial *serial); static int digi_port_probe(struct usb_serial_port *port); static void digi_port_remove(struct usb_serial_port *port); @@ -272,7 +265,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { .dtr_rts = digi_dtr_rts, .write = digi_write, .write_room = digi_write_room, - .write_bulk_callback = digi_write_bulk_callback, + .write_bulk_callback = digi_write_bulk_callback, .read_bulk_callback = digi_read_bulk_callback, .chars_in_buffer = digi_chars_in_buffer, .throttle = digi_rx_throttle, @@ -282,7 +275,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .disconnect = digi_disconnect, .release = digi_release, .port_probe = digi_port_probe, .port_remove = digi_port_remove, @@ -301,7 +293,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { .close = digi_close, .write = digi_write, .write_room = digi_write_room, - .write_bulk_callback = digi_write_bulk_callback, + .write_bulk_callback = digi_write_bulk_callback, .read_bulk_callback = digi_read_bulk_callback, .chars_in_buffer = digi_chars_in_buffer, .throttle = digi_rx_throttle, @@ -311,7 +303,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .disconnect = digi_disconnect, .release = digi_release, .port_probe = digi_port_probe, .port_remove = digi_port_remove, @@ -335,7 +326,6 @@ static struct usb_serial_driver * const serial_drivers[] = { * interruptible_sleep_on_timeout is deprecated and has been replaced * with the equivalent code. */ - static long cond_wait_interruptible_timeout_irqrestore( wait_queue_head_t *q, long timeout, spinlock_t *lock, unsigned long flags) @@ -351,6 +341,13 @@ __releases(lock) return timeout; } +static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + + return serial_priv->ds_oob_port; +} + /* * Digi Write OOB Command * @@ -361,15 +358,14 @@ __releases(lock) * the interruptible flag is true, or a negative error * returned by usb_submit_urb. */ - static int digi_write_oob_command(struct usb_serial_port *port, unsigned char *buf, int count, int interruptible) { - int ret = 0; - int len; - struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned long flags; + int ret = 0; + int len; dev_dbg(&port->dev, "digi_write_oob_command: TOP: port=%d, count=%d\n", @@ -406,10 +402,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", __func__, ret); return ret; - } - /* * Digi Write In Band Command * @@ -421,16 +415,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, * timeout ticks. Returns 0 if successful, or a negative * error returned by digi_write. */ - static int digi_write_inb_command(struct usb_serial_port *port, unsigned char *buf, int count, unsigned long timeout) { - int ret = 0; - int len; struct digi_port *priv = usb_get_serial_port_data(port); unsigned char *data = port->write_urb->transfer_buffer; unsigned long expire; unsigned long flags; + int ret = 0; + int len; dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", priv->dp_port_num, count); @@ -459,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, /* len must be a multiple of 4 and small enough to */ /* guarantee the write will send buffered data first, */ /* so commands are in order with data and not split */ - len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); if (len > 4) len &= ~3; @@ -467,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, if (priv->dp_out_buf_len > 0) { data[0] = DIGI_CMD_SEND_DATA; data[1] = priv->dp_out_buf_len; - memcpy(data + 2, priv->dp_out_buf, - priv->dp_out_buf_len); + memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); memcpy(data + 2 + priv->dp_out_buf_len, buf, len); - port->write_urb->transfer_buffer_length - = priv->dp_out_buf_len + 2 + len; + port->write_urb->transfer_buffer_length = + priv->dp_out_buf_len + 2 + len; } else { memcpy(data, buf, len); port->write_urb->transfer_buffer_length = len; @@ -484,7 +476,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, count -= len; buf += len; } - } spin_unlock_irqrestore(&priv->dp_port_lock, flags); @@ -495,7 +486,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, return ret; } - /* * Digi Set Modem Signals * @@ -505,17 +495,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, * -EINTR if interrupted while sleeping, or a non-zero error * returned by usb_submit_urb. */ - static int digi_set_modem_signals(struct usb_serial_port *port, unsigned int modem_signals, int interruptible) { - - int ret; struct digi_port *port_priv = usb_get_serial_port_data(port); - struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned char *data = oob_port->write_urb->transfer_buffer; unsigned long flags; + int ret; dev_dbg(&port->dev, "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", @@ -573,14 +561,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, * is only called from close, and only one process can be in close on a * port at a time, so its ok. */ - static int digi_transmit_idle(struct usb_serial_port *port, unsigned long timeout) { - int ret; - unsigned char buf[2]; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[2]; unsigned long flags; + int ret; spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_transmit_idle = 0; @@ -607,16 +594,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, } priv->dp_transmit_idle = 0; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return 0; - } - static void digi_rx_throttle(struct tty_struct *tty) { - unsigned long flags; struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; /* stop receiving characters by not resubmitting the read urb */ spin_lock_irqsave(&priv->dp_port_lock, flags); @@ -625,13 +611,12 @@ static void digi_rx_throttle(struct tty_struct *tty) spin_unlock_irqrestore(&priv->dp_port_lock, flags); } - static void digi_rx_unthrottle(struct tty_struct *tty) { - int ret = 0; - unsigned long flags; struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; + int ret = 0; spin_lock_irqsave(&priv->dp_port_lock, flags); @@ -651,7 +636,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) __func__, ret, priv->dp_port_num); } - static void digi_set_termios(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios) @@ -762,7 +746,6 @@ static void digi_set_termios(struct tty_struct *tty, /* set stop bits */ if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - if ((cflag & CSTOPB)) arg = DIGI_STOP_BITS_2; else @@ -772,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, buf[i++] = priv->dp_port_num; buf[i++] = arg; buf[i++] = 0; - } /* set input flow control */ @@ -841,7 +823,6 @@ static void digi_set_termios(struct tty_struct *tty, tty_encode_baud_rate(tty, baud, baud); } - static int digi_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; @@ -855,50 +836,48 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) return digi_write_inb_command(port, buf, 4, 0); } - static int digi_tiocmget(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - unsigned int val; unsigned long flags; + unsigned int val; spin_lock_irqsave(&priv->dp_port_lock, flags); val = priv->dp_modem_signals; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return val; } - static int digi_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - unsigned int val; unsigned long flags; + unsigned int val; spin_lock_irqsave(&priv->dp_port_lock, flags); val = (priv->dp_modem_signals & ~clear) | set; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return digi_set_modem_signals(port, val, 1); } - static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count) { - - int ret, data_len, new_len; struct digi_port *priv = usb_get_serial_port_data(port); unsigned char *data = port->write_urb->transfer_buffer; + int ret, data_len, new_len; unsigned long flags; dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", priv->dp_port_num, count); /* copy user data (which can sleep) before getting spin lock */ - count = min(count, port->bulk_out_size-2); + count = min(count, port->bulk_out_size - 2); count = min(64, count); /* be sure only one write proceeds at a time */ @@ -920,7 +899,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, /* allow space for any buffered data and for new data, up to */ /* transfer buffer size - 2 (for command and length bytes) */ - new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); data_len = new_len + priv->dp_out_buf_len; if (data_len == 0) { @@ -928,7 +907,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, return 0; } - port->write_urb->transfer_buffer_length = data_len+2; + port->write_urb->transfer_buffer_length = data_len + 2; *data++ = DIGI_CMD_SEND_DATA; *data++ = data_len; @@ -954,39 +933,22 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, "%s: usb_submit_urb failed, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); dev_dbg(&port->dev, "digi_write: returning %d\n", ret); - return ret; + return ret; } static void digi_write_bulk_callback(struct urb *urb) { - struct usb_serial_port *port = urb->context; - struct usb_serial *serial; - struct digi_port *priv; - struct digi_serial *serial_priv; + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; unsigned long flags; - int ret = 0; - int status = urb->status; bool wakeup; - - /* port and serial sanity check */ - if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { - pr_err("%s: port or port->private is NULL, status=%d\n", - __func__, status); - return; - } - serial = port->serial; - if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { - dev_err(&port->dev, - "%s: serial or serial->private is NULL, status=%d\n", - __func__, status); - return; - } + int ret = 0; /* handle oob callback */ if (priv->dp_port_num == serial_priv->ds_oob_port_num) { - dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; wake_up_interruptible(&priv->write_wait); @@ -999,15 +961,13 @@ static void digi_write_bulk_callback(struct urb *urb) spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; if (priv->dp_out_buf_len > 0) { - *((unsigned char *)(port->write_urb->transfer_buffer)) - = (unsigned char)DIGI_CMD_SEND_DATA; - *((unsigned char *)(port->write_urb->transfer_buffer) + 1) - = (unsigned char)priv->dp_out_buf_len; - port->write_urb->transfer_buffer_length = - priv->dp_out_buf_len + 2; - memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, - priv->dp_out_buf_len); - ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; + memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + + urb->transfer_buffer_length = priv->dp_out_buf_len + 2; + + ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret == 0) { priv->dp_write_urb_in_use = 1; priv->dp_out_buf_len = 0; @@ -1041,8 +1001,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) spin_unlock_irqrestore(&priv->dp_port_lock, flags); dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); - return room; + return room; } static unsigned int digi_chars_in_buffer(struct tty_struct *tty) @@ -1070,17 +1030,53 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); } +static int digi_open_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + int ret = 0; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count++ == 0) { + ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", + ret); + serial_priv->open_count--; + } + } + + mutex_unlock(&serial_priv->open_mutex); + + return ret; +} + +static void digi_close_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count-- == 1) { + usb_kill_urb(oob_port->read_urb); + usb_kill_urb(oob_port->write_urb); + } + + mutex_unlock(&serial_priv->open_mutex); +} + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) { - int ret; - unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); struct ktermios not_termios; - int throttled; + unsigned char buf[32]; + int ret; - /* be sure the device is started up */ - if (digi_startup_device(port->serial) != 0) - return -ENXIO; + ret = digi_open_oob_port(port->serial); + if (ret) + return ret; /* read modem signals automatically whenever they change */ buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; @@ -1106,32 +1102,34 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) } spin_lock_irq(&priv->dp_port_lock); - throttled = priv->dp_throttle_restart; priv->dp_throttled = 0; priv->dp_throttle_restart = 0; spin_unlock_irq(&priv->dp_port_lock); - if (throttled) { - ret = usb_submit_urb(port->read_urb, GFP_KERNEL); - if (ret) { - dev_err(&port->dev, "failed to submit read urb: %d\n", ret); - return ret; - } + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&port->dev, "failed to submit read urb: %d\n", ret); + goto err_close_oob; } return 0; -} +err_close_oob: + digi_close_oob_port(port->serial); + + return ret; +} static void digi_close(struct usb_serial_port *port) { + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[32]; DEFINE_WAIT(wait); int ret; - unsigned char buf[32]; - struct digi_port *priv = usb_get_serial_port_data(port); + + usb_kill_urb(port->read_urb); mutex_lock(&port->serial->disc_mutex); - /* if disconnected, just clear flags */ if (port->serial->disconnected) goto exit; @@ -1177,57 +1175,18 @@ static void digi_close(struct usb_serial_port *port) TASK_INTERRUPTIBLE); schedule_timeout(DIGI_CLOSE_TIMEOUT); finish_wait(&priv->dp_flush_wait, &wait); +exit: + mutex_unlock(&port->serial->disc_mutex); /* shutdown any outstanding bulk writes */ usb_kill_urb(port->write_urb); -exit: - spin_lock_irq(&priv->dp_port_lock); - priv->dp_write_urb_in_use = 0; - wake_up_interruptible(&priv->dp_close_wait); - spin_unlock_irq(&priv->dp_port_lock); - mutex_unlock(&port->serial->disc_mutex); -} - -/* - * Digi Startup Device - * - * Starts reads on all ports. Must be called AFTER startup, with - * urbs initialized. Returns 0 if successful, non-zero error otherwise. - */ - -static int digi_startup_device(struct usb_serial *serial) -{ - int i, ret = 0; - struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *port; - - /* be sure this happens exactly once */ - spin_lock(&serial_priv->ds_serial_lock); - if (serial_priv->ds_device_started) { - spin_unlock(&serial_priv->ds_serial_lock); - return 0; - } - serial_priv->ds_device_started = 1; - spin_unlock(&serial_priv->ds_serial_lock); - - /* start reading from each bulk in endpoint for the device */ - /* set USB_DISABLE_SPD flag for write bulk urbs */ - for (i = 0; i < serial->type->num_ports + 1; i++) { - port = serial->port[i]; - ret = usb_submit_urb(port->read_urb, GFP_KERNEL); - if (ret != 0) { - dev_err(&port->dev, - "%s: usb_submit_urb failed, ret=%d, port=%d\n", - __func__, ret, i); - break; - } - } - return ret; + digi_close_oob_port(port->serial); } static int digi_port_init(struct usb_serial_port *port, unsigned port_num) { + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); struct digi_port *priv; priv = kzalloc_obj(*priv); @@ -1235,10 +1194,13 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) return -ENOMEM; spin_lock_init(&priv->dp_port_lock); + + if (port == serial_priv->ds_oob_port) + lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); + priv->dp_port_num = port_num; init_waitqueue_head(&priv->dp_transmit_idle_wait); init_waitqueue_head(&priv->dp_flush_wait); - init_waitqueue_head(&priv->dp_close_wait); init_waitqueue_head(&priv->write_wait); priv->dp_port = port; @@ -1275,10 +1237,13 @@ static int digi_startup(struct usb_serial *serial) if (!serial_priv) return -ENOMEM; - spin_lock_init(&serial_priv->ds_serial_lock); + mutex_init(&serial_priv->open_mutex); + serial_priv->ds_oob_port_num = oob_port_num; serial_priv->ds_oob_port = serial->port[oob_port_num]; + usb_set_serial_data(serial, serial_priv); + ret = digi_port_init(serial_priv->ds_oob_port, serial_priv->ds_oob_port_num); if (ret) { @@ -1286,24 +1251,9 @@ static int digi_startup(struct usb_serial *serial) return ret; } - usb_set_serial_data(serial, serial_priv); - return 0; } - -static void digi_disconnect(struct usb_serial *serial) -{ - int i; - - /* stop reads and writes on all ports */ - for (i = 0; i < serial->type->num_ports + 1; i++) { - usb_kill_urb(serial->port[i]->read_urb); - usb_kill_urb(serial->port[i]->write_urb); - } -} - - static void digi_release(struct usb_serial *serial) { struct digi_serial *serial_priv; @@ -1333,30 +1283,25 @@ static void digi_port_remove(struct usb_serial_port *port) static void digi_read_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct digi_port *priv; - struct digi_serial *serial_priv; - int ret; + struct usb_serial *serial = port->serial; + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct digi_port *priv = usb_get_serial_port_data(port); int status = urb->status; - - /* port sanity check, do not resubmit if port is not valid */ - if (port == NULL) - return; - priv = usb_get_serial_port_data(port); - if (priv == NULL) { - dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", - __func__, status); - return; - } - if (port->serial == NULL || - (serial_priv = usb_get_serial_data(port->serial)) == NULL) { - dev_err(&port->dev, "%s: serial is bad or serial->private " - "is NULL, status=%d\n", __func__, status); - return; - } + int ret; /* do not resubmit urb if it has any status error */ - if (status) { - dev_err(&port->dev, + switch (status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_err(&serial->interface->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; + default: + dev_err(&serial->interface->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); return; @@ -1374,11 +1319,10 @@ static void digi_read_bulk_callback(struct urb *urb) /* continue read */ ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret != 0 && ret != -EPERM) { - dev_err(&port->dev, + dev_err(&serial->interface->dev, "%s: failed resubmitting urb, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); } - } /* @@ -1390,7 +1334,6 @@ static void digi_read_bulk_callback(struct urb *urb) * It returns 0 if successful, 1 if successful but the port is * throttled, and -1 if the sanity checks failed. */ - static int digi_read_inb_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; @@ -1468,10 +1411,8 @@ static int digi_read_inb_callback(struct urb *urb) dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); return throttled ? 1 : 0; - } - /* * Digi Read OOB Callback * @@ -1480,10 +1421,8 @@ static int digi_read_inb_callback(struct urb *urb) * the port->serial is valid. It returns 0 if successful, and * -1 if the sanity checks failed. */ - static int digi_read_oob_callback(struct urb *urb) { - struct usb_serial_port *port = urb->context; struct usb_serial *serial = port->serial; struct tty_struct *tty; @@ -1491,8 +1430,8 @@ static int digi_read_oob_callback(struct urb *urb) unsigned char *buf = urb->transfer_buffer; int opcode, line, status, val; unsigned long flags; - int i; unsigned int rts; + int i; if (urb->actual_length < 4) return -1; @@ -1504,7 +1443,8 @@ static int digi_read_oob_callback(struct urb *urb) status = buf[i + 2]; val = buf[i + 3]; - dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + dev_dbg(&serial->interface->dev, + "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", opcode, line, status, val); if (status != 0 || line >= serial->type->num_ports) @@ -1562,8 +1502,8 @@ static int digi_read_oob_callback(struct urb *urb) } tty_kref_put(tty); } - return 0; + return 0; } module_usb_serial_driver(serial_drivers, id_table_combined); diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index dd4cfd17f7ad..4ee0bdf98c0e 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -31,7 +31,6 @@ struct keyspan_pda_private { int tx_room; struct work_struct unthrottle_work; - struct usb_serial *serial; struct usb_serial_port *port; bool throttled; bool throttle_req; diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index 35473544f1c8..22c5f071f116 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -36,6 +36,7 @@ struct metrousb_private { spinlock_t lock; int throttled; + int throttle_req; unsigned long control_state; }; @@ -108,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) unsigned char *data = urb->transfer_buffer; unsigned long flags; int throttled = 0; - int result = 0; + int result; dev_dbg(&port->dev, "%s\n", __func__); @@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) /* Set any port variables. */ spin_lock_irqsave(&metro_priv->lock, flags); - throttled = metro_priv->throttled; + if (metro_priv->throttle_req) { + metro_priv->throttled = 1; + throttled = 1; + } spin_unlock_irqrestore(&metro_priv->lock, flags); if (throttled) @@ -169,12 +173,13 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) struct usb_serial *serial = port->serial; struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; - int result = 0; + int result; /* Set the private data information for the port. */ spin_lock_irqsave(&metro_priv->lock, flags); metro_priv->control_state = 0; metro_priv->throttled = 0; + metro_priv->throttle_req = 0; spin_unlock_irqrestore(&metro_priv->lock, flags); /* Clear the urb pipe. */ @@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) /* Set the private information for the port to stop reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); - metro_priv->throttled = 1; + metro_priv->throttle_req = 1; spin_unlock_irqrestore(&metro_priv->lock, flags); } @@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) struct usb_serial_port *port = tty->driver_data; struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; - int result = 0; + int throttled; + int result; /* Set the private information for the port to resume reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); + throttled = metro_priv->throttled; metro_priv->throttled = 0; + metro_priv->throttle_req = 0; spin_unlock_irqrestore(&metro_priv->lock, flags); - /* Submit the urb to read from the port. */ - result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); - if (result) - dev_err(&port->dev, - "failed submitting interrupt in urb error code=%d\n", - result); + if (throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", + result); + } + } } static struct usb_serial_driver metrousb_device = { diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 580f06f5ce5e..e4ad14375d6a 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2689,12 +2689,26 @@ static void option_instat_callback(struct urb *urb) dev_dbg(dev, "%s: NULL req_pkt\n", __func__); return; } + + if (urb->actual_length < sizeof(*req_pkt)) { + dev_err(dev, "%s: short packet: %u bytes\n", __func__, + urb->actual_length); + return; + } + if ((req_pkt->bRequestType == 0xA1) && (req_pkt->bRequest == 0x20)) { + unsigned char signals; int old_dcd_state; - unsigned char signals = *((unsigned char *) - urb->transfer_buffer + - sizeof(struct usb_ctrlrequest)); + + if (urb->actual_length < sizeof(*req_pkt) + 1) { + dev_err(dev, "%s: short interrupt transfer: %u bytes\n", + __func__, urb->actual_length); + return; + } + + signals = *((unsigned char *)urb->transfer_buffer + + sizeof(*req_pkt)); dev_dbg(dev, "%s: signal x%x\n", __func__, signals); diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 0bcbdcea52af..bf42a545c20f 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), @@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); #define VENDOR_WRITE_REQUEST_TYPE 0x40 #define VENDOR_WRITE_REQUEST 0x01 #define VENDOR_WRITE_NREQUEST 0x80 +#define VENDOR_WRITE_MPREQUEST 0x80 #define VENDOR_READ_REQUEST_TYPE 0xc0 #define VENDOR_READ_REQUEST 0x01 #define VENDOR_READ_NREQUEST 0x81 +#define VENDOR_READ_MPREQUEST 0x80 + +#define PL256X_RESET_REQUEST_TYPE 0x40 +#define PL256X_RESET_REQUEST 0x96 #define UART_STATE_INDEX 8 #define UART_STATE_MSR_MASK 0x8b @@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c +#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 +#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 +#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 +#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 + +#define PL256X_FLOWCTRL_MASK 0x43 +#define PL256X_FLOWCTRL_XON_XOFF 0x40 +#define PL256X_FLOWCTRL_RTS_CTS 0x03 +#define PL256X_FLOWCTRL_NONE 0x00 + static int pl2303_set_break(struct usb_serial_port *port, bool enable); enum pl2303_type { @@ -181,6 +197,7 @@ enum pl2303_type { TYPE_TB, TYPE_HXD, TYPE_HXN, + TYPE_MP, TYPE_COUNT }; @@ -196,6 +213,8 @@ struct pl2303_type_data { struct pl2303_serial_private { const struct pl2303_type_data *type; unsigned long quirks; + u16 interface_num; + u16 flowctrl_reg; }; struct pl2303_private { @@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { .max_baud_rate = 12000000, .no_divisors = true, }, + [TYPE_MP] = { + .name = "MP", + .max_baud_rate = 24000000, + .no_divisors = true, + }, }; +static int pl256x_uart_reset(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int res; + + res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, + 0, spriv->interface_num, NULL, 0, 100); + if (res) { + dev_err(dev, "failed to reset device: %d\n", res); + return res; + } + + return 0; +} + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, unsigned char buf[1]) { @@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, if (spriv->type == &pl2303_type_data[TYPE_HXN]) request = VENDOR_READ_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_READ_MPREQUEST; else request = VENDOR_READ_REQUEST; @@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) if (spriv->type == &pl2303_type_data[TYPE_HXN]) request = VENDOR_WRITE_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_WRITE_MPREQUEST; else request = VENDOR_WRITE_REQUEST; @@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) if (!buf) return -ENOMEM; - if (spriv->type == &pl2303_type_data[TYPE_HXN]) + if (spriv->type == &pl2303_type_data[TYPE_HXN] || + spriv->type == &pl2303_type_data[TYPE_MP]) { ret = pl2303_vendor_read(serial, reg, buf); - else + } else { ret = pl2303_vendor_read(serial, reg | 0x80, buf); + } if (ret) goto out_free; @@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) case 0x905: /* GT-2AB */ case 0x1005: /* GC-Q20 */ return TYPE_HXN; + case 0x3302: /* PL2533 VC 2 Port */ + case 0x3304: /* PL2533 VC 4 Port */ + case 0x4302: /* PL2543 VC 2 Port */ + case 0x4304: /* PL2543 VC 4 Port */ + case 0x6502: /* PL2565 VC 2 Port */ + case 0x6504: /* PL2565 VC 4 Port */ + case 0x6506: /* PL2565 VC 4 Port QFN64 package */ + return TYPE_MP; } break; } @@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) struct pl2303_serial_private *spriv; enum pl2303_type type; unsigned char *buf; + unsigned int ifnum; int ret; ret = pl2303_detect_type(serial); @@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) type = ret; dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ifnum = serial->interface->altsetting->desc.bInterfaceNumber; + spriv = kzalloc_obj(*spriv); if (!spriv) return -ENOMEM; + if (type == TYPE_MP) { + switch (ifnum) { + case 0: + spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; + break; + case 1: + spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; + break; + case 2: + spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; + break; + case 3: + spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; + break; + default: + kfree(spriv); + return -ENODEV; + } + } + spriv->type = &pl2303_type_data[type]; spriv->quirks = (unsigned long)usb_get_serial_data(serial); spriv->quirks |= spriv->type->quirks; + spriv->interface_num = ifnum; if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; usb_set_serial_data(serial, spriv); - if (type != TYPE_HXN) { + if (type != TYPE_HXN && type != TYPE_MP) { buf = kmalloc(1, GFP_KERNEL); if (!buf) { kfree(spriv); @@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) { struct usb_device *dev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); int retval; dev_dbg(&port->dev, "%s - %02x\n", __func__, value); retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, - value, 0, NULL, 0, 100); + value, spriv->interface_num, NULL, 0, 100); if (retval) dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); @@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, - 0, 0, buf, 7, 100); + 0, spriv->interface_num, buf, 7, 100); if (ret != 7) { dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); @@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, unsigned char buf[7]) { struct usb_device *udev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); int ret; ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, - 0, 0, buf, 7, 100); + 0, spriv->interface_num, buf, 7, 100); if (ret < 0) { dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); return ret; @@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_RTS_CTS); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_RTS_CTS); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); } @@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_XON_XOFF); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_XON_XOFF); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); } @@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_NONE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_NONE); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); } @@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, PL2303_HXN_RESET_UPSTREAM_PIPE | PL2303_HXN_RESET_DOWNSTREAM_PIPE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl256x_uart_reset(serial); } else { pl2303_vendor_write(serial, 8, 0); pl2303_vendor_write(serial, 9, 0); @@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), BREAK_REQUEST, BREAK_REQUEST_TYPE, state, - 0, NULL, 0, 100); + spriv->interface_num, NULL, 0, 100); if (result) { dev_err(&port->dev, "error sending break = %d\n", result); return result; diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h index d60eda7f6eda..8eb1e7b5d3ec 100644 --- a/drivers/usb/serial/pl2303.h +++ b/drivers/usb/serial/pl2303.h @@ -26,7 +26,7 @@ #define PL2303_PRODUCT_ID_HCR331 0x331a #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 - +#define PL256X_PRODUCT_ID_4P 0x2533 #define ATEN_VENDOR_ID 0x0557 #define ATEN_VENDOR_ID2 0x0547 diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index c11d64bf08fb..3ef7ca2c0cf4 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -133,14 +133,14 @@ struct spcp8x5_private { static int spcp8x5_probe(struct usb_serial *serial, const struct usb_device_id *id) { - usb_set_serial_data(serial, (void *)id); + usb_set_serial_data(serial, (void *)id->driver_info); return 0; } static int spcp8x5_port_probe(struct usb_serial_port *port) { - const struct usb_device_id *id = usb_get_serial_data(port->serial); + unsigned int quirks = (unsigned int)(unsigned long)usb_get_serial_data(port->serial); struct spcp8x5_private *priv; priv = kzalloc_obj(*priv); @@ -148,7 +148,7 @@ static int spcp8x5_port_probe(struct usb_serial_port *port) return -ENOMEM; spin_lock_init(&priv->lock); - priv->quirks = id->driver_info; + priv->quirks = quirks; usb_set_serial_port_data(port, priv); @@ -235,18 +235,6 @@ static void spcp8x5_set_work_mode(struct usb_serial_port *port, u16 value, dev_err(&port->dev, "failed to set work mode: %d\n", ret); } -static int spcp8x5_carrier_raised(struct usb_serial_port *port) -{ - u8 msr; - int ret; - - ret = spcp8x5_get_msr(port, &msr); - if (ret || msr & MSR_STATUS_LINE_DCD) - return 1; - - return 0; -} - static void spcp8x5_dtr_rts(struct usb_serial_port *port, int on) { struct spcp8x5_private *priv = usb_get_serial_port_data(port); @@ -458,7 +446,6 @@ static struct usb_serial_driver spcp8x5_device = { .num_bulk_out = 1, .open = spcp8x5_open, .dtr_rts = spcp8x5_dtr_rts, - .carrier_raised = spcp8x5_carrier_raised, .set_termios = spcp8x5_set_termios, .init_termios = spcp8x5_init_termios, .tiocmget = spcp8x5_tiocmget, diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig index 7867fa7c405d..b054c0b6a8d4 100644 --- a/drivers/usb/typec/altmodes/Kconfig +++ b/drivers/usb/typec/altmodes/Kconfig @@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers" config TYPEC_DP_ALTMODE tristate "DisplayPort Alternate Mode driver" depends on DRM + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF help DisplayPort USB Type-C Alternate Mode allows DisplayPort displays and adapters to be attached to the USB Type-C diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 263a89c5f324..51c92dd887ab 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt) struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); struct fwnode_handle *fwnode; struct dp_altmode *dp; + u32 cap = DP_CAP_CAPABILITY(alt->vdo); /* Port can only be DFP_U. */ if (typec_altmode_get_data_role(alt) != TYPEC_HOST) @@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt) return -ENODEV; } + /* + * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section + * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a + * USB-C receptacle must advertise at least one pin assignment for the + * capability it claims, otherwise Alt Mode can never be configured. + */ + if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || + (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { + typec_altmode_put_plug(plug); + return -ENODEV; + } + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); if (!dp) { typec_altmode_put_plug(plug); @@ -790,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt) dp->alt = alt; alt->desc = "DisplayPort"; - typec_altmode_set_ops(alt, &dp_altmode_ops); if (plug) { plug->desc = "Displayport"; @@ -811,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt) if (plug) typec_altmode_set_drvdata(plug, dp); + if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) + return 0; + + typec_altmode_set_ops(alt, &dp_altmode_ops); if (!alt->mode_selection) { dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; schedule_work(&dp->work); diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c index 32250b94262a..2e1b1a1da7d9 100644 --- a/drivers/usb/typec/altmodes/thunderbolt.c +++ b/drivers/usb/typec/altmodes/thunderbolt.c @@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) alt->desc = "Thunderbolt3"; typec_altmode_set_drvdata(alt, tbt); + + if (typec_cable_altmode_unsupported(alt)) + return 0; + typec_altmode_set_ops(alt, &tbt_altmode_ops); if (!alt->mode_selection && tbt_ready(alt)) { @@ -303,6 +307,8 @@ static void tbt_altmode_remove(struct typec_altmode *alt) { struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + disable_work_sync(&tbt->work); + for (int i = TYPEC_PLUG_SOP_PP; i >= 0; --i) { if (tbt->plug[i]) typec_altmode_put_plug(tbt->plug[i]); diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 0595e8cb83aa..54c7810b563b 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) } EXPORT_SYMBOL_GPL(typec_cable_is_active); +enum typec_cable_altmode_support { + CABLE_SUPPORT_UNKNOWN, + CABLE_SUPPORTED, + CABLE_NOT_SUPPORTED, +}; + +static enum typec_cable_altmode_support +typec_cable_check_altmode_support(struct typec_cable *cable, + struct typec_altmode *alt) +{ + struct typec_altmode *plug; + u32 speed; + + /* + * Check if the cable has an e-marker, supports modal operation, and the + * SOP' altmode nodes are created. + */ + plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + if (plug) { + typec_altmode_put_plug(plug); + return CABLE_SUPPORTED; + } + + /* The identity is not specified */ + if (!cable->identity) + return CABLE_SUPPORT_UNKNOWN; + + /* Non-e-marked cable */ + if (!cable->identity->id_header) + return CABLE_NOT_SUPPORTED; + + switch (PD_IDH_PTYPE(cable->identity->id_header)) { + case IDH_PTYPE_PCABLE: + speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); + if (speed == CABLE_USB2_ONLY) + return CABLE_NOT_SUPPORTED; + return CABLE_SUPPORTED; + case IDH_PTYPE_ACABLE: + /* + * Active cables must establish an SOP' communication + * node. Since that check failed at the beginning of + * this function, this active cable does not support + * this specific altmode. + */ + return CABLE_NOT_SUPPORTED; + } + + return CABLE_SUPPORT_UNKNOWN; +} + +/** + * typec_cable_altmode_unsupported - Check if a cable restricts altmode + * @alt: The Alternate Mode to evaluate + * + * Returns true if the connected cable is incapable of handling the altmode. + */ +bool typec_cable_altmode_unsupported(struct typec_altmode *alt) +{ + enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; + struct typec_cable *cable; + + cable = typec_cable_get(typec_altmode2port(alt)); + if (cable) { + support = typec_cable_check_altmode_support(cable, alt); + typec_cable_put(cable); + } + + return support == CABLE_NOT_SUPPORTED; +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); + /** * typec_cable_set_identity - Report result from Discover Identity command * @cable: The cable updated identity values diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index 3e39b800e6b5..d0de5a2488f9 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -218,7 +218,7 @@ static void hd3ss3220_regulator_control(struct hd3ss3220 *hd3ss3220, bool on) if (ret) dev_err(hd3ss3220->dev, - "vbus regulator %s failed: %d\n", on ? "disable" : "enable", ret); + "vbus regulator %s failed: %d\n", on ? "enable" : "disable", ret); } static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c index d6e1289a4945..34ef81f75693 100644 --- a/drivers/usb/typec/mux/tusb1046.c +++ b/drivers/usb/typec/mux/tusb1046.c @@ -178,6 +178,7 @@ static const struct of_device_id tusb1046_match_table[] = { {.compatible = "ti,tusb1046"}, {}, }; +MODULE_DEVICE_TABLE(of, tusb1046_match_table); static struct i2c_driver tusb1046_driver = { .driver = { diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 7ac7000b2d13..6717ac914c6a 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -38,6 +38,7 @@ struct tcpci { struct regmap *regmap; unsigned int alert_mask; + unsigned int rx_type_mask; bool controls_vbus; @@ -488,6 +489,8 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) if (tcpci->data->cable_comm_capable) reg |= TCPC_RX_DETECT_SOP1; } + + tcpci->rx_type_mask = reg; ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); if (ret < 0) return ret; @@ -749,6 +752,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) if (status & TCPC_ALERT_RX_STATUS) { struct pd_message msg; unsigned int cnt, payload_cnt; + enum tcpm_transmit_type rx_type; u16 header; regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt); @@ -773,10 +777,16 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) regmap_raw_read(tcpci->regmap, TCPC_RX_DATA, &msg.payload, payload_cnt); + ret = regmap_read(tcpci->regmap, TCPC_RX_BUF_FRAME_TYPE, &rx_type); + if (ret) + return ret; + /* Read complete, clear RX status alert bit */ tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP); + rx_type &= TCPC_RX_BUF_FRAME_TYPE_MASK; + if (tcpci->rx_type_mask & BIT(rx_type)) + tcpm_pd_receive(tcpci->port, &msg, rx_type); } if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) { diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 89eec20a2064..a8cd1959c426 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -212,6 +213,24 @@ static const char * const tcpm_ams_str[] = { FOREACH_AMS(GENERATE_STRING) }; +#define FOREACH_VDM_DISCOVERY(S) \ + S(VDM_DISCOVERY_UNKNOWN), \ + S(VDM_DISCOVERY_PARTNER_IDENT), \ + S(VDM_DISCOVERY_CABLE_IDENT), \ + S(VDM_DISCOVERY_PARTNER_SVIDS), \ + S(VDM_DISCOVERY_PARTNER_MODES), \ + S(VDM_DISCOVERY_CABLE_SVIDS), \ + S(VDM_DISCOVERY_CABLE_MODES), \ + S(VDM_DISCOVERY_COMPLETE) + +enum vdm_discovery_states { + FOREACH_VDM_DISCOVERY(GENERATE_ENUM) +}; + +static const char * const vdm_discovery_state_strings[] = { + FOREACH_VDM_DISCOVERY(GENERATE_STRING) +}; + enum vdm_states { VDM_STATE_ERR_BUSY = -3, VDM_STATE_ERR_SEND = -2, @@ -232,7 +251,9 @@ enum pd_msg_request { PD_MSG_DATA_SINK_CAP, PD_MSG_DATA_SOURCE_CAP, PD_MSG_DATA_REV, - PD_MSG_EXT_SINK_CAP_EXT + PD_MSG_EXT_SINK_CAP_EXT, + PD_MSG_DATA_BATT_STATUS, + PD_MSG_EXT_BATT_CAP, }; enum adev_actions { @@ -274,7 +295,7 @@ enum frs_typec_current { #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) #define GET_SINK_CAP_RETRY_MS 100 -#define SEND_DISCOVER_RETRY_MS 100 +#define SEND_DISCOVERY_VDM_RETRY_MS 100 struct pd_mode_data { int svid_index; /* current SVID index */ @@ -387,7 +408,15 @@ struct pd_timings { }; /* Convert microwatt to watt */ -#define UW_TO_W(pow) ((pow) / 1000000) +#define UW_TO_W(pow) (div_u64((pow), 1000000)) + +/* + * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries + * that a port can be queried is restricted to 4. + */ +#define MAX_NUM_FIXED_BATT 4 + +#define BATTERY_PROPERTY_UNKNOWN 0xffff /* * struct pd_identifier - Contains info about PD identifiers @@ -483,8 +512,6 @@ struct tcpm_port { bool vbus_source; bool vbus_charge; - /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */ - bool send_discover; bool op_vsafe5v; int try_role; @@ -510,8 +537,8 @@ struct tcpm_port { struct kthread_work vdm_state_machine; struct hrtimer enable_frs_timer; struct kthread_work enable_frs; - struct hrtimer send_discover_timer; - struct kthread_work send_discover_work; + struct hrtimer vdm_discovery_timer; + struct kthread_work vdm_discovery_work; bool state_machine_running; /* Set to true when VDM State Machine has following actions. */ bool vdm_sm_running; @@ -578,6 +605,9 @@ struct tcpm_port { u32 bist_request; + /* VDM Discovery State to determine message sent */ + enum vdm_discovery_states vdm_discovery_state; + /* PD state for Vendor Defined Messages */ enum vdm_states vdm_state; u32 vdm_retries; @@ -641,12 +671,6 @@ struct tcpm_port { bool potential_contaminant; /* SOP* Related Fields */ - /* - * Flag to determine if SOP' Discover Identity is available. The flag - * is set if Discover Identity on SOP' does not immediately follow - * Discover Identity on SOP. - */ - bool send_discover_prime; /* * tx_sop_type determines which SOP* a message is being sent on. * For messages that are queued and not sent immediately such as in @@ -683,6 +707,9 @@ struct tcpm_port { struct pd_identifier pd_ident; struct sink_caps_ext_data sink_caps_ext; + struct power_supply **fixed_batt; + u32 fixed_batt_cnt; + u32 batt_request_id; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -771,6 +798,9 @@ static const char * const pd_rev[] = { #define tcpm_wait_for_discharge(port) \ (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) +#define tcpm_can_send_vdm(state) \ + ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) { if (port->port_type == TYPEC_PORT_DRP) { @@ -1470,6 +1500,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } +static void tcpm_get_fixed_batt(struct tcpm_port *port) +{ + int ret; + + if (!port->self_powered || port->fixed_batt_cnt > 0) + return; + + ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); + if (ret < 0) + tcpm_log(port, "Failed to get battery array, ret=%d", ret); + else + port->fixed_batt_cnt = ret; +} + static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) { u16 operating_snk_watt = port->operating_snk_mw / 1000; @@ -1482,6 +1526,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) if (!port->self_powered) data->spr_op_pdp = operating_snk_watt; + tcpm_get_fixed_batt(port); + /* * SPR Sink Minimum PDP indicates the minimum power required to operate * a sink device in its lowest level of functionality without requiring @@ -1507,6 +1553,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) skedb.load_step = data->load_step; skedb.load_char = cpu_to_le16(data->load_char); skedb.compliance = data->compliance; + skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); skedb.modes = data->modes; skedb.spr_min_pdp = data->spr_min_pdp; skedb.spr_op_pdp = data->spr_op_pdp; @@ -1525,6 +1572,153 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) port->message_id, data_obj_cnt, 1 /* Denotes if ext header */)); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static u16 tcpm_charge_to_energy(int charge, int voltage) +{ + u64 energy = div_u64((u64)charge * voltage, 1000000); + + /* Battery telemetry is reported in increments of 0.1Wh */ + return (u16)UW_TO_W(energy * 10); +} + +static int tcpm_pd_send_batt_status(struct tcpm_port *port) +{ + u16 present_charge = BATTERY_PROPERTY_UNKNOWN; + bool batt_present = false, invalid_ref = true; + u32 batt_id = port->batt_request_id; + union power_supply_propval val; + struct power_supply *batt; + u8 charging_status = 0; + struct pd_message msg; + int ret, charge_now; + u32 bsdo; + + tcpm_get_fixed_batt(port); + memset(&msg, 0, sizeof(msg)); + + if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) + goto send_status; + + invalid_ref = false; + batt = port->fixed_batt[batt_id]; + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); + if (ret) + tcpm_log(port, + "Failed to fetch power_supply_prop_present ret %d", + ret); + else + batt_present = val.intval > 0; + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, + &val); + if (!ret) { + charge_now = val.intval; + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + &val); + if (!ret) + present_charge = tcpm_charge_to_energy(charge_now, + val.intval); + } + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); + if (!ret) { + switch (val.intval) { + case POWER_SUPPLY_STATUS_CHARGING: + charging_status = BSDO_BATTERY_INFO_CHARGING; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + charging_status = BSDO_BATTERY_INFO_DISCHARGING; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + case POWER_SUPPLY_STATUS_FULL: + charging_status = BSDO_BATTERY_INFO_IDLE; + break; + default: + charging_status = BSDO_BATTERY_INFO_RSVD; + break; + } + } + +send_status: + + bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); + msg.payload[0] = cpu_to_le32(bsdo); + msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 1); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_batt_cap(struct tcpm_port *port) +{ + u16 design_cap = BATTERY_PROPERTY_UNKNOWN; + u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; + u32 batt_id = port->batt_request_id; + union power_supply_propval val; + struct batt_cap_ext_msg bcdb; + struct power_supply *batt; + bool invalid_ref = true; + struct pd_message msg; + u8 data_obj_cnt; + int ret, vol; + + tcpm_get_fixed_batt(port); + memset(&msg, 0, sizeof(msg)); + + if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) + goto send_cap; + + invalid_ref = false; + batt = port->fixed_batt[batt_id]; + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, + &val); + if (!ret) { + vol = val.intval; + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + &val); + if (!ret) + design_cap = tcpm_charge_to_energy(val.intval, vol); + + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_CHARGE_FULL, + &val); + if (!ret) + charge_cap = tcpm_charge_to_energy(val.intval, vol); + } + +send_cap: + + /* + * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the + * USB-IF) does not exist or an invalid battery reference is made by the + * requestor, then set the VID field to 0xffff. If the VID field is + * 0xffff, set the PID field to 0. + */ + bcdb.vid = BATTERY_PROPERTY_UNKNOWN; + bcdb.pid = 0; + bcdb.batt_design_cap = cpu_to_le16(design_cap); + bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); + bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; + memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); + msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), + 0, /* Denotes if request chunk */ + 0, /* Chunk number */ + 1 /* Chunked */); + + data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); + msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, + port->data_role, port->negotiated_rev, + port->message_id, data_obj_cnt); + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } @@ -1559,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del } } -static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) +{ + hrtimer_cancel(&port->vdm_discovery_timer); + kthread_cancel_work_sync(&port->vdm_discovery_work); +} + +static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) { if (delay_ms) { - hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); } else { - hrtimer_cancel(&port->send_discover_timer); - kthread_queue_work(port->wq, &port->send_discover_work); + hrtimer_cancel(&port->vdm_discovery_timer); + kthread_queue_work(port->wq, &port->vdm_discovery_work); } } @@ -1773,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, WARN_ON(!mutex_is_locked(&port->lock)); /* If is sending discover_identity, handle received message first */ - if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { - if (tx_sop_type == TCPC_TX_SOP_PRIME) - port->send_discover_prime = true; - else - port->send_discover = true; - mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); - } else { + if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) + mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + else /* Make sure we are not still processing a previous VDM packet */ WARN_ON(port->vdm_state > VDM_STATE_DONE); - } port->vdo_count = cnt + 1; port->vdo_data[0] = header; @@ -1805,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) struct tcpm_port *port = event->port; mutex_lock(&port->lock); - if (port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) { + if (!tcpm_can_send_vdm(port->state)) { tcpm_log_force(port, "dropping altmode_vdm_event"); goto port_unlock; } @@ -2034,7 +2228,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) if (!port->partner) return; - for (i = 0; i < modep->altmodes; i++) { + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { altmode = typec_partner_register_altmode(port->partner, &modep->altmode_desc[i]); if (IS_ERR(altmode)) { @@ -2052,9 +2246,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port) struct typec_altmode *altmode; int i; - typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); + typec_plug_set_num_altmodes(port->plug_prime, + min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); - for (i = 0; i < modep->altmodes; i++) { + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { altmode = typec_plug_register_altmode(port->plug_prime, &modep->altmode_desc[i]); if (IS_ERR(altmode)) { @@ -2154,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port) tcpm_can_communicate_sop_prime(port); } +static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, + enum vdm_discovery_states new_state) +{ + enum vdm_discovery_states old_state = port->vdm_discovery_state; + + if (old_state != new_state) + tcpm_log_force(port, "vdm discovery state changed: %s -> %s", + vdm_discovery_state_strings[old_state], + vdm_discovery_state_strings[new_state]); + + port->vdm_discovery_state = new_state; +} + static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, enum tcpm_transmit_type rx_sop_type, enum tcpm_transmit_type *response_tx_sop_type) @@ -2171,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, response[0] = VDO(svid, 1, typec_get_negotiated_svdm_version(typec), CMD_DISCOVER_MODES); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); return 1; } @@ -2179,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, response[0] = VDO(USB_SID_PD, 1, typec_get_cable_svdm_version(typec), CMD_DISCOVER_SVID); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); return 1; } tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { modep = &port->mode_data_prime; modep->svid_index++; @@ -2193,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, response[0] = VDO(svid, 1, typec_get_cable_svdm_version(typec), CMD_DISCOVER_MODES); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); return 1; } tcpm_register_plug_altmodes(port); tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } return 0; @@ -2376,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, typec_cable_set_svdm_version(port->cable, svdm_version); } + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); + /* 6.4.4.3.1 */ svdm_consume_identity(port, p, cnt); /* Attempt Vconn swap, delay SOP' discovery if necessary */ if (tcpm_attempt_vconn_swap_discovery(port)) { - port->send_discover_prime = true; port->upcoming_state = VCONN_SWAP_SEND; ret = tcpm_ams_start(port, VCONN_SWAP); if (!ret) return 0; /* Cannot perform Vconn swap */ port->upcoming_state = INVALID_STATE; - port->send_discover_prime = false; } /* @@ -2398,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, if (IS_ERR_OR_NULL(port->cable) && tcpm_can_communicate_sop_prime(port)) { *response_tx_sop_type = TCPC_TX_SOP_PRIME; - port->send_discover_prime = true; response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec), CMD_DISCOVER_IDENT); @@ -2426,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); return 0; } + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); *response_tx_sop_type = TCPC_TX_SOP; response[0] = VDO(USB_SID_PD, 1, @@ -2445,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, rlen = 1; } else { if (rx_sop_type == TCPC_TX_SOP) { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_PARTNER_SVIDS); if (modep->nsvids && supports_modal(port)) { response[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); rlen = 1; + } else { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_COMPLETE); } } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_CABLE_SVIDS); if (modep_prime->nsvids) { response[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); rlen = 1; + } else { + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_COMPLETE); } } } @@ -2505,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, case CMDT_RSP_NAK: tcpm_ams_finish(port); switch (cmd) { + /* + * The cable is not allowed to respond with NAK so this must've happened over SOP + */ case CMD_DISCOVER_IDENT: case CMD_DISCOVER_SVID: + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): break; case CMD_DISCOVER_MODES: @@ -2686,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, port->vdm_sm_running = false; } -static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, - const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) -{ - int svdm_version; - u32 header; - - switch (tx_sop_type) { - case TCPC_TX_SOP_PRIME: - /* - * If the port partner is discovered, then the port partner's - * SVDM Version will be returned - */ - svdm_version = typec_get_cable_svdm_version(port->typec_port); - if (svdm_version < 0) - svdm_version = SVDM_VER_MAX; - break; - case TCPC_TX_SOP: - svdm_version = typec_get_negotiated_svdm_version(port->typec_port); - if (svdm_version < 0) - return; - break; - default: - svdm_version = typec_get_negotiated_svdm_version(port->typec_port); - if (svdm_version < 0) - return; - break; - } - - if (WARN_ON(count > VDO_MAX_SIZE - 1)) - count = VDO_MAX_SIZE - 1; - - /* set VDM header with VID & CMD */ - header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? - 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), - svdm_version, cmd); - tcpm_queue_vdm(port, header, data, count, tx_sop_type); -} - static unsigned int vdm_ready_timeout(u32 vdm_hdr) { unsigned int timeout; @@ -2769,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) * if there's traffic or we're not in PDO ready state don't send * a VDM. */ - if (port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) { + if (!tcpm_can_send_vdm(port->state)) { port->vdm_sm_running = false; break; } @@ -2780,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) switch (PD_VDO_CMD(vdo_hdr)) { case CMD_DISCOVER_IDENT: res = tcpm_ams_start(port, DISCOVER_IDENTITY); - if (res == 0) { - switch (port->tx_sop_type) { - case TCPC_TX_SOP_PRIME: - port->send_discover_prime = false; - break; - case TCPC_TX_SOP: - port->send_discover = false; - break; - default: - port->send_discover = false; - break; - } - } else if (res == -EAGAIN) { + if (res == -EAGAIN) { port->vdo_data[0] = 0; - mod_send_discover_delayed_work(port, - SEND_DISCOVER_RETRY_MS); + mod_vdm_discovery_delayed_work(port, + SEND_DISCOVERY_VDM_RETRY_MS); } break; case CMD_DISCOVER_SVID: @@ -2853,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) */ if (port->state == SRC_VDM_IDENTITY_REQUEST) { tcpm_ams_finish(port); + port->vdo_data[0] = 0; port->vdm_state = VDM_STATE_DONE; tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); /* @@ -2869,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) tcpm_ams_finish(port); } else { tcpm_ams_finish(port); + port->vdo_data[0] = 0; if (port->tx_sop_type == TCPC_TX_SOP) break; /* Handle SOP' Transmission Errors */ @@ -2878,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) * discovery process on SOP only. */ case CMD_DISCOVER_IDENT: - port->vdo_data[0] = 0; response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version( port->typec_port), CMD_DISCOVER_SVID); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); tcpm_queue_vdm(port, response[0], &response[1], 0, TCPC_TX_SOP); break; @@ -2892,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) */ case CMD_DISCOVER_SVID: tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); break; case CMD_DISCOVER_MODES: tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); break; default: break; @@ -3807,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); } else { - if (port->send_discover && port->negotiated_rev < PD_REV30) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -3823,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); } else { - if (port->send_discover && port->negotiated_rev < PD_REV30) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -3832,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, } break; case PD_CTRL_VCONN_SWAP: - if (port->send_discover && port->negotiated_rev < PD_REV30) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -3891,6 +4076,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, { enum pd_ext_msg_type type = pd_header_type_le(msg->header); unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); + const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; /* stopping VDM state machine if interrupted by other Messages */ if (tcpm_vdm_ams(port)) { @@ -3899,7 +4085,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, mod_vdm_delayed_work(port, 0); } - if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { + if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); tcpm_log(port, "Unchunked extended messages unsupported"); return; @@ -3924,9 +4110,25 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, NONE_AMS, 0); } break; - case PD_EXT_SOURCE_CAP_EXT: - case PD_EXT_GET_BATT_CAP: case PD_EXT_GET_BATT_STATUS: + if (data_size >= 1) { + port->batt_request_id = ext_msg->data[0]; + tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, + GETTING_BATTERY_STATUS); + } else { + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } + break; + case PD_EXT_GET_BATT_CAP: + if (data_size >= 1) { + port->batt_request_id = ext_msg->data[0]; + tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, + GETTING_BATTERY_CAPABILITIES); + } else { + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } + break; + case PD_EXT_SOURCE_CAP_EXT: case PD_EXT_BATT_CAP: case PD_EXT_GET_MANUFACTURER_INFO: case PD_EXT_MANUFACTURER_INFO: @@ -4137,6 +4339,22 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) ret); tcpm_ams_finish(port); break; + case PD_MSG_DATA_BATT_STATUS: + ret = tcpm_pd_send_batt_status(port); + if (ret) + tcpm_log(port, + "Failed to send battery status ret=%d", + ret); + tcpm_ams_finish(port); + break; + case PD_MSG_EXT_BATT_CAP: + ret = tcpm_pd_send_batt_cap(port); + if (ret) + tcpm_log(port, + "Failed to send battery cap ret=%d", + ret); + tcpm_ams_finish(port); + break; default: break; } @@ -4853,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port) port->partner = NULL; port->attached = true; - port->send_discover = true; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); return 0; @@ -4896,11 +5113,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) struct pd_mode_data *modep_prime = &port->mode_data_prime; int i; - for (i = 0; i < modep->altmodes; i++) { + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { typec_unregister_altmode(port->partner_altmode[i]); port->partner_altmode[i] = NULL; } - for (i = 0; i < modep_prime->altmodes; i++) { + for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { typec_unregister_altmode(port->plug_prime_altmode[i]); port->plug_prime_altmode[i] = NULL; } @@ -4931,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port) port->in_ams = false; port->ams = NONE_AMS; port->vdm_sm_running = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + mod_vdm_discovery_cancel_delayed_work(port); tcpm_unregister_altmodes(port); tcpm_typec_disconnect(port); port->attached = false; @@ -5014,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) port->partner = NULL; port->attached = true; - port->send_discover = true; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); return 0; } @@ -5422,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port) * as well. */ if (port->explicit_contract) { - if (port->send_discover_prime) { - port->tx_sop_type = TCPC_TX_SOP_PRIME; - } else { - port->tx_sop_type = TCPC_TX_SOP; + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) tcpm_set_initial_svdm_version(port); - } - mod_send_discover_delayed_work(port, 0); + mod_vdm_discovery_delayed_work(port, 0); } else { - port->send_discover = false; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } /* @@ -5814,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port) * as well. */ if (port->explicit_contract) { - if (port->send_discover_prime) { - port->tx_sop_type = TCPC_TX_SOP_PRIME; - } else { - port->tx_sop_type = TCPC_TX_SOP; + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) tcpm_set_initial_svdm_version(port); - } - mod_send_discover_delayed_work(port, 0); + mod_vdm_discovery_delayed_work(port, 0); } else { - port->send_discover = false; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); } power_supply_changed(port->psy); @@ -5869,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port) port->tcpc->set_pd_rx(port->tcpc, false); tcpm_unregister_altmodes(port); port->nr_sink_caps = 0; - port->send_discover = true; - port->send_discover_prime = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + mod_vdm_discovery_cancel_delayed_work(port); if (port->pwr_role == TYPEC_SOURCE) tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, PD_T_PS_HARD_RESET); @@ -6017,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port) /* DR_Swap states */ case DR_SWAP_SEND: tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { - port->send_discover = true; - port->send_discover_prime = false; - } tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; case DR_SWAP_ACCEPT: tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { - port->send_discover = true; - port->send_discover_prime = false; - } tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); break; case DR_SWAP_SEND_TIMEOUT: tcpm_swap_complete(port, -ETIMEDOUT); - port->send_discover = false; - port->send_discover_prime = false; tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -6047,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port) else tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, TYPEC_HOST); + if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -6330,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port) /* Cable states */ case SRC_VDM_IDENTITY_REQUEST: - port->send_discover_prime = true; - port->tx_sop_type = TCPC_TX_SOP_PRIME; - mod_send_discover_delayed_work(port, 0); + mod_vdm_discovery_delayed_work(port, 0); port->upcoming_state = SRC_SEND_CAPABILITIES; break; @@ -7036,8 +7234,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) goto unlock; /* Send when the state machine is idle */ - if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || - port->send_discover_prime) + if (port->state != SNK_READY || port->vdm_sm_running || + port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) goto resched; port->upcoming_state = GET_SINK_CAP; @@ -7054,29 +7252,160 @@ static void tcpm_enable_frs_work(struct kthread_work *work) mutex_unlock(&port->lock); } -static void tcpm_send_discover_work(struct kthread_work *work) +static void tcpm_vdm_discovery_work(struct kthread_work *work) { - struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work); + struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); + enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; + struct typec_port *typec = port->typec_port; + struct pd_mode_data *modep, *modep_prime; + u32 msg[2] = { }; + int svdm_version; mutex_lock(&port->lock); - /* No need to send DISCOVER_IDENTITY anymore */ - if (!port->send_discover && !port->send_discover_prime) - goto unlock; - if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { - port->send_discover = false; - port->send_discover_prime = false; + tcpm_log_force(port, "%s state [%s]", __func__, + vdm_discovery_state_strings[port->vdm_discovery_state]); + + /* No need to perform work if Discovery process is complete */ + if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) goto unlock; - } /* Retry if the port is not idle */ - if ((port->state != SRC_READY && port->state != SNK_READY && - port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { - mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); + if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { + mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); goto unlock; } - tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); + modep = &port->mode_data; + modep_prime = &port->mode_data_prime; + + svdm_version = typec_get_negotiated_svdm_version(typec); + + switch (port->vdm_discovery_state) { + /* + * The port has not received a Discover Identity response from the port partner. + * + * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and + * SNK_READY states if there is an explicit contract + * 2. The port will send Discover Identity to the cable over SOP' in the + * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. + */ + case VDM_DISCOVERY_UNKNOWN: + /* Can't send Discover Identity, VDM discovery is complete */ + if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = SVDM_VER_MAX; + } + + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); + break; + /* + * The port has received a Discover Identity ACK from the port partner. + * + * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and + * SNK_READY states if it did not previously discover the cable but is capable of doing + * so. + * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and + * SNK_READY states otherwise. + */ + case VDM_DISCOVERY_PARTNER_IDENT: + if (tcpm_can_communicate_sop_prime(port) && !port->cable) { + tx_sop_type = TCPC_TX_SOP_PRIME; + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); + } else { + if (tcpm_can_communicate_sop_prime(port)) + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + } + break; + /* + * The port has received a Discover Identity ACK from the cable. + * + * 1. The port will send Discover SVIDs to the partner over SOP. + */ + case VDM_DISCOVERY_CABLE_IDENT: + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + break; + /* + * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported + * by the partner. + * + * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports + * modal operation and valid SVIDs were registered. + * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. + */ + case VDM_DISCOVERY_PARTNER_SVIDS: + if (modep->nsvids && supports_modal(port)) { + msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + break; + /* + * The port has received a Discover Modes ACK from the partner for any mode. + * + * 1. The port will send Discover Modes for the next SVID that has not been discovered to + * the port partner over SOP. + * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' + * and the cable supports VDMs. + * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. + */ + case VDM_DISCOVERY_PARTNER_MODES: + /* Not all modes have been discovered yet */ + if (modep->svid_index < modep->nsvids) { + msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, + CMD_DISCOVER_MODES); + } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + break; + /* + * The port has received a Discover SVIDs ACK from the cable over SOP'. + * + * 1. The port will send Discover Modes for the first SVID over SOP'. + */ + case VDM_DISCOVERY_CABLE_SVIDS: + if (modep_prime->nsvids) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); + } else { + goto unlock; + } + break; + /* + * The port has received a Discover Modes ACK from the cable for any mode. + * + * 1. The port will send Discover Modes for the next SVID that has not been discovered to + * the cable over SOP'. + */ + case VDM_DISCOVERY_CABLE_MODES: + if (modep_prime->svid_index < modep_prime->nsvids) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, + CMD_DISCOVER_MODES); + } else { + goto unlock; + } + break; + default: + goto unlock; + } + + if (svdm_version >= 0) + tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); unlock: mutex_unlock(&port->lock); @@ -8492,12 +8821,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) return HRTIMER_NORESTART; } -static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer) +static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) { - struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); + struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); if (port->registered) - kthread_queue_work(port->wq, &port->send_discover_work); + kthread_queue_work(port->wq, &port->vdm_discovery_work); return HRTIMER_NORESTART; } @@ -8531,14 +8860,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); kthread_init_work(&port->event_work, tcpm_pd_event_handler); kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); - kthread_init_work(&port->send_discover_work, tcpm_send_discover_work); + kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC, + hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL); spin_lock_init(&port->pd_event_lock); @@ -8633,11 +8962,12 @@ void tcpm_unregister_port(struct tcpm_port *port) port->registered = false; kthread_destroy_worker(port->wq); - hrtimer_cancel(&port->send_discover_timer); + hrtimer_cancel(&port->vdm_discovery_timer); hrtimer_cancel(&port->enable_frs_timer); hrtimer_cancel(&port->vdm_state_machine_timer); hrtimer_cancel(&port->state_machine_timer); + power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); tcpm_reset_port(port); tcpm_port_unregister_pd(port); diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d5ee0af9058b..522f56742aa9 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -130,6 +130,7 @@ enum { TPS_MODE_BIST, TPS_MODE_DISC, TPS_MODE_PTCH, + TPS_MODE_APP1, }; static const char *const modes[] = { @@ -138,6 +139,7 @@ static const char *const modes[] = { [TPS_MODE_BIST] = "BIST", [TPS_MODE_DISC] = "DISC", [TPS_MODE_PTCH] = "PTCH", + [TPS_MODE_APP1] = "APP1", }; /* Unrecognized commands will be replaced with "!CMD" */ @@ -159,6 +161,7 @@ struct tipd_data { int (*init)(struct tps6598x *tps); int (*switch_power_state)(struct tps6598x *tps, u8 target_state); bool (*read_data_status)(struct tps6598x *tps); + bool (*read_power_status)(struct tps6598x *tps); int (*reset)(struct tps6598x *tps); int (*connect)(struct tps6598x *tps, u32 status); }; @@ -630,6 +633,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) return true; } +/* + * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported + * and the remaining bits are redundant with STATUS register (0x1A). + * Synthesize pwr_status from the already-read STATUS register. + */ +static bool tps66993_read_power_status(struct tps6598x *tps) +{ + u16 pwr_status = 0; + + /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ + if (tps->status & TPS_STATUS_PLUG_PRESENT) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); + + /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ + if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); + + if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, + TPS_POWER_STATUS_TYPEC_CURRENT_PD); + + tps->pwr_status = pwr_status; + + tps->data->trace_power_status(pwr_status); + + return true; +} + static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) { int ret; @@ -897,7 +928,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) goto err_unlock; if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_unlock; if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { if (tps6598x_read_partner_identity(tps)) { @@ -952,7 +983,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) goto err_clear_ints; if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_clear_ints; if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) @@ -1025,17 +1056,25 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) if (!tps6598x_read_status(tps, &status)) goto err_unlock; + tps->status = status; + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) if (!tps->data->read_data_status(tps)) goto err_unlock; - /* Handle plug insert or removal */ - if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) + /* + * Refresh power status before connect - needed for TPS66993 which + * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. + */ + if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { + if (!tps->data->read_power_status(tps)) + goto err_unlock; tps6598x_handle_plug_event(tps, status); + } err_unlock: mutex_unlock(&tps->lock); @@ -1071,6 +1110,7 @@ static int tps6598x_check_mode(struct tps6598x *tps) switch (ret) { case TPS_MODE_APP: + case TPS_MODE_APP1: case TPS_MODE_PTCH: return ret; case TPS_MODE_BOOT: @@ -1744,7 +1784,7 @@ static int tps6598x_probe(struct i2c_client *client) struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; - u32 vid; + u32 vid = 0; int ret; data = i2c_get_match_data(client); @@ -1772,8 +1812,11 @@ static int tps6598x_probe(struct i2c_client *client) if (!device_is_compatible(tps->dev, "ti,tps25750")) { ret = tps6598x_read32(tps, TPS_REG_VID, &vid); - if (ret < 0 || !vid) + if (ret < 0 || !vid) { + dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", + ret, vid); return -ENODEV; + } } /* @@ -1809,6 +1852,8 @@ static int tps6598x_probe(struct i2c_client *client) goto err_clear_mask; } + tps->status = status; + /* * This fwnode has a "compatible" property, but is never populated as a * struct device. Instead we simply parse it to read the properties. @@ -1836,7 +1881,7 @@ static int tps6598x_probe(struct i2c_client *client) if (status & TPS_STATUS_PLUG_PRESENT) { ret = -EINVAL; - if (!tps6598x_read_power_status(tps)) + if (!tps->data->read_power_status(tps)) goto err_unregister_port; if (!tps->data->read_data_status(tps)) goto err_unregister_port; @@ -1851,7 +1896,7 @@ static int tps6598x_probe(struct i2c_client *client) IRQF_SHARED | IRQF_ONESHOT, dev_name(&client->dev), tps); } else { - dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); + dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL)); @@ -1978,6 +2023,7 @@ static const struct tipd_data cd321x_data = { .trace_status = trace_tps6598x_status, .init = cd321x_init, .read_data_status = cd321x_read_data_status, + .read_power_status = tps6598x_read_power_status, .reset = cd321x_reset, .switch_power_state = cd321x_switch_power_state, .connect = cd321x_connect, @@ -1997,6 +2043,25 @@ static const struct tipd_data tps6598x_data = { .apply_patch = tps6598x_apply_patch, .init = tps6598x_init, .read_data_status = tps6598x_read_data_status, + .read_power_status = tps6598x_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, +}; + +static const struct tipd_data tps66993_data = { + .irq_handler = tps6598x_interrupt, + .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), + .register_port = tps6598x_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, + .read_power_status = tps66993_read_power_status, .reset = tps6598x_reset, .connect = tps6598x_connect, }; @@ -2015,12 +2080,14 @@ static const struct tipd_data tps25750_data = { .apply_patch = tps25750_apply_patch, .init = tps25750_init, .read_data_status = tps6598x_read_data_status, + .read_power_status = tps6598x_read_power_status, .reset = tps25750_reset, .connect = tps6598x_connect, }; static const struct of_device_id tps6598x_of_match[] = { { .compatible = "ti,tps6598x", &tps6598x_data}, + { .compatible = "ti,tps66993", &tps66993_data}, { .compatible = "apple,cd321x", &cd321x_data}, { .compatible = "ti,tps25750", &tps25750_data}, {} diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 03edbb77bbd6..d4140f4da5bb 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -142,9 +142,13 @@ #define TPS_SYSTEM_POWER_STATE_S4 0x04 #define TPS_SYSTEM_POWER_STATE_S5 0x05 -/* TPS_REG_POWER_STATUS bits */ -#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x)) -#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x)) +/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ +#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) +#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) +#define TPS_POWER_STATUS_CONNECTION(x) \ + TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) +#define TPS_POWER_STATUS_SOURCESINK(x) \ + TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) #define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) #define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index ff33a5e7c6b0..77a0dd75edd3 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) if (ucsi->debugfs->status) return ucsi->debugfs->status; - seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high, - ucsi->debugfs->response.low); + seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, + ucsi->debugfs->response.high, ucsi->debugfs->response.low); return 0; } DEFINE_SHOW_ATTRIBUTE(ucsi_resp); @@ -162,6 +162,7 @@ void ucsi_debugfs_unregister(struct ucsi *ucsi) debugfs_remove_recursive(ucsi->debugfs->dentry); kfree(ucsi->debugfs); + ucsi->debugfs = NULL; } void ucsi_debugfs_init(void) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 49f1c53721bb..bef3f9b71d71 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -91,7 +91,8 @@ int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, if (ret) goto out_clear_bit; - if (!wait_for_completion_timeout(&ucsi->complete, 5 * HZ)) + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) ret = -ETIMEDOUT; out_clear_bit: @@ -1101,8 +1102,8 @@ static int ucsi_register_plug(struct ucsi_connector *con) plug = typec_register_plug(con->cable, &desc); if (IS_ERR(plug)) { dev_err(con->ucsi->dev, - "con%d: failed to register plug (%ld)\n", con->num, - PTR_ERR(plug)); + "con%d: failed to register plug (%pe)\n", con->num, + plug); return PTR_ERR(plug); } @@ -1160,8 +1161,8 @@ static int ucsi_register_cable(struct ucsi_connector *con) cable = typec_register_cable(con->port, &desc); if (IS_ERR(cable)) { dev_err(con->ucsi->dev, - "con%d: failed to register cable (%ld)\n", con->num, - PTR_ERR(cable)); + "con%d: failed to register cable (%pe)\n", con->num, + cable); return PTR_ERR(cable); } @@ -1290,8 +1291,8 @@ static int ucsi_register_partner(struct ucsi_connector *con) partner = typec_register_partner(con->port, &desc); if (IS_ERR(partner)) { dev_err(con->ucsi->dev, - "con%d: failed to register partner (%ld)\n", con->num, - PTR_ERR(partner)); + "con%d: failed to register partner (%pe)\n", con->num, + partner); return PTR_ERR(partner); } @@ -2220,7 +2221,7 @@ static void ucsi_init_work(struct work_struct *work) return; } - queue_delayed_work(system_long_wq, &ucsi->work, + queue_delayed_work(system_dfl_long_wq, &ucsi->work, UCSI_ROLE_SWITCH_INTERVAL); } } @@ -2344,7 +2345,7 @@ int ucsi_register(struct ucsi *ucsi) UCSI_BCD_GET_MINOR(ucsi->version), UCSI_BCD_GET_SUBMINOR(ucsi->version)); - queue_delayed_work(system_long_wq, &ucsi->work, 0); + queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); ucsi_debugfs_register(ucsi); return 0; @@ -2366,6 +2367,8 @@ void ucsi_unregister(struct ucsi *ucsi) cancel_delayed_work_sync(&ucsi->work); cancel_work_sync(&ucsi->resume_work); + ucsi_debugfs_unregister(ucsi); + /* Disable notifications */ ucsi->ops->async_control(ucsi, cmd); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 6e1608d88ec3..dc594388dcd0 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -466,6 +466,7 @@ struct ucsi_debugfs_entry { struct ucsi_data { u64 low; u64 high; + u64 ext; } response; int status; u8 message_out[MESSAGE_OUT_MAX_LEN]; diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index ca1b534cb183..57b37fca150c 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -105,6 +105,7 @@ struct gaokun_ucsi { struct notifier_block nb; u16 version; u8 num_ports; + bool registered; }; /* -------------------------------------------------------------------------- */ @@ -482,8 +483,13 @@ static void gaokun_ucsi_register_worker(struct work_struct *work) } ret = ucsi_register(ucsi); - if (ret) + if (ret) { dev_err_probe(ucsi->dev, ret, "ucsi register failed\n"); + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + return; + } + + uec->registered = true; } static int gaokun_ucsi_probe(struct auxiliary_device *adev, @@ -528,8 +534,11 @@ static void gaokun_ucsi_remove(struct auxiliary_device *adev) int i; disable_delayed_work_sync(&uec->work); - gaokun_ec_unregister_notify(uec->ec, &uec->nb); - ucsi_unregister(uec->ucsi); + if (uec->registered) { + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + ucsi_unregister(uec->ucsi); + } + for (i = 0; i < uec->num_ports; ++i) typec_mux_put(uec->ports[i].typec_mux); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 557288c0274b..d48623fda79b 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) */ struct tb_service_driver { struct device_driver driver; - int (*probe)(struct tb_service *svc, const struct tb_service_id *id); + int (*probe)(struct tb_service *svc); void (*remove)(struct tb_service *svc); void (*shutdown)(struct tb_service *svc); const struct tb_service_id *id_table; @@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc); * @hop_count: Number of rings (end point hops) supported by NHI. * @quirks: NHI specific quirks if any * @domain_released: Completed when domain has been fully released + * @host_reset: Host router was reset on driver load, or forced on system + * shutdown/reboot. When set, tb_stop() asserts DPR on connected + * downstream ports to signal disconnect before tearing down the + * router tree. Only Thunderbolt 3 devices are reset; USB4 + * routers are skipped. */ struct tb_nhi { spinlock_t lock; @@ -528,6 +533,7 @@ struct tb_nhi { u32 hop_count; unsigned long quirks; struct completion domain_released; + bool host_reset; }; /** @@ -592,6 +598,8 @@ struct tb_ring { #define RING_FLAG_FRAME BIT(1) /* Enable end-to-end flow control */ #define RING_FLAG_E2E BIT(2) +/* Do not enable interrupt for the ring */ +#define RING_FLAG_NO_INTERRUPT BIT(3) struct ring_frame; typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/include/linux/usb.h b/include/linux/usb.h index 1da4ad1610bc..49ab8dbb885f 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1185,7 +1185,8 @@ extern ssize_t usb_show_dynids(struct usb_dynids *dynids, char *buf); * interface. It may also use usb_set_interface() to specify the * appropriate altsetting. If unwilling to manage the interface, * return -ENODEV, if genuine IO errors occurred, an appropriate - * negative errno value. + * negative errno value. The usb_device_id parameter is only valid during + * probe. * @disconnect: Called when the interface is no longer accessible, usually * because its device has been (or is being) disconnected or the * driver module is being unloaded. diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 337a5485af7c..914e78f8acfd 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -106,6 +106,9 @@ enum pd_ext_msg_type { #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) +#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1))) + static inline unsigned int pd_header_cnt(u16 header) { return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; @@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size) return ((size / 4) + (size % 4 ? 1 : 0)); } +/** + * batt_cap_ext_msg - Battery capability extended PD message + * @vid: Battery Vendor ID (assigned by USB-IF) + * @pid: Battery Product ID (assigned by battery or device vendor) + * @batt_design_cap: Battery design capacity in 0.1Wh + * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh + * @batt_type: Battery Type. bit0 when set indicates invalid battery reference. + * Rest of the bits are reserved. + */ +struct batt_cap_ext_msg { + __le16 vid; + __le16 pid; + __le16 batt_design_cap; + __le16 batt_last_chg_cap; + u8 batt_type; +} __packed; + +#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0) + /* Sink Caps Extended Data Block Version */ #define SKEDB_VER_1_0 1 @@ -493,7 +515,7 @@ static inline unsigned int pdo_epr_avs_apdo_min_voltage_mv(u32 pdo) static inline unsigned int pdo_epr_avs_apdo_max_voltage_mv(u32 pdo) { - return FIELD_GET(PDO_EPR_AVS_APDO_MIN_VOLT, pdo) * 100; + return FIELD_GET(PDO_EPR_AVS_APDO_MAX_VOLT, pdo) * 100; } static inline unsigned int pdo_epr_avs_apdo_pdp_w(u32 pdo) @@ -724,4 +746,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi #endif /* CONFIG_TYPEC */ +/* Battery Status Data Object */ +#define BSDO_PRESENT_CAPACITY GENMASK(31, 16) +#define BSDO_CHG_STATUS GENMASK(11, 10) +#define BSDO_BATTERY_PRESENT BIT(9) +#define BSDO_INVALID_BATTERY_REFERENCE BIT(8) + +/* + * Battery Charge Status: Battery Charging Status Values as defined in + * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)". + */ +#define BSDO_BATTERY_INFO_CHARGING 0x0 +#define BSDO_BATTERY_INFO_DISCHARGING 0x1 +#define BSDO_BATTERY_INFO_IDLE 0x2 +#define BSDO_BATTERY_INFO_RSVD 0x3 + +/** + * BSDO() - Pack data into Battery Status Data Object format. + * @batt_charge: Battery's present state of charge in 0.1WH increment. + * @chg_status: Battery charge status. + * @batt_present: Indicates that battery is present/attached when set else absent when unset. + * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status + * request. + */ +#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \ + ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \ + (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \ + ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \ + ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0)) + #endif /* __LINUX_USB_PD_H */ diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h index f7f5cfbdef12..9b46a6bc762c 100644 --- a/include/linux/usb/tcpci.h +++ b/include/linux/usb/tcpci.h @@ -144,6 +144,7 @@ #define TCPC_RX_BUF_FRAME_TYPE 0x31 #define TCPC_RX_BUF_FRAME_TYPE_SOP 0 #define TCPC_RX_BUF_FRAME_TYPE_SOP1 1 +#define TCPC_RX_BUF_FRAME_TYPE_MASK GENMASK(2, 0) #define TCPC_RX_HDR 0x32 #define TCPC_RX_DATA 0x34 /* through 0x4f */ diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index d61ec38216fa..10a783b738ef 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); struct typec_cable *typec_cable_get(struct typec_port *port); void typec_cable_put(struct typec_cable *cable); int typec_cable_is_active(struct typec_cable *cable); +bool typec_cable_altmode_unsupported(struct typec_altmode *alt); struct typec_plug *typec_register_plug(struct typec_cable *cable, struct typec_plug_desc *desc); diff --git a/include/target/target_core_fabric.h b/include/target/target_core_fabric.h index e9039e73d058..390ace5bb252 100644 --- a/include/target/target_core_fabric.h +++ b/include/target/target_core_fabric.h @@ -95,6 +95,8 @@ struct target_core_fabric_ops { struct se_lun *); void (*fabric_pre_unlink)(struct se_portal_group *, struct se_lun *); + void (*fabric_post_unlink)(struct se_portal_group *se_tpg, + struct se_lun *lun); struct se_tpg_np *(*fabric_make_np)(struct se_portal_group *, struct config_group *, const char *); void (*fabric_drop_np)(struct se_tpg_np *); diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index beef1752e36e..290308c9dd92 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -25,6 +25,7 @@ enum functionfs_flags { FUNCTIONFS_EVENTFD = 32, FUNCTIONFS_ALL_CTRL_RECIP = 64, FUNCTIONFS_CONFIG0_SETUP = 128, + FUNCTIONFS_RW_PROXY_EPS = 256, }; /* Descriptor of an non-audio endpoint */ @@ -414,4 +415,27 @@ struct usb_functionfs_event { #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ struct usb_ffs_dmabuf_transfer_req) +/* + * Enable or disable automatic zero-length packet (ZLP) appending for the + * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to + * enable. + * + * When enabled, the kernel will automatically append a ZLP at the end of + * a transfer if the payload length is an exact multiple of the endpoint's + * max packet size. + * + * This is useful for compatibility with legacy protocols which require + * automatic ZLP appending to data written from userspace. + * + * This ioctl can only be used on IN endpoints. It can be called at any time + * after the FunctionFS instance is active, even before the host has connected + * or enabled the endpoint. + * + * Returns zero on success, or a negative errno value on error: + * -ENODEV: The FunctionFS instance is not active. + * -EINVAL: The endpoint is not an IN endpoint. + * -EFAULT: Invalid user space pointer for the argument. + */ +#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 558e15a08478..993760ff332b 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -24,11 +24,8 @@ }; use core::{ marker::PhantomData, - mem::{ - offset_of, - MaybeUninit, // - }, - ptr::NonNull, + mem::offset_of, + ptr::NonNull, // }; /// An adapter for the registration of USB drivers. @@ -133,8 +130,7 @@ pub const fn from_id(vendor: u16, product: u16) -> Self { match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, idVendor: vendor, idProduct: product, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -146,8 +142,7 @@ pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16 idProduct: product, bcdDevice_lo: bcd_lo, bcdDevice_hi: bcd_hi, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -158,8 +153,7 @@ pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { bDeviceClass: class, bDeviceSubClass: subclass, bDeviceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -170,8 +164,7 @@ pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self bInterfaceClass: class, bInterfaceSubClass: subclass, bInterfaceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -183,8 +176,7 @@ pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) - idVendor: vendor, idProduct: product, bInterfaceClass: class, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -196,8 +188,7 @@ pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: idVendor: vendor, idProduct: product, bInterfaceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -209,8 +200,7 @@ pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) idVendor: vendor, idProduct: product, bInterfaceNumber: number, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } @@ -230,8 +220,7 @@ pub const fn from_device_and_interface_info( bInterfaceClass: class, bInterfaceSubClass: subclass, bInterfaceProtocol: protocol, - // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }) } } @@ -385,6 +374,7 @@ fn as_ref(&self) -> &Device { // SAFETY: Instances of `Interface` are always reference-counted. unsafe impl AlwaysRefCounted for Interface { + #[inline] fn inc_ref(&self) { // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` // returns a valid `struct usb_interface` pointer, for which we will @@ -392,6 +382,7 @@ fn inc_ref(&self) { unsafe { bindings::usb_get_intf(self.as_raw()) }; } + #[inline] unsafe fn dec_ref(obj: NonNull) { // SAFETY: The safety requirements guarantee that the refcount is non-zero. unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } @@ -436,6 +427,7 @@ fn as_raw(&self) -> *mut bindings::usb_device { // SAFETY: Instances of `Device` are always reference-counted. unsafe impl AlwaysRefCounted for Device { + #[inline] fn inc_ref(&self) { // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` // returns a valid `struct usb_device` pointer, for which we will @@ -443,6 +435,7 @@ fn inc_ref(&self) { unsafe { bindings::usb_get_dev(self.as_raw()) }; } + #[inline] unsafe fn dec_ref(obj: NonNull) { // SAFETY: The safety requirements guarantee that the refcount is non-zero. unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) }