diff options
194 files changed, 6914 insertions, 2037 deletions
diff --git a/Documentation/ABI/testing/configfs-thunderbolt_stream b/Documentation/ABI/testing/configfs-thunderbolt_stream new file mode 100644 index 000000000000..7abc6b73a1e4 --- /dev/null +++ b/Documentation/ABI/testing/configfs-thunderbolt_stream @@ -0,0 +1,83 @@ +What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service> +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + Configuration group for a stream Thunderbolt/USB4 + service. It is possible to create groups even if there + is no connection yet to the other host. Once a + connection established and there is stream service on + the remote side that matches, this configuration is + applied to it. + + To find the service name you can run tblist from tbtools [1]: + + # tblist -A + ... + Domain 0 Route 3 Index 0: stream + + [1] https://github.com/intel/tbtools + +What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + Creates new stream with $name and fills it with the + 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/<xdomain>.<service>/$name/index +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + This matches the X in /dev/tbstreamX and allows userspace + to map the configfs directory to the corresponding character + device. + +What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/in_hopid +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + In HopID used with the read path of the tunnel. Available HopIDs + for tunneling start from 8. You can pass also -1 for automatic + allocation. The allocated value can be read here. Writing 0 will + de-allocate if the stream is not in use. + + To figure out the maximum HopID you can run tbget from + tbtools for the lane adapter. For example below we check + for lane adapter number 1 (first USB4 port): + + # tbget -r 0 -a 1 -D ADP_CS_5.Max\ Input\ HopID + 19 + + This allows to use anything between 8 and 19 inclusive. + +What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/out_hopid +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + Out HopID used with the write path of the tunnel. Available HopIDs + for tunneling start from 8. You can pass also -1 for automatic + allocation. The allocated value can be read here. Writing 0 will + de-allocate if the stream is not in use. See @in_hopid + for how to figure out the maximum HopID. + +What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/ring_size +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + Size of the TX/RX rings. Can be adjusted between 32 and + 4096. The default is 256. + +What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/throttling +Date: Sep 2026 +KernelVersion: v7.2 +Contact: Mika Westerberg <mika.westerberg@linux.intel.com> +Description: + Interrupt throttling rate in ns. Lower values can give + better latency. The default is 8192 ns. diff --git a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd index d10e6de3adb2..98a8376a83d2 100644 --- a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd +++ b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd @@ -22,7 +22,7 @@ Description: Reading this attribute gives the state of the DbC. It can be one of the following states: disabled, enabled, - initialized, connected, configured and stalled. + initialized, connected or configured. What: /sys/bus/pci/drivers/xhci_hcd/.../dbc_idVendor Date: March 2023 diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 89df26553aa0..91a6cb109988 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -373,6 +373,67 @@ port which are named like ``thunderbolt0`` and so on. From this point you can either use standard userspace tools like ``ip`` to configure the interface or let your GUI handle it automatically. +Streaming data directly over Thunderbolt cable +---------------------------------------------- +In addition to Thunderbolt networking (aka. USB4NET) Linux supports +streaming data directly over a cable as well (aka. USB4STREAM). This is +possible through ``thunderbolt-stream`` driver. + +Similarly to ``thunderbolt-net`` you load the driver first on one end:: + + host1 # modprobe thunderbolt-stream + +Then you configure it via ``ConfigFS``:: + + host1 # cd /sys/kernel/config/thunderbolt/stream + host1 # mkdir -p 0-1.0/data + host1 # cd 0-1.0 + host1 # echo -1 > data/in_hopid + host1 # echo -1 > data/out_hopid + +This information is automatically announced to the other side via +XDomain properties so if you have cable connected the other side knows +that there is a stream named ``data`` available and can configure it for +you automatically:: + + host2 # cd /sys/kernel/config/thunderbolt/stream + host2 # mkdir -p 0-3.0/data + +Here we used auto-configuration but you can configure it manually too. +In that case you need to fill ``in_hopid`` and ``out_hopid`` accordingly. +If you set them to ``-1`` the next available HopID is used which is +typically what we want. + +Once they are configured you can use ``/dev/tbstreamX`` on both sides to +transfer data:: + + host2 # cat /dev/tbstream0 + host1 # dmesg > /dev/tbstream0 + +Once you are done with the stream you can remove them:: + + host2 # cd /sys/kernel/config/thunderbolt/stream + host2 # rmdir -p 0-1.0/data + host1 # cd /sys/kernel/config/thunderbolt/stream + host1 # rmdir -p 0-3.0/data + +Since streams are essentially files you can use any existing application +that supports ``read(2)`` and ``write(2)`` in some form. + +It is possible to have more than one stream and you can have both stream +and ``thunderbolt-net`` in use simultaneously. For example we can create +two streams with name ``control`` and ``data`` like this:: + + host1 # cd /sys/kernel/config/thunderbolt/stream + host1 # mkdir 0-1.0 + host1 # cd 0-1.0 + host1 # mkdir control + host1 # mkdir data + +Then you have ``/dev/tbstream0`` for ``control`` and ``/dev/tbstream1`` +for ``data``. Before you can use them you need to configure them as +shown above for the one stream case. + Forcing power ------------- Many OEMs include a method that can be used to force the power of a diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index a199e5ba6416..e8082c5c05a2 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -4,29 +4,37 @@ $id: http://devicetree.org/schemas/usb/cdns,usb3.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Cadence USBSS-DRD controller +title: Cadence USBSS and USBSSP DRD controller maintainers: - Pawel Laszczak <pawell@cadence.com> +description: + Cadence USB dual-role controller. Covers USBSS (SuperSpeed, USB 3.0) and + USBSSP (SuperSpeed Plus, USB 3.1 gen2x1). Both variants share the same + DRD/OTG register interface, so the driver auto-detects the controller + version at runtime. + properties: compatible: - const: cdns,usb3 + oneOf: + - const: cdns,usb3 + - items: + - {} + - const: cdns,cdnsp reg: - items: - - description: OTG controller registers - - description: XHCI Host controller registers - - description: DEVICE controller registers + minItems: 2 + maxItems: 3 reg-names: + minItems: 2 + maxItems: 3 items: - - const: otg - - const: xhci - - const: dev + enum: [ otg, xhci, dev ] interrupts: - minItems: 3 + minItems: 2 items: - description: XHCI host controller interrupt - description: Device controller interrupt @@ -35,7 +43,7 @@ properties: cleared by xhci core, this interrupt is optional interrupt-names: - minItems: 3 + minItems: 2 items: - const: host - const: peripheral @@ -49,7 +57,7 @@ properties: cdns3 to type C connector. maximum-speed: - enum: [super-speed, high-speed, full-speed] + enum: [super-speed-plus, super-speed, high-speed, full-speed] phys: minItems: 1 @@ -87,6 +95,47 @@ allOf: - $ref: usb-drd.yaml# - $ref: usb-xhci.yaml# + - if: + properties: + compatible: + contains: + const: cdns,cdnsp + then: + properties: + reg: + items: + - description: XHCI Host controller registers + - description: DEVICE controller registers + reg-names: + items: + - const: xhci + - const: dev + interrupts: + maxItems: 2 + interrupt-names: + items: + - const: host + - const: peripheral + dr_mode: + enum: [host, peripheral] + else: + properties: + reg: + items: + - description: OTG controller registers + - description: XHCI Host controller registers + - description: DEVICE controller registers + reg-names: + items: + - const: otg + - const: xhci + - const: dev + interrupts: + minItems: 3 + maxItems: 4 + interrupt-names: + minItems: 3 + unevaluatedProperties: false examples: diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml index fec04702f530..620b564914d4 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml @@ -45,11 +45,11 @@ properties: clocks: minItems: 1 - maxItems: 2 + maxItems: 3 clock-names: minItems: 1 - maxItems: 2 + maxItems: 3 operating-points-v2: description: A phandle to the OPP table containing the performance states. @@ -92,6 +92,27 @@ allOf: - $ref: chipidea,usb2-common.yaml# - $ref: usb-hcd.yaml# - $ref: usb-drd.yaml# + - if: + properties: + compatible: + contains: + const: qcom,ci-hdrc + then: + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + items: + - const: iface + - const: core + - const: fs + else: + properties: + clocks: + maxItems: 2 + clock-names: + maxItems: 2 unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml b/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml index 9a94b2a74a1e..e8b8c03f87a0 100644 --- a/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml +++ b/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml @@ -15,6 +15,7 @@ properties: - usb5e3,608 - usb5e3,610 - usb5e3,620 + - usb5e3,625 - usb5e3,626 reg: true @@ -26,6 +27,10 @@ properties: description: The regulator that provides 3.3V or 5.0V core power to the hub. + vdd12-supply: + description: + The regulator that provides 1.2V power to the hub. + peer-hub: true ports: @@ -56,6 +61,7 @@ allOf: properties: peer-hub: false vdd-supply: false + vdd12-supply: false - if: properties: @@ -68,6 +74,18 @@ allOf: properties: peer-hub: true vdd-supply: true + vdd12-supply: false + + - if: + properties: + compatible: + contains: + enum: + - usb5e3,625 + then: + properties: + peer-hub: true + vdd-supply: true unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml index 6d3fa2bc9cee..ba790c4488b7 100644 --- a/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml @@ -79,22 +79,17 @@ properties: - const: fr-swap vdd-vbus-supply: - description: VBUS power supply. + deprecated: true + description: use connector/vbus-supply instead. vdd-pdphy-supply: description: VDD regulator supply to the PDPHY. - port: - $ref: /schemas/graph.yaml#/properties/port - description: - Contains a port which produces data-role switching messages. - required: - compatible - reg - interrupts - interrupt-names - - vdd-vbus-supply allOf: - if: diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt b/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt deleted file mode 100644 index 6f8115db2ea9..000000000000 --- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt +++ /dev/null @@ -1,44 +0,0 @@ -Richtek RT1711H TypeC PD Controller. - -Required properties: - - compatible : Must be "richtek,rt1711h". - - reg : Must be 0x4e, it's slave address of RT1711H. - - interrupts : <a b> where a is the interrupt number and b represents an - encoding of the sense and level information for the interrupt. - -Required sub-node: -- connector: The "usb-c-connector" attached to the tcpci chip, the bindings - of connector node are specified in - Documentation/devicetree/bindings/connector/usb-connector.yaml - -Example : -rt1711h@4e { - compatible = "richtek,rt1711h"; - reg = <0x4e>; - interrupt-parent = <&gpio26>; - interrupts = <0 IRQ_TYPE_LEVEL_LOW>; - - usb_con: connector { - compatible = "usb-c-connector"; - label = "USB-C"; - data-role = "dual"; - power-role = "dual"; - try-power-role = "sink"; - source-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)>; - sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM) - PDO_VAR(5000, 12000, 2000)>; - op-sink-microwatt = <10000000>; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@1 { - reg = <1>; - usb_con_ss: endpoint { - remote-endpoint = <&usb3_data_ss>; - }; - }; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml index fd1b13c0ed6b..0554dbc4b854 100644 --- a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml @@ -26,6 +26,7 @@ select: contains: enum: - rockchip,rk3328-dwc3 + - rockchip,rk3528-dwc3 - rockchip,rk3562-dwc3 - rockchip,rk3568-dwc3 - rockchip,rk3576-dwc3 @@ -38,6 +39,7 @@ properties: items: - enum: - rockchip,rk3328-dwc3 + - rockchip,rk3528-dwc3 - rockchip,rk3562-dwc3 - rockchip,rk3568-dwc3 - rockchip,rk3576-dwc3 @@ -135,6 +137,7 @@ allOf: compatible: contains: enum: + - rockchip,rk3528-dwc3 - rockchip,rk3568-dwc3 - rockchip,rk3576-dwc3 then: diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 4aa910569c31..29130df44d12 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -225,6 +225,7 @@ Hardware Monitoring Kernel Drivers pmbus powerz powr1220 + prom21-xhci pt5161l pxe1610 pwm-fan diff --git a/Documentation/hwmon/prom21-xhci.rst b/Documentation/hwmon/prom21-xhci.rst new file mode 100644 index 000000000000..7984fb187bd8 --- /dev/null +++ b/Documentation/hwmon/prom21-xhci.rst @@ -0,0 +1,101 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver prom21-xhci +========================= + +Supported chips: + + * AMD Promontory 21 (PROM21) xHCI USB host controller + + Prefix: 'prom21_xhci' + + PCI IDs: 1022:43fc, 1022:43fd + +Author: + + - Jihong Min <hurryman2212@gmail.com> + +Description +----------- + +This driver exposes the temperature sensor in AMD PROM21 xHCI controllers. + +The driver binds to an auxiliary device created by the xHCI PCI driver for +supported controllers. The sensor value is accessed through a vendor-specific +index/data register pair in the controller's PCI MMIO BAR. +The auxiliary device is created by the ``xhci-pci-prom21`` PCI glue driver. +USB host operation is otherwise delegated to the common ``xhci-pci`` code. + +PROM21 is an AMD chipset IP used in single-chip or daisy-chained configurations +to build AMD 6xx/8xx series chipsets. Since the xHCI controllers are +integrated in PROM21, this temperature can also be used as a monitor for a +temperature close to the AMD chipset temperature. + +Register access +--------------- + +The temperature value is read through a vendor-specific index/data register +pair in the xHCI PCI MMIO BAR. The driver uses the following byte offsets from +the MMIO BAR base: + +======================= ===================================================== +0x3000 Vendor index register +0x3008 Vendor data register +======================= ===================================================== + +The driver saves the current vendor index register value, writes the +temperature selector ``0x0001e520`` to the vendor index register, reads the +vendor data register, and restores the previous vendor index value before +returning. The raw temperature value is the low 8 bits of the vendor data +register value. + +The hwmon core serializes this driver's callbacks, and the driver restores the +previous index value after each read. This does not provide synchronization +with firmware, SMM, ACPI AML, or any other user outside this driver. + +No public AMD reference is available for the register pair or the raw value. +The register pair was identified on an X870E system with two PROM21 xHCI +controllers. One controller was passed through to a Windows VM, and the same +controller's PCI MMIO BAR was observed from the Linux host while HWiNFO64 was +reporting the PROM21 xHCI temperature. In the test environment, the reported +temperature was very stable at idle and the displayed sensor resolution was +low, which made it possible to look for a consistently repeating MMIO response +for the same reported temperature. During observation, offset 0x3000 repeatedly +contained selector ``0x0001e520``. Writing the same selector to offset 0x3000 +from Linux and then reading offset 0x3008 reproduced the same raw value, so the +offsets are treated as a vendor index/data register pair. + +The conversion formula was empirically inferred by matching observed raw +8-bit values against HWiNFO64's reported PROM21 xHCI temperature for the same +controller. The observed mapping is: + + temp[C] = raw * 0.9066 - 78.624 + +Runtime PM +---------- + +The driver does not wake the xHCI PCI device for hwmon reads. It reads the +temperature only when the parent device is already active. A read from a +suspended device returns ``-ENODATA``. After a successful read, the driver +drops its active-only runtime PM reference and lets the PM core re-evaluate the +idle state. + +Sysfs entries +------------- + +======================= ===================================================== +temp1_input Temperature in millidegrees Celsius +======================= ===================================================== + +The hwmon device name is ``prom21_xhci``. The sysfs path depends on the hwmon +device number assigned by the kernel. Userspace can locate the device by +matching the ``name`` attribute: + +.. code-block:: sh + + for hwmon in /sys/class/hwmon/hwmon*; do + [ "$(cat "$hwmon/name")" = "prom21_xhci" ] || continue + cat "$hwmon/temp1_input" + done + +If the raw register value is invalid, ``temp1_input`` returns ``-ENODATA``. diff --git a/Documentation/translations/zh_CN/subsystem-apis.rst b/Documentation/translations/zh_CN/subsystem-apis.rst index 830217140fb6..b52e1feb0167 100644 --- a/Documentation/translations/zh_CN/subsystem-apis.rst +++ b/Documentation/translations/zh_CN/subsystem-apis.rst @@ -90,6 +90,7 @@ TODOList: security/index PCI/index peci/index + usb/index TODOList: @@ -104,6 +105,5 @@ TODOList: * accel/index * crypto/index * bpf/index -* usb/index * misc-devices/index * wmi/index diff --git a/Documentation/translations/zh_CN/usb/CREDITS b/Documentation/translations/zh_CN/usb/CREDITS new file mode 100644 index 000000000000..c133b1a5daff --- /dev/null +++ b/Documentation/translations/zh_CN/usb/CREDITS @@ -0,0 +1,163 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/CREDITS + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +简易 Linux USB 驱动的致谢名单: + +以下人员都为 Linux USB 驱动代码作出了贡献 +(按姓氏字母顺序排列)。我相信这份名单本应 +更长一些,但确实不容易维护。 +如需将自己加入名单,请提交补丁。 + + Georg Acher <acher@informatik.tu-muenchen.de> + David Brownell <dbrownell@users.sourceforge.net> + Alan Cox <alan@lxorguk.ukuu.org.uk> + Randy Dunlap <randy.dunlap@intel.com> + Johannes Erdfelt <johannes@erdfelt.com> + Deti Fliegl <deti@fliegl.de> + ham <ham@unsuave.com> + Bradley M Keryan <keryan@andrew.cmu.edu> + Greg Kroah-Hartman <greg@kroah.com> + Pavel Machek <pavel@suse.cz> + Paul Mackerras <paulus@cs.anu.edu.au> + Petko Manlolov <petkan@dce.bg> + David E. Nelson <dnelson@jump.net> + Vojtech Pavlik <vojtech@suse.cz> + Bill Ryder <bryder@sgi.com> + Thomas Sailer <sailer@ife.ee.ethz.ch> + Gregory P. Smith <greg@electricrain.com> + Linus Torvalds <torvalds@linux-foundation.org> + Roman Weissgaerber <weissg@vienna.at> + <Kazuki.Yasumatsu@fujixerox.co.jp> + +特别感谢: + + Inaky Perez Gonzalez <inaky@peloncho.fis.ucm.es> + 感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd + 驱动中的大部分代码。我们从那项工作中学到了很多。 + + NetBSD 和 FreeBSD 的 USB 开发者们 + 感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。 + +附加感谢: + 还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助 + (摘自 Inaky 驱动原始的 THANKS 文件): + + 以下公司曾帮助我们开发 Linux USB / UUSBD: + + - 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 实现。 + + - 由于他们对我们的支持,Keytronic 可以放心, + 他们的键盘能卖给至少 300 万 Linux 用户中的一部分。 + + - ing büro h doran [http://www.ibhdoran.com]! + 在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的 + (我自己做的那个相当糟糕 :))。现在我知道该去哪里买漂亮的 USB + 配件了! + + - Genius Germany 捐赠了一只 USB 鼠标,用于测试鼠标启动协议; + 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢! + + - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB 驱动。 + AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux 在内的 + 所有操作系统平台开放。 + + - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱, + 使我们能够测试批量传输代码。 + + - 感谢 Logitech 捐赠了一只三轴 USB 鼠标。 + + Logitech 负责设计、制造并销售各种人机接口设备, + 在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的 + 控制设备方面拥有悠久历史和丰富经验。 + + 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、 + 摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户 + 能在自己喜欢的操作系统上获得支持,并让所有 Linux 用户都能使用 + Logitech 以及其他 USB 硬件。 + + Logitech 也是 1999 年 2 月 11 日维也纳 Linux 大会的官方赞助商, + 我们将在会上展示 Linux USB 工作的最新进展。 + + - 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI 内部实现中 + 那些不为人知的角落。 + + - 感谢 Entrega 为开发工作提供 PCI 转 USB 卡、集线器和转换器产品。 + + - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档, + 让这个驱动得以写成。 + + - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明, + 以及驱动开发过程中的宝贵建议。 + + 另外还要感谢以下个人(嘿,顺序不分先后 :)) + + - Oren Tirosh <orenti@hishome.net>, + 他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。 + + - Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>, + 指出了致命 bug,并给出了宝贵建议。 + + - Edmund Humemberger <ed@atnet.at>,他在公共关系与项目管理方面 + 为 Linux-USB 项目付出了巨大的努力。 + + - Alberto Menegazzi <flash@flash.iol.it> 正在着手编写 UUSBD 文档,加油! + + - Ric Klaren <ia_ric@cs.utwente.nl> 编写了很好的入门文档, + 与 Alberto 的作品形成良性竞争:)。 + + - Christian Groessler <cpg@aladdin.de>,感谢他在那些棘手细节上的帮助。 + + - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持, + 并提供了大量的改进意见。 + + - Fernando Herrera <fherrera@eurielec.etsit.upm.es> + 负责撰写、维护并不断补充那份期待已久、独一无二又精彩的 + UUSBD FAQ!太棒了! + + - Rasca Gmelch <thron@gmx.de> 重新启用了 raw 驱动, + 指出了一些错误,并启动了 uusbd-utils 软件包。 + + - Peter Dettori <dettori@ozy.dec.com>,像疯了一样挖掘 bug, + 还提出了很多很酷的建议,太棒了! + + - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、 + MIT X 联盟、TeX 社区等等,谢谢你们! + + - 特别感谢 Richard Stallman 创造了 Emacs! + + - 感谢 linux-usb 邮件列表的所有成员,读了那么多邮件——不开玩笑了, + 感谢你们提出的所有建议! + + - 感谢 USB Implementers Forum 成员们的帮助与支持。 + + - Nathan Myers <ncm@cantrip.org>,感谢他的建议! + (希望你喜欢 Cibeles 的派对。) + + - 感谢 Linus Torvalds 创建、开发并管理 Linux。 + + - Mike Smith、Craig Keithley、Thierry Giron 和 Janet Schank + 感谢他们让我认识到标准 USB 集线器其实也没那么“标准”, + 这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。 diff --git a/Documentation/translations/zh_CN/usb/acm.rst b/Documentation/translations/zh_CN/usb/acm.rst new file mode 100644 index 000000000000..51d6eb8f5660 --- /dev/null +++ b/Documentation/translations/zh_CN/usb/acm.rst @@ -0,0 +1,147 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/acm.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +==================== +Linux ACM 驱动 v0.16 +==================== + +版权所有 (c) 1999 Vojtech Pavlik <vojtech@suse.cz> + +由 SuSE 赞助 + +0. 免责声明 +~~~~~~~~~~~ +本程序是自由软件;你可以在自由软件基金会发布的 +GNU 通用公共许可证第 2 版,或者(按你的选择) +任何后续版本的条款下重新发布和/或修改它。 + +发布本程序是希望它能发挥作用,但它不附带任何担保; +甚至不包括对适销性或特定用途适用性的默示担保。 +详情见 GNU 通用公共许可证。 + +你应该已经随本程序收到了 GNU 通用公共许可证的副本; +如果没有,请致信:Free Software Foundation, Inc., 59 +Temple Place, Suite 330, Boston, MA 02111-1307 USA。 + +如需联系作者,可发送电子邮件至 vojtech@suse.cz, +或邮寄至: +Vojtech Pavlik, Ucitelska 1576, Prague 8, +182 00, Czech Republic。 + +为方便起见,软件包中已附带 GNU 通用公共许可证 +第 2 版:见 COPYING 文件。 + +1. 使用方法 +~~~~~~~~~~~ +``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB +通信设备类抽象控制模型(USB CDC ACM)规范的 +USB 调制解调器和 USB ISDN 终端适配器。 + +许多调制解调器支持此驱动,以下是我所知道的一些型号: + + - 3Com OfficeConnect 56k + - 3Com Voice FaxModem Pro + - 3Com Sportster + - MultiTech MultiModem 56k + - Zoom 2986L FaxModem + - Compaq 56k FaxModem + - ELSA Microlink 56k + +我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用: + + - 3Com USR ISDN Pro TA + +一些手机也可以通过 USB 连接。 +我知道以下机型可以正常工作: + + - SonyEricsson K800i + +遗憾的是,许多调制解调器和大多数 ISDN TA +都使用专有接口,因此无法与此驱动配合工作。 +购买前请先确认设备是否符合 ACM 规范。 + +要使用这些调制解调器,需要加载以下模块:: + + usbcore.ko + uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko + cdc-acm.ko + +之后就应该可以访问这些调制解调器了。 +应当可以使用 ``minicom``、``ppp`` 和 ``mgetty`` +与它们通信。 + +2. 验证驱动是否正常工作 +~~~~~~~~~~~~~~~~~~~~~~~ + +第一步是检查 ``/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 + D: Ver= 1.00 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1 + P: Vendor=0000 ProdID=0000 Rev= 0.00 + S: Product=USB UHCI Root Hub + S: SerialNumber=6800 + C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr= 0mA + I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub + E: Ad=81(I) Atr=03(Int.) MxPS= 8 Ivl=255ms + T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=12 MxCh= 0 + D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2 + P: Vendor=04c1 ProdID=008f Rev= 2.07 + S: Manufacturer=3Com Inc. + S: Product=3Com U.S. Robotics Pro ISDN TA + S: SerialNumber=UFT53A49BVT7 + C: #Ifs= 1 Cfg#= 1 Atr=60 MxPwr= 0mA + I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=acm + E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms + E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms + E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms + C:* #Ifs= 2 Cfg#= 2 Atr=60 MxPwr= 0mA + I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm + E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms + I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm + 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.)``,那就无能为力了: +这说明你手上的设备使用的是厂商专有接口:: + + 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 + I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm + +在系统日志中应该可以看到:: + + usb.c: USB new device connect, assigned device number 2 + usb.c: kmalloc IF c7691fa0, numif 1 + usb.c: kmalloc IF c7b5f3e0, numif 2 + usb.c: skipped 4 class/vendor specific interface descriptors + usb.c: new device strings: Mfr=1, Product=2, SerialNumber=3 + usb.c: USB device number 2 default language ID 0x409 + Manufacturer: 3Com Inc. + Product: 3Com U.S. Robotics Pro ISDN TA + SerialNumber: UFT53A49BVT7 + acm.c: probing config 1 + acm.c: probing config 2 + ttyACM0: USB ACM device + acm.c: acm_control_msg: rq: 0x22 val: 0x0 len: 0x0 result: 0 + acm.c: acm_control_msg: rq: 0x20 val: 0x0 len: 0x7 result: 7 + usb.c: acm driver claimed interface c7b5f3e0 + usb.c: acm driver claimed interface c7b5f3f8 + usb.c: acm driver claimed interface c7691fa0 + +如果以上都正常,请启动 ``minicom``, +把它配置为连接 ``ttyACM`` 设备,然后 +尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。 diff --git a/Documentation/translations/zh_CN/usb/authorization.rst b/Documentation/translations/zh_CN/usb/authorization.rst new file mode 100644 index 000000000000..2aa311f6b967 --- /dev/null +++ b/Documentation/translations/zh_CN/usb/authorization.rst @@ -0,0 +1,139 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/authorization.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +============================= +授权或禁止 USB 设备连接到系统 +============================= + +版权 (C) 2007 Inaky Perez-Gonzalez +<inaky@linux.intel.com> 英特尔公司 + +此功能允许你控制 USB 设备是否可以在系统中使用。 +借助它,你可以完全通过用户空间实现对 USB 设备的锁定。 + +目前,当插入一个 USB 设备时,系统会对其进行配置, +其接口会立即向用户开放。 +有了这项改动,只有在 root 授权设备完成配置后, +设备才可被使用。 + + +用法 +==== + +授权设备接入:: + + $ echo 1 > /sys/bus/usb/devices/DEVICE/authorized + +取消对设备的授权:: + + $ echo 0 > /sys/bus/usb/devices/DEVICE/authorized + +将新连接到 ``hostX`` 的设备默认设为未授权(即锁定):: + + $ echo 0 > /sys/bus/usb/devices/usbX/authorized_default + +解除锁定:: + + $ echo 1 > /sys/bus/usb/devices/usbX/authorized_default + +默认情况下,所有 USB 设备都是授权的。 +向 ``authorized_default`` 属性写入 ``2`` 会使内核 +默认只授权连接到内部 USB 端口的设备。 + +系统锁定示例(比较粗糙) +------------------------ + +假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入 +(例如某台带有外露 USB 端口的自助服务终端):: + + 启动系统 + rc.local -> + + for host in /sys/bus/usb/devices/usb* + do + echo 0 > $host/authorized_default + done + +给 udev 挂一个脚本,用于处理新插入的 USB 设备:: + + if device_is_my_type $DEV + then + echo 1 > $device_path/authorized + done + + +``device_is_my_type()`` 才是锁定方案真正见功夫的 +地方。仅仅检查 class、type 和 protocol 是否匹配 +某个值,是你能做出的最糟糕的安全验证之一; +对想绕过它的人来说,这反而是最容易利用的方案。 +如果你需要真正安全的办法,那就该使用加密、 +证书认证之类的机制。把 USB 存储设备当作 +“钥匙”的一个简单例子可以是:: + + function device_is_my_type() + { + echo 1 > authorized # 暂时授权它 + # FIXME: 确保没人能挂载它 + mount DEVICENODE /mntpoint + sum=$(md5sum /mntpoint/.signature) + if [ $sum = $(cat /etc/lockdown/keysum) ] + then + echo "We are good, connected" + umount /mntpoint + # 再做一些额外处理,让其他人也能使用它 + else + echo 0 > authorized + fi + } + + +当然,这个例子很粗糙;真正要做的话, +你会想用基于 PKI 的证书校验,这样就不必依赖 +共享密钥之类的东西。不过你应该已经明白意思了。 +任何拿到设备仿真工具包的人都能伪造描述符和设备信息。 +别信这个。 + +接口授权 +-------- + +也有类似的方法用于允许或拒绝特定 USB 接口。 +这使得你可以只阻止某个 USB 设备中的部分接口。 + +授权接口:: + + $ echo 1 > /sys/bus/usb/devices/INTERFACE/authorized + +取消接口授权:: + + $ echo 0 > /sys/bus/usb/devices/INTERFACE/authorized + +也可以更改特定 USB 总线上新接口的默认授权值。 + +默认允许接口:: + + $ echo 1 > /sys/bus/usb/devices/usbX/interface_authorized_default + +默认拒绝接口:: + + $ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default + +默认情况下, +``interface_authorized_default`` 位为 ``1``, +因此所有接口默认都处于已授权状态。 + +注意: + 如果把一个先前未授权的接口改为已授权, + 则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe`` + 来手动触发驱动探测。 + +对于需要多个接口的驱动程序,应先授权所有必需接口, +然后再触发驱动探测。这样做可以避免副作用。 diff --git a/Documentation/translations/zh_CN/usb/chipidea.rst b/Documentation/translations/zh_CN/usb/chipidea.rst new file mode 100644 index 000000000000..ea0dc3043189 --- /dev/null +++ b/Documentation/translations/zh_CN/usb/chipidea.rst @@ -0,0 +1,150 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/chipidea.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +============================= +ChipIdea 高速双角色控制器驱动 +============================= + +1. 如何测试 OTG FSM(HNP 和 SRP) +--------------------------------- + +下面以两块 Freescale i.MX6Q Sabre SD 开发板为例, +说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。 + +1.1 如何使能 OTG FSM +-------------------- + +1.1.1 在 ``menuconfig`` 中选择 ``CONFIG_USB_OTG_FSM``,并重新编译内核 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +重新构建内核镜像和模块。如果想查看 OTG FSM 的 +一些内部变量,可以挂载 ``debugfs``;其中有两个文件 +可以显示 OTG FSM 变量以及部分控制器寄存器的值:: + + cat /sys/kernel/debug/ci_hdrc.0/otg + cat /sys/kernel/debug/ci_hdrc.0/registers + +1.1.2 在控制器节点对应的 ``dts`` 文件中添加以下条目 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + otg-rev = <0x0200>; + adp-disable; + +1.2 测试步骤 +------------ + +1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电, + 并加载 gadget 类驱动(例如 ``g_mass_storage``)。 + +2) 用 USB 线连接两块开发板: + 一端是 micro A 插头,另一端是 micro B 插头。 + + 插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。 + +3) 角色切换 + + 在 B 设备上执行:: + + echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req + + B 设备应接管主机角色并枚举 A 设备。 + +4) A 设备切回主机角色 + + 在 B 设备上执行:: + + echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req + + 或者,通过引入 HNP 轮询,B 端主机可以知道 + A 端外设希望切换为主机角色,因此这次角色切换 + 也可以通过 A 端外设响应 B 端主机的轮询, + 在 A 侧触发。 + 这可以通过在 A 设备上执行下面的命令来完成:: + + echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req + + A 设备应切回主机角色并枚举 B 设备。 + +5) 拔掉 B 设备(拔掉 micro B 插头), + 并在 10 秒内重新插入; + A 设备应重新枚举 B 设备。 + +6) 拔掉 B 设备(拔掉 micro B 插头), + 并在 10 秒后重新插入; + A 设备不应重新枚举 B 设备。 + + 如果 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 设备上执行:: + + echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req + +7) A 设备关闭总线供电 + + 在 A 设备上执行:: + + echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop + + A 设备应断开与 B 设备的连接,并关闭总线供电。 + +8) B 设备发出 SRP 数据脉冲 + + 在 B 设备上执行:: + + echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req + + A 设备应恢复 USB 总线并枚举 B 设备。 + +1.3 参考文档 +------------ +《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 用作系统唤醒源的示例。 + +2.1 使能核心控制器的唤醒功能:: + + echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup + +2.2 使能 glue 层的唤醒功能:: + + echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup + +2.3 使能 PHY 的唤醒功能(可选):: + + echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup + +2.4 使能根集线器的唤醒功能:: + + echo enabled > /sys/bus/usb/devices/usb1/power/wakeup + +2.5 使能相关设备的唤醒功能:: + + echo enabled > /sys/bus/usb/devices/1-1/power/wakeup + +如果系统只有一个 USB 端口, +而你希望在该端口上启用 USB 唤醒功能, +可以使用下面的脚本:: + + 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 new file mode 100644 index 000000000000..3468ce50c5ba --- /dev/null +++ b/Documentation/translations/zh_CN/usb/dwc3.rst @@ -0,0 +1,63 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/dwc3.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +========= +DWC3 驱动 +========= + + +待办 +~~~~ + +阅读时如果想顺手认领点任务,可以从下面挑一项 :) + +- 将中断处理程序改为每个端点各自使用线程化 IRQ + + 事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。 + 当前代码会一直自旋等待命令完成,这种设计并不好。 + + 实现思路: + + - DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。 + 中断号在探测(``probe``)阶段分配,并归属于该设备。 + 如果硬件通过 ``MSI`` 为每个端点提供独立中断, + 那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。 + + - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源, + 在调用 ``usb_ep_disable()`` 时释放中断资源。 + 最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。 + - ``dwc3_send_gadget_ep_cmd()`` 将在 ``wait_for_completion_timeout()`` + 中休眠,直到命令完成。 + - 中断处理程序分为以下几个部分: + + - 设备级主中断处理程序 + 遍历每个事件,并对其调用 ``generic_handle_irq()``。 + 从 ``generic_handle_irq()`` 返回后,确认事件计数器,使中断最终消失。 + + - 设备级线程化处理程序 + 无。 + + - 端点中断的主处理程序 + 读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。 + 事件保存在每个端点的数据结构中。 + 还要注意,一旦把某项工作交给线程处理, + 就不要再在主处理程序里处理它, + 以免出现优先级反转之类的问题。 + + - 端点中断的线程化处理程序 + 处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。 + + 延迟: + + 不应增加延迟,因为中断线程具有较高优先级, + 会在普通用户态任务之前运行 + (除非用户更改了调度优先级)。 diff --git a/Documentation/translations/zh_CN/usb/ehci.rst b/Documentation/translations/zh_CN/usb/ehci.rst new file mode 100644 index 000000000000..e05e493a30d3 --- /dev/null +++ b/Documentation/translations/zh_CN/usb/ehci.rst @@ -0,0 +1,261 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/ehci.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +========= +EHCI 驱动 +========= + +2002年12月27日 + +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 2.0 系统上使用。当它们 +插入 EHCI 控制器时,会被交由 USB 1.1 的伴随 +(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。 + +当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过 +集线器中的事务转换器(Transaction Translator,TT) +与 EHCI 控制器交互,该转换器将低速或全速事务转换为 +高速分割事务,从而避免浪费传输带宽。 + +截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行 +(按字母顺序):Intel、NEC、Philips 和 VIA。 +其他供应商的 EHCI 实现正在陆续问世; +预计该驱动在这些实现上也可正常运行。 + +自 2001 年年中起,usb-storage 设备就已可用 +(在 2.4 版该驱动上速度相当不错), +集线器则直到 2001 年底才开始可用,而其他类型的高速设备 +似乎要等到更多系统内置 USB 2.0 后才会出现。 +这类新系统从 2002 年初开始上市, +并在 2002 年下半年变得更加常见。 + +注意,USB 2.0 支持并不只是 EHCI 本身。 +它还需要对 Linux-USB 核心 API 作出其他修改, +包括 hub 驱动;不过这些修改并不需要真正改变 +暴露给 USB 设备驱动的基本 ``usbcore`` API。 + +- David Brownell + <dbrownell@users.sourceforge.net> + + +功能 +==== + +该驱动会定期在 x86 硬件上进行测试, +也已在 PPC 硬件上使用,因此大小端问题应当已经解决。 +因此可以认为,它已经处理好了所有必要的 PCI 细节, +所以即便在 DMA 映射有些特殊的系统上, +I/O 也应能正常运行。 + +传输类型 +-------- + +截至本文撰写时,该驱动应当已经能够很好地处理 +所有控制传输、批量传输和中断传输, +包括通过 USB 2.0 集线器中的事务转换器 +与 USB 1.1 设备通信;但仍可能存在 bug。 + +高速等时(ISO)传输支持也已可用,但截至本文撰写时, +还没有 Linux 驱动使用这项支持。 + +目前尚不支持通过事务转换器实现全速等时传输。 +需要注意,ISO 传输的 split transaction 支持 +与高速 ISO 传输几乎无法共用代码, +因为 EHCI 用不同的数据结构表示它们。 +因此,目前大多数 USB 音频和视频设备 +还不能通过高速总线连接使用。 + +驱动行为 +-------- + +所有类型的传输都可以排队。 +这意味着来自一个接口驱动的控制传输 +(或通过 usbfs 发出的控制传输)不会干扰 +另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期, +而不必担心中断处理开销导致的数据丢失。 + + +EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。 +该驱动不需要了解那些驱动的任何细节; +一个原本就能正常工作的 OHCI 或 UHCI 驱动, +并不会因为 EHCI 驱动也存在而需要更改。 + +电源管理方面还有一些问题; +当前挂起/恢复的行为还不完全正确。 + +此外,在调度周期性事务 +(中断和等时传输)时还采取了一些简化处理。 +这些简化会限制可调度的周期性事务数量, +并且无法使用小于一帧的轮询间隔。 + +使用方式 +======== + +假设有一个 EHCI 控制器(位于 PCI 卡或主板上), +并且已将此驱动编译为模块,可这样加载:: + + # modprobe ehci-hcd + +卸载方式:: + + # rmmod ehci-hcd + +还应加载一个伴随控制器驱动, +例如 ``ohci-hcd`` 或 ``uhci-hcd``。 +如果 EHCI 驱动出现任何问题,只需卸载它的模块, +随后该伴随控制器驱动就会接手 +此前由 EHCI 驱动处理的所有设备 +(但速度会降低)。 + +模块参数(传给 ``modprobe``)包括: + + log2_irq_thresh(默认值 0): + 默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧 + (125 微秒)。最大值 6 表示 2^6 = 64 个微帧。 + 该值控制 EHCI 控制器发出中断的频率。 + +如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持, +则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件: + + ``async`` + 转储异步调度,用于控制传输和批量传输。它会显示每个活动的 ``qh`` + 以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。 + (可以在 ``usb-storage`` 做磁盘 I/O 时看它;顺便观察请求队列!) + + ``periodic`` + 转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。 + + ``registers`` + 显示控制器寄存器状态。 + +这些文件的内容有助于定位驱动问题。 + + +设备驱动通常不需要关心自己是否运行在 EHCI 之上, +但它们可能想检查 +``usb_device->speed == USB_SPEED_HIGH``。 +高速设备能做到全速(或低速)设备做不到的事, +例如高带宽的周期性传输(中断或 ISO 传输)。 +另外,设备描述符中的某些值 +(例如周期性传输的轮询间隔) +在高速模式下使用不同的编码方式。 + +不过,一定要让设备驱动经过 USB 2.0 集线器的测试。 +当使用事务转换器时,这些集线器报告某些故障 +(例如断开连接)的方式会不同; +已经见过一些驱动在遇到与 OHCI 或 UHCI +所报告的不同故障时表现不佳。 + +性能 +==== + +USB 2.0 吞吐量主要受两个因素制约: +主机控制器处理请求的速度,以及设备响应这些请求的速度。 +480 Mbit/sec 的“原始传输率”对所有设备都成立, +但总吞吐量还会受到诸如单个高速包之间的延迟、 +驱动是否足够聪明,以及系统整体负载等因素的影响。 +延迟也是性能考量因素。 + +批量传输最常用于关注吞吐量的场景。 +需要记住的是,批量传输总是以 512 字节包为单位, +而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。 +8 个 USB 2.0 微帧构成一个 USB 1.1 帧; +一个微帧的时长是 1 毫秒 / 8 = 125 微秒。 + +因此,只要硬件和设备驱动软件都允许, +批量传输可提供超过 50 MByte/sec 的带宽。 +周期性传输模式(等时和中断)允许使用更大的包大小, +从而可以逼近所宣称的 480 Mbit/sec 传输率。 + +硬件性能 +-------- + +截至本文撰写时,单个 USB 2.0 设备的最大传输速率 +通常约为 20 MByte/sec。 +这当然会随着时间改变:一些设备现在更快,一些更慢。 + +第一代 NEC EHCI 实现似乎存在 +大约 28 MByte/sec 的硬件瓶颈。 +虽然这对单个 20 MByte/sec 的设备显然已经够用, +但把三个这样的设备挂到同一总线上, +并不能得到 60 MByte/sec。 +问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问, +因此它每个微帧只会尝试 6 次(也许是 7 次) +USB 事务,而不是 13 次。 +(对一个比其他产品早上市一年的芯片来说, +这是个合理的妥协!) + + +预计较新的实现会在这方面做得更好, +通过投入更多芯片面积来解决这个问题, +使新的主板芯片组更接近 60 MByte/sec 的目标。 +这既包括 NEC 的更新实现,也包括其他厂商的芯片。 + + +主机从 EHCI 控制器收到“请求已完成”中断的最小延迟 +为一个微帧(125 微秒)。该延迟可以调节; +驱动提供了一个模块选项。默认情况下, +``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制 +或批量请求时,通常可以在不到 250 微秒内得知它已完成 +(具体取决于传输大小)。 + +软件性能 +-------- + +即便只是要达到 20 MByte/sec 的传输速率, +Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。 +这意味着要发出较大的请求, +或者在需要发出一连串小请求时使用批量请求排队。 +如果驱动未做到这一点,那么会直接从性能结果上表现出来。 + + +在典型情况下,使用 ``usb_bulk_msg()`` +以 4 KB 块循环写出, +会浪费超过一半的 USB 2.0 带宽。 +I/O 完成与驱动发出下一次请求之间的延迟, +通常会比一次 I/O 本身耗时更长。 +如果同样的循环改用 16 KB 块,会好一些; +若使用一连串 128 KB 块,则浪费会少得多。 + + +但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效, +不如直接向主机控制器排入多个(批量)请求, +然后等待它们全部完成(或在出错时取消)。 +这种 URB 排队方式对所有 USB 1.1 +主机控制器驱动也同样适用。 + + +在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API; +它们会把 scatterlist 中的所有缓冲区都排入队列。 +它们还使用 scatterlist 的 DMA 映射 +(其中可能应用 IOMMU)并减少中断次数, +这些都有助于让高速传输尽可能快地运行。 + +待办: + 中断传输和等时(ISO)传输的性能问题。 + 这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。 + +待办: + 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数, + 可以分配超过标准 80% 的周期性带宽。 + 后续将对此进行说明。 diff --git a/Documentation/translations/zh_CN/usb/index.rst b/Documentation/translations/zh_CN/usb/index.rst new file mode 100644 index 000000000000..eb5aca0c13ec --- /dev/null +++ b/Documentation/translations/zh_CN/usb/index.rst @@ -0,0 +1,54 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/index.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +======== +USB 支持 +======== + +.. toctree:: + :maxdepth: 1 + + acm + authorization + chipidea + dwc3 + ehci + usbmon + +Todolist: + +* functionfs +* functionfs-desc +* gadget_configfs +* gadget_hid +* gadget_multi +* gadget_printer +* gadget_serial +* gadget_uvc +* gadget-testing +* iuu_phoenix +* mass-storage +* misc_usbsevseg +* mtouchusb +* ohci +* raw-gadget +* usbip_protocol +* usb-serial +* usb-help +* text_files + +.. only:: subproject and html + + 索引 + ==== + + * :ref:`genindex` diff --git a/Documentation/translations/zh_CN/usb/usbmon.rst b/Documentation/translations/zh_CN/usb/usbmon.rst new file mode 100644 index 000000000000..11b6d5b59dce --- /dev/null +++ b/Documentation/translations/zh_CN/usb/usbmon.rst @@ -0,0 +1,427 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/usb/usbmon.rst + +:翻译: + + 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com> + +:校译: + + +====== +usbmon +====== + +简介 +==== +小写形式的 ``usbmon`` 指的是内核中的一项功能, +用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具 +``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。 +类似地,人们希望使用 usbdump 或 USBMon +(首字母大写)之类的工具来检查 +usbmon 生成的原始跟踪数据。 + +usbmon 报告的是各个外设驱动 +向主机控制器驱动(HCD)发出的请求。 +因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息 +可能无法精确对应实际的总线事务。 +这和 tcpdump 的情况是一样的。 + +目前实现了两种 API: ``text`` 和 ``binary``。 +二进制 API 通过 ``/dev`` 命名空间中的字符设备提供, +并且属于 ABI。文本 API 自内核 2.6.35 起已废弃, +但为了方便仍然可用。 + +如何使用 usbmon 收集原始文本跟踪信息 +==================================== + +与数据包套接字不同,usbmon 提供了一种接口, +可以输出文本格式的跟踪信息。这样做有两个目的: +第一,在更完善的格式最终确定之前, +它作为工具间通用的跟踪交换格式; +第二,在不使用工具的情况下,人们也可以直接阅读这些信息。 + +要收集原始文本跟踪信息,请按以下步骤进行操作。 + +1. 准备 +------- + +挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块 +(如果它是作为模块构建的)。如果 usbmon 已经编入内核, +那么第二步可以省略。 + +命令示例:: + + # mount -t debugfs none_debugs /sys/kernel/debug + # modprobe usbmon + # + +确认总线套接字是否存在:: + + # ls /sys/kernel/debug/usb/usbmon + 0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u + # + +现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包, +并跳到第 3 步; +也可以先按第 2 步找到目标设备所在的总线。 +这样可以过滤掉那些持续输出数据的烦人设备。 + +2. 查找目标设备连接的是哪条总线 +------------------------------- + +运行 ``cat /sys/kernel/debug/usb/devices``, +找到对应设备的 T 行。通常可以通过厂商字符串来查找。 +如果有许多类似设备,可以拔掉其中一个, +再比较前后两次 ``/sys/kernel/debug/usb/devices`` +的输出。T 行里会包含总线编号。 + +示例:: + + T: Bus=03 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0 + D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1 + P: Vendor=0557 ProdID=2004 Rev= 1.00 + S: Manufacturer=ATEN + S: Product=UC100KM V2.00 + +``Bus=03`` 表示它位于 3 号总线上。或者, +也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。 + +示例如下:: + + Bus 003 Device 002: ID 0557:2004 ATEN UC100KM V2.00 + + +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 盘、拷贝文件、操作摄像头等。 + + +5. 停止 cat +----------- + +这一步通常通过键盘中断(Control-C)完成。 + +此时输出文件(本例中为 ``/tmp/1.mon.out``) +可以保存、通过电子邮件发送,或使用文本编辑器查看。 +如果使用最后一种方式,请确保文件不会大到编辑器无法打开。 + + +原始文本数据格式 +================ + +目前支持两种格式:原始格式,也就是 ``1t`` 格式, +以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。 +``1u`` 格式增加了一些字段,例如 ISO 帧描述符、 +``interval`` 等。它生成的行会稍长一些, +但在其他方面是 ``1t`` 格式的完整超集。 + +如果程序需要区分上述两种格式, +可以查看 ``address`` 字段(见下文)。 +如果其中有两个冒号,就是 ``1t`` 格式; +否则是 ``1u`` 格式。 + +任何文本格式的数据由一系列事件组成, +如 URB 提交、URB 回调、提交错误等。 +每个事件对应单独的一行文本, +由使用空白符间隔的若干字段组成。 +字段的数量与位置可能取决于事件类型, +但以下字段对所有类型都通用: + +下面按从左到右的顺序列出这些共有字段: + +- URB Tag。用于标识 URB,通常是 URB 结构体在内核中的地址 + (以十六进制表示), + 但也可能是序号或其他合理的唯一字符串。 + +- 时间戳(微秒),十进制数字。 + 时间戳的精度取决于可用时钟, + 因此可能远差于 + 1 微秒(例如实现使用的是 jiffies)。 + +- 事件类型。它表示的是事件的格式,而不是 URB 的类型。 + 可用值为:``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。 + +- ``Address`` 字段(以前称作 ``pipe``)。 + 它包含四个由冒号分隔的字段: + URB 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下: + + == == ========================== + Ci Co 控制输入和输出 + Zi Zo 等时输入和输出 + Ii Io 中断输入和输出 + Bi Bo 批量输入和输出 + == == ========================== + + 总线号、设备地址和端点号使用十进制,但可能有前导零。 + +- URB 状态字段。这个字段要么是一个字母, + 要么是几个由冒号分隔的数字: + URB 状态、``interval``、``start frame`` 和 ``error count``。 + 与 ``address`` 字段不同,除了状态外,其余字段都是可选的。 + ``interval`` 只会为中断和等时 URB 打印;``start frame`` 只会为 + 等时 URB 打印;错误计数只会在等时回调事件中打印。 + + 状态字段是一个十进制数字,有时为负数, + 对应 URB 的 ``status`` 字段。 + 对于提交事件,这个字段本身没有实际意义, + 但为了便于脚本解析,它仍然存在。 + 当发生错误时,该字段包含错误码。 + + 在提交控制包时,这个字段包含的是 ``Setup Tag``, + 而不是一组数字。 + 判断 ``Setup Tag`` 是否存在很容易,因为它从来不是数字。 + 因此,如果脚本在这个字段里发现的是一组数字, + 就会继续读取数据长度(等时 URB 除外)。 + 如果发现的是其他内容,比如一个字母, + 那么脚本会先读取 ``Setup`` 包,再读取数据长度或等时描述符。 + +- ``Setup`` 包由 5 个字段组成: + ``bmRequestType``、``bRequest``、``wValue``、 + ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。 + 如果 ``Setup Tag`` 为 ``s``,就可以安全地解码这些字段。 + 否则,说明 Setup 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。 + +- 等时传输帧描述符的数量及其内容: + 如果一个等时传输事件带有一组描述符,首先打印该 URB 中描述符的总数, + 然后为每个描述符打印一个字段,最多打印 5 个字段。 + 每个字段由三个用冒号分隔的十进制数字组成, + 分别表示状态(status)、偏移(offset)和长度(length)。 + 对于提交(submission),报告的是初始长度; + 对于回调(callback),报告的是实际长度。 + +- 数据长度: + 对于提交,表示请求的长度;对于回调,表示实际传输的长度。 + +- 数据标签: + 即使数据长度非零,usbmon 也不一定会捕获数据。 + 仅当标签为 ``=`` 时,才会有数据字段。 + +- 数据字段: + 以大端十六进制格式显示。注意,这些并不是真正的机器字, + 而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含 + 1 到 4 个字节。 + 收集的数据长度是有限的,可能小于数据长度字段中报告的值。 + 因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区, + 所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下, + 收集的数据长度可能大于数据长度字段的值。 + + + +示例: + +获取端口状态的输入控制传输:: + + 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``)的输出批量传输:: + + 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 的整体架构与前文大体相同,只是事件以二进制格式传递。 +每个事件都通过下面的结构发送 +(这个名字是为了叙述方便而虚构的):: + + + struct usbmon_packet { + u64 id; /* 0: URB ID - 从提交到回调 */ + unsigned char type; /* 8: 与文本相同;可扩展 */ + unsigned char xfer_type; /* ISO (0)、中断、控制、批量 (3) */ + unsigned char epnum; /* 端点号和传输方向 */ + unsigned char devnum; /* 设备地址 */ + u16 busnum; /* 12: 总线号 */ + char flag_setup; /* 14: 与文本相同 */ + char flag_data; /* 15: 与文本相同;二进制零也可 */ + s64 ts_sec; /* 16: gettimeofday */ + s32 ts_usec; /* 24: gettimeofday */ + int status; /* 28: */ + unsigned int length; /* 32: 数据长度(提交或实际) */ + unsigned int len_cap; /* 36: 已捕获的数据长度 */ + union { /* 40: */ + unsigned char setup[SETUP_LEN]; /* 仅用于控制类 S 事件 */ + struct iso_rec { /* 仅用于 ISO */ + int error_count; + int numdesc; + } iso; + } s; + int interval; /* 48: 仅用于中断和 ISO */ + int start_frame; /* 52: 仅用于 ISO */ + unsigned int xfer_flags; /* 56: URB 的 transfer_flags 副本 */ + unsigned int ndesc; /* 60: 实际 ISO 描述符数量 */ + }; /* 64 总长度 */ + +可以用 ``read(2)``、``ioctl(2)``, +或者通过 ``mmap`` 访问缓冲区, +从字符设备接收这些事件。 +不过,出于兼容性原因,``read(2)`` +只返回前 48 个字节。 + +字符设备通常命名为 ``/dev/usbmonN``, +其中 ``N`` 是 USB 总线号。 +编号为零的设备(``/dev/usbmon0``)比较特殊, +表示“所有总线”。 +请注意,具体命名策略由 Linux 发行版决定。 + +如果你手动创建 ``/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)`` + +参数是指向以下结构的指针:: + + struct mon_bin_stats { + u32 queued; + u32 dropped; + }; + +成员 ``queued`` 表示当前缓冲区中已经排队的事件数量, +而不是自上次重置以来处理过的事件数量。 + +成员 ``dropped`` 表示自上次调用 +``MON_IOCG_STATS`` 以来丢失的事件数量。 + +``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)`` + +此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。 +大小可能会向下取整到下一个块(或页)。 +如果请求的大小超出该内核的 [未指定] 范围, +则调用会失败并返回 ``-EINVAL``。 + +``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)`` + +该调用返回缓冲区当前大小(以字节为单位)。 + +``MON_IOCX_GET``,定义为 +``_IOW(MON_IOC_MAGIC, 6, struct mon_get_arg)`` +``MON_IOCX_GETX``,定义为 +``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)`` + +如果内核缓冲区中没有事件, +这些调用就会一直等待,直到有事件到达, +然后返回第一个事件。 +参数是指向以下结构的指针:: + + struct mon_get_arg { + struct usbmon_packet *hdr; + void *data; + size_t alloc; /* 数据长度可以为零 */ + }; + + +调用前,应填好 ``hdr``、``data`` 和 ``alloc``。 +调用返回后,``hdr`` 指向的区域中包含下一个事件的结构; +如果存在数据,那么数据缓冲区中也会包含相应数据。 +该事件会从内核缓冲区中移除。 + +``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域, +``MON_IOCX_GETX`` 会复制 64 字节。 + +``MON_IOCX_MFETCH``,定义为 +``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)`` + +当应用程序通过 ``mmap(2)`` 访问缓冲区时, +主要使用这个 ioctl。 +其参数是指向以下结构的指针:: + + struct mon_mfetch_arg { + uint32_t *offvec; /* 获取的事件偏移向量 */ + uint32_t nfetch; /* 要获取的事件数量(输出:已获取) */ + uint32_t nflush; /* 要刷新的事件数量 */ + }; + + +该 ioctl 的操作分为三个阶段: + +首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。 +实际丢弃的事件数量会写回 ``nflush``。 + +其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待, +直到缓冲区中出现事件。 + +第三,将最多 ``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)`` 不起作用。 + +* 二进制 API 的内核缓冲区内存映射访问 + +基本思想很简单: + +准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。 +然后执行类似下面伪代码的循环:: + + struct mon_mfetch_arg fetch; + struct usbmon_packet *hdr; + int nflush = 0; + for (;;) { + fetch.offvec = vec; // 有 N 个 32 位字 + fetch.nfetch = N; // 或者少于 N + fetch.nflush = nflush; + ioctl(fd, MON_IOCX_MFETCH, &fetch); // 同时处理错误 + nflush = fetch.nfetch; // 完成后要刷新这么多包 + for (i = 0; i < nflush; i++) { + hdr = (struct ubsmon_packet *) &mmap_area[vec[i]]; + if (hdr->type == '@') // 填充包 + continue; + caddr_t data = &mmap_area[vec[i]] + 64; + process_packet(hdr, data); + } + } + + + +因此,主要思想是每 N 个事件只执行一次 ioctl。 + +虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端, +因此上面的伪代码无需任何合并操作。 diff --git a/arch/arm/mach-pxa/devices.c b/arch/arm/mach-pxa/devices.c index 7695cfce01a1..edad956a1483 100644 --- a/arch/arm/mach-pxa/devices.c +++ b/arch/arm/mach-pxa/devices.c @@ -11,7 +11,6 @@ #include <linux/platform_data/i2c-pxa.h> #include <linux/soc/pxa/cpu.h> -#include "udc.h" #include <linux/platform_data/video-pxafb.h> #include <linux/platform_data/mmc-pxamci.h> #include "irqs.h" @@ -83,10 +82,6 @@ void __init pxa_set_mci_info(const struct pxamci_platform_data *info, pr_err("Unable to create mci device: %d\n", err); } -static struct pxa2xx_udc_mach_info pxa_udc_info = { - .gpio_pullup = -1, -}; - static struct resource pxa2xx_udc_resources[] = { [0] = { .start = 0x40600000, @@ -108,7 +103,6 @@ struct platform_device pxa25x_device_udc = { .resource = pxa2xx_udc_resources, .num_resources = ARRAY_SIZE(pxa2xx_udc_resources), .dev = { - .platform_data = &pxa_udc_info, .dma_mask = &udc_dma_mask, } }; @@ -119,7 +113,6 @@ struct platform_device pxa27x_device_udc = { .resource = pxa2xx_udc_resources, .num_resources = ARRAY_SIZE(pxa2xx_udc_resources), .dev = { - .platform_data = &pxa_udc_info, .dma_mask = &udc_dma_mask, } }; diff --git a/arch/arm/mach-pxa/gumstix.c b/arch/arm/mach-pxa/gumstix.c index 1713bdf3b71e..6074815a4bca 100644 --- a/arch/arm/mach-pxa/gumstix.c +++ b/arch/arm/mach-pxa/gumstix.c @@ -39,7 +39,6 @@ #include "pxa25x.h" #include <linux/platform_data/mmc-pxamci.h> -#include "udc.h" #include "gumstix.h" #include "devices.h" diff --git a/arch/arm/mach-pxa/udc.h b/arch/arm/mach-pxa/udc.h deleted file mode 100644 index 9a827e32db98..000000000000 --- a/arch/arm/mach-pxa/udc.h +++ /dev/null @@ -1,8 +0,0 @@ -/* - * arch/arm/mach-pxa/include/mach/udc.h - * - */ -#include <linux/platform_data/pxa2xx_udc.h> - -extern void pxa_set_udc_info(struct pxa2xx_udc_mach_info *info); - diff --git a/arch/arm64/boot/dts/qcom/pm4125.dtsi b/arch/arm64/boot/dts/qcom/pm4125.dtsi index cf8c822e80ce..542e8fe030da 100644 --- a/arch/arm64/boot/dts/qcom/pm4125.dtsi +++ b/arch/arm64/boot/dts/qcom/pm4125.dtsi @@ -61,7 +61,6 @@ "attach-detach", "legacy-cable-detect", "try-snk-src-detect"; - vdd-vbus-supply = <&pm4125_vbus>; status = "disabled"; }; diff --git a/arch/arm64/boot/dts/qcom/pm7250b.dtsi b/arch/arm64/boot/dts/qcom/pm7250b.dtsi index 0761e6b5fd8d..43cab07126c5 100644 --- a/arch/arm64/boot/dts/qcom/pm7250b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm7250b.dtsi @@ -86,7 +86,6 @@ "msg-tx-discarded", "msg-rx-discarded", "fr-swap"; - vdd-vbus-supply = <&pm7250b_vbus>; status = "disabled"; }; diff --git a/arch/arm64/boot/dts/qcom/pm8150b.dtsi b/arch/arm64/boot/dts/qcom/pm8150b.dtsi index 3f7b0b6a1d10..b83be4b6cb1c 100644 --- a/arch/arm64/boot/dts/qcom/pm8150b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8150b.dtsi @@ -95,7 +95,6 @@ "msg-tx-discarded", "msg-rx-discarded", "fr-swap"; - vdd-vbus-supply = <&pm8150b_vbus>; }; pm8150b_temp: temp-alarm@2400 { diff --git a/arch/arm64/boot/dts/qcom/pmi632.dtsi b/arch/arm64/boot/dts/qcom/pmi632.dtsi index 8c899d148e46..b0ed35094a98 100644 --- a/arch/arm64/boot/dts/qcom/pmi632.dtsi +++ b/arch/arm64/boot/dts/qcom/pmi632.dtsi @@ -69,7 +69,6 @@ "attach-detach", "legacy-cable-detect", "try-snk-src-detect"; - vdd-vbus-supply = <&pmi632_vbus>; status = "disabled"; }; diff --git a/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts b/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts index da46e9d65528..4ace2d6c06ce 100644 --- a/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts +++ b/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts @@ -409,6 +409,8 @@ typec-power-opmode = "default"; pd-disable; + vbus-supply = <&pm4125_vbus>; + ports { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts b/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts index 1203172729fa..22baee407fbe 100644 --- a/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts +++ b/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts @@ -323,6 +323,8 @@ typec-power-opmode = "default"; pd-disable; + vbus-supply = <&pmi632_vbus>; + ports { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts index 54da0d759a67..690b484352ed 100644 --- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts +++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts @@ -1499,6 +1499,8 @@ data-role = "dual"; self-powered; + vbus-supply = <&pm8150b_vbus>; + source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_USB_COMM | diff --git a/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts b/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts index 0edb2992b902..3223884f9cd6 100644 --- a/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts +++ b/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts @@ -252,6 +252,8 @@ typec-power-opmode = "default"; pd-disable; + vbus-supply = <&pmi632_vbus>; + port { pmi632_hs_in: endpoint { remote-endpoint = <&usb_dwc3_hs>; diff --git a/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts b/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts index 466ad409e924..0f23eaef01f2 100644 --- a/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts +++ b/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts @@ -251,6 +251,8 @@ typec-power-opmode = "default"; pd-disable; + vbus-supply = <&pmi632_vbus>; + ports { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts b/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts index 3964aae47fd4..23f950067a08 100644 --- a/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts +++ b/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts @@ -953,6 +953,8 @@ typec-power-opmode = "default"; pd-disable; + vbus-supply = <&pm7250b_vbus>; + ports { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/arm64/boot/dts/qcom/sm8150-hdk.dts b/arch/arm64/boot/dts/qcom/sm8150-hdk.dts index 6ae6e07c37df..8fb04ce72f91 100644 --- a/arch/arm64/boot/dts/qcom/sm8150-hdk.dts +++ b/arch/arm64/boot/dts/qcom/sm8150-hdk.dts @@ -572,6 +572,8 @@ data-role = "dual"; self-powered; + vbus-supply = <&pm8150b_vbus>; + source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_USB_COMM | diff --git a/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi b/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi index c017399297b9..51b57c697a75 100644 --- a/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi @@ -725,6 +725,8 @@ data-role = "dual"; self-powered; + vbus-supply = <&pm8150b_vbus>; + source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_USB_COMM | diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e4c4f2b09732..5c2d3ff5fce8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -963,6 +963,16 @@ config SENSORS_POWR1220 This driver can also be built as a module. If so, the module will be called powr1220. +config SENSORS_PROM21_XHCI + tristate "AMD Promontory 21 xHCI temperature sensor" + depends on USB_XHCI_PCI + help + If you say yes here you get support for the AMD Promontory 21 + (PROM21) xHCI temperature sensor. + + This driver can also be built as a module. If so, the module + will be called prom21-xhci. + config SENSORS_LAN966X tristate "Microchip LAN966x Hardware Monitoring" depends on SOC_LAN966 || COMPILE_TEST diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index d5c8bc05123a..63effc0ab8d1 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -199,6 +199,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o +obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o diff --git a/drivers/hwmon/prom21-xhci.c b/drivers/hwmon/prom21-xhci.c new file mode 100644 index 000000000000..d40d0c53ce45 --- /dev/null +++ b/drivers/hwmon/prom21-xhci.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Promontory 21 xHCI Hwmon Implementation + * (only temperature monitoring is supported) + * + * This can be effectively used as the alternative chipset temperature monitor. + * + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com> + */ + +#include <linux/auxiliary_bus.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/hwmon.h> +#include <linux/io.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_data/usb-xhci-prom21.h> +#include <linux/pm_runtime.h> + +#define PROM21_XHCI_INDEX_OFFSET 0x3000 +#define PROM21_XHCI_DATA_OFFSET 0x3008 +#define PROM21_XHCI_TEMP_SELECTOR 0x0001e520 + +struct prom21_xhci { + struct pci_dev *pdev; + struct device *hwmon_dev; + void __iomem *regs; +}; + +static int prom21_xhci_pm_get(struct prom21_xhci *hwmon) +{ + struct device *dev = &hwmon->pdev->dev; + int ret; + + /* + * PROM21 temperature register access does not return a valid value while + * the parent xHCI PCI function is suspended. Do not wake the device from + * a hwmon read. On success, hold a usage reference without changing the + * runtime PM state; if runtime PM is disabled, allow the read unless the + * device is still marked suspended. + */ + ret = pm_runtime_get_if_active(dev); + if (ret > 0) + return 0; + + if (ret == -EINVAL) { + if (pm_runtime_status_suspended(dev)) + return -ENODATA; + + pm_runtime_get_noresume(dev); + return 0; + } + + if (!ret) + return -ENODATA; + + return ret; +} + +/* + * This is not a pure MMIO read. The PROM21 vendor data register is selected + * by temporarily writing PROM21_XHCI_TEMP_SELECTOR to the vendor index + * register. + * The hwmon core already serializes this driver's callbacks, so this driver + * does not need an additional private lock. That does not synchronize with + * firmware, SMM, ACPI, or other possible users. Keep the sequence short and + * restore the previous index before returning. + */ +static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon, + u8 *raw) +{ + struct device *dev = &hwmon->pdev->dev; + u32 index; + u8 data; + int ret; + + ret = prom21_xhci_pm_get(hwmon); + if (ret) + return ret; + + index = readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET); + /* Select the PROM21 temperature register through the vendor index. */ + writel(PROM21_XHCI_TEMP_SELECTOR, + hwmon->regs + PROM21_XHCI_INDEX_OFFSET); + /* Use a 32-bit read for PCI MMIO register access. */ + data = readl(hwmon->regs + PROM21_XHCI_DATA_OFFSET) & 0xff; + /* Restore the previous vendor index register value. */ + writel(index, hwmon->regs + PROM21_XHCI_INDEX_OFFSET); + readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET); + + /* + * Drop the usage reference taken by prom21_xhci_pm_get(). This is + * enough because the read path never resumes the device; use the normal + * put path so the PM core can re-evaluate idle state after the read. + * Otherwise, a racing xHCI autosuspend attempt can see a nonzero + * runtime PM usage count and skip autosuspend, and a later + * pm_runtime_put_noidle(), which does not check for an idle device, + * would leave the device active. + */ + pm_runtime_put(dev); + + if (!data) + return -ENODATA; + + *raw = data; + return 0; +} + +static long prom21_xhci_raw_to_millicelsius(u8 raw) +{ + /* + * No public AMD reference is available for this value. + * The scale was derived from observed PROM21 xHCI temperature readings: + * temp[C] = raw * 0.9066 - 78.624 + */ + return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624; +} + +static umode_t prom21_xhci_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + if (type != hwmon_temp) + return 0; + + switch (attr) { + case hwmon_temp_input: + return 0444; + default: + return 0; + } +} + +static int prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct prom21_xhci *hwmon = dev_get_drvdata(dev); + u8 raw; + int ret; + + if (type != hwmon_temp || attr != hwmon_temp_input) + return -EOPNOTSUPP; + + ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw); + if (ret) + return ret; + + *val = prom21_xhci_raw_to_millicelsius(raw); + return 0; +} + +static const struct hwmon_ops prom21_xhci_ops = { + .is_visible = prom21_xhci_is_visible, + .read = prom21_xhci_read, +}; + +static const struct hwmon_channel_info *const prom21_xhci_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL, +}; + +static const struct hwmon_chip_info prom21_xhci_chip_info = { + .ops = &prom21_xhci_ops, + .info = prom21_xhci_info, +}; + +static int prom21_xhci_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &auxdev->dev; + const struct prom21_xhci_pdata *pdata = dev_get_platdata(dev); + struct prom21_xhci *hwmon; + + if (!pdata) + return dev_err_probe(dev, -ENODEV, + "platform data unavailable\n"); + + if (!pdata->regs || + pdata->rsrc_len < PROM21_XHCI_DATA_OFFSET + sizeof(u32)) + return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n"); + + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->pdev = pdata->pdev; + hwmon->regs = pdata->regs; + auxiliary_set_drvdata(auxdev, hwmon); + + /* + * Parent the hwmon device to the PCI function because the temperature + * value is read from that function's MMIO BAR, and systems may contain + * multiple PROM21 xHCI functions. This lets userspace identify the PCI + * endpoint for each reading. The auxiliary driver still owns the hwmon + * lifetime and unregisters it before HCD teardown. + */ + hwmon->hwmon_dev = + hwmon_device_register_with_info(&pdata->pdev->dev, "prom21_xhci", + hwmon, &prom21_xhci_chip_info, + NULL); + if (IS_ERR(hwmon->hwmon_dev)) + return PTR_ERR(hwmon->hwmon_dev); + + return 0; +} + +static void prom21_xhci_remove(struct auxiliary_device *auxdev) +{ + struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev); + + /* + * The PROM21 PCI glue destroys the auxiliary device before HCD teardown. + * Unregister the hwmon device here so sysfs removes the attributes, + * stops new reads, and drains active hwmon callbacks before the xHCI + * MMIO mapping is released. + */ + hwmon_device_unregister(hwmon->hwmon_dev); +} + +static const struct auxiliary_device_id prom21_xhci_id_table[] = { + { .name = "xhci_pci_prom21.hwmon" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table); + +static struct auxiliary_driver prom21_xhci_driver = { + .name = "prom21-xhci", + .probe = prom21_xhci_probe, + .remove = prom21_xhci_remove, + .id_table = prom21_xhci_id_table, +}; +module_auxiliary_driver(prom21_xhci_driver); + +MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>"); +MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 7aae5d915a1e..f8f97e8e2226 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -34,11 +34,12 @@ #define TBNET_RING_SIZE 256 #define TBNET_LOGIN_RETRIES 60 #define TBNET_LOGOUT_RETRIES 10 +#define TBNET_THROTTLING 128000 #define TBNET_E2E BIT(0) #define TBNET_MATCH_FRAGS_ID BIT(1) #define TBNET_64K_FRAMES BIT(2) #define TBNET_MAX_MTU SZ_64K -#define TBNET_FRAME_SIZE SZ_4K +#define TBNET_FRAME_SIZE TB_MAX_FRAME_SIZE #define TBNET_MAX_PAYLOAD_SIZE \ (TBNET_FRAME_SIZE - sizeof(struct thunderbolt_ip_frame_header)) /* Rx packets need to hold space for skb_shared_info */ @@ -327,11 +328,6 @@ static void stop_login(struct tbnet *net) netdev_dbg(net->dev, "login stopped\n"); } -static inline unsigned int tbnet_frame_size(const struct tbnet_frame *tf) -{ - return tf->frame.size ? : TBNET_FRAME_SIZE; -} - static void tbnet_free_buffers(struct tbnet_ring *ring) { unsigned int i; @@ -562,7 +558,7 @@ static struct tbnet_frame *tbnet_get_tx_buffer(struct tbnet *net) tf->frame.size = 0; dma_sync_single_for_cpu(dma_dev, tf->frame.buffer_phy, - tbnet_frame_size(tf), DMA_TO_DEVICE); + tb_ring_frame_size(&tf->frame), DMA_TO_DEVICE); return tf; } @@ -744,7 +740,7 @@ static bool tbnet_check_frame(struct tbnet *net, const struct tbnet_frame *tf, } /* Should be greater than just header i.e. contains data */ - size = tbnet_frame_size(tf); + size = tb_ring_frame_size(&tf->frame); if (size <= sizeof(*hdr)) { net->stats.rx_length_errors++; return false; @@ -961,6 +957,9 @@ static int tbnet_open(struct net_device *dev) } net->rx_ring.ring = ring; + tb_ring_throttling(net->tx_ring.ring, TBNET_THROTTLING); + tb_ring_throttling(net->rx_ring.ring, TBNET_THROTTLING); + napi_enable(&net->napi); start_login(net); @@ -1011,7 +1010,8 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb, hdr->frame_index, hdr->frame_count); dma_sync_single_for_device(dma_dev, frames[i]->frame.buffer_phy, - tbnet_frame_size(frames[i]), DMA_TO_DEVICE); + tb_ring_frame_size(&frames[i]->frame), + DMA_TO_DEVICE); } return true; @@ -1085,7 +1085,7 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb, */ for (i = 0; i < frame_count; i++) { dma_sync_single_for_device(dma_dev, frames[i]->frame.buffer_phy, - tbnet_frame_size(frames[i]), DMA_TO_DEVICE); + tb_ring_frame_size(&frames[i]->frame), DMA_TO_DEVICE); } return true; diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index db3b0bef48f4..294b3227a545 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -18,6 +18,10 @@ menuconfig USB4 if USB4 +config USB4_CONFIGFS + def_tristate USB4 + depends on CONFIGFS_FS && !(USB4=y && CONFIGFS_FS=m) + config USB4_DEBUGFS_WRITE bool "Enable write by debugfs to configuration spaces (DANGEROUS)" help @@ -60,4 +64,15 @@ config USB4_DMA_TEST To compile this driver a module, choose M here. The module will be called thunderbolt_dma_test. +config USB4_STREAM + tristate "Stream data over Thunderbolt/USB4 cable" + depends on USB4_CONFIGFS + help + This adds support for USB4STREAM protocol that allows two + hosts to stream data directly over Thunderbolt/USB4 cable + through /dev/tbstreamX devices. + + To compile this driver a module, choose M here. The module will be + called thunderbolt_stream. + endif # USB4 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index b44b32dcb832..beb054c3126b 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,14 +1,18 @@ # SPDX-License-Identifier: GPL-2.0-only ccflags-y := -I$(src) obj-${CONFIG_USB4} := thunderbolt.o -thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o pci.o path.o tunnel.o eeprom.o thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o thunderbolt-${CONFIG_ACPI} += acpi.o thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o +thunderbolt-$(CONFIG_USB4_CONFIGFS) += configfs.o thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o CFLAGS_test.o += $(DISABLE_STRUCTLEAK_PLUGIN) thunderbolt_dma_test-${CONFIG_USB4_DMA_TEST} += dma_test.o obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o + +thunderbolt_stream-${CONFIG_USB4_STREAM} += stream.o +obj-$(CONFIG_USB4_STREAM) += thunderbolt_stream.o diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 45d1415871b4..53546bc477a5 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -28,7 +28,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, return AE_OK; /* It needs to reference this NHI */ - if (dev_fwnode(&nhi->pdev->dev) != fwnode) + if (dev_fwnode(nhi->dev) != fwnode) goto out_put; /* @@ -57,16 +57,16 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, */ pm_runtime_get_sync(&pdev->dev); - link = device_link_add(&pdev->dev, &nhi->pdev->dev, + link = device_link_add(&pdev->dev, nhi->dev, DL_FLAG_AUTOREMOVE_SUPPLIER | DL_FLAG_RPM_ACTIVE | DL_FLAG_PM_RUNTIME); if (link) { - dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); *(bool *)ret = true; } else { - dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_warn(nhi->dev, "device link creation from %s failed\n", dev_name(&pdev->dev)); } @@ -93,7 +93,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) acpi_status status; bool ret = false; - if (!has_acpi_companion(&nhi->pdev->dev)) + if (!has_acpi_companion(nhi->dev)) return false; /* @@ -103,7 +103,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32, tb_acpi_add_link, NULL, nhi, (void **)&ret); if (ACPI_FAILURE(status)) { - dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n"); + dev_warn(nhi->dev, "failed to enumerate tunneled ports\n"); return false; } @@ -305,7 +305,7 @@ static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw) struct tb_nhi *nhi = sw->tb->nhi; struct acpi_device *parent_adev; - parent_adev = ACPI_COMPANION(&nhi->pdev->dev); + parent_adev = ACPI_COMPANION(nhi->dev); if (parent_adev) adev = acpi_find_child_device(parent_adev, 0, false); } diff --git a/drivers/thunderbolt/configfs.c b/drivers/thunderbolt/configfs.c new file mode 100644 index 000000000000..dc6bc363dfe8 --- /dev/null +++ b/drivers/thunderbolt/configfs.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ConfigFS support + * + * Copyright (C) 2026, Intel Corporation + * Author: Mika Westerberg <mika.westerberg@linux.intel.com> + */ + +#include <linux/configfs.h> +#include <linux/export.h> + +#include "tb.h" + +static const struct config_item_type tb_root_group_type = { + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem tb_configfs = { + .su_group = { + .cg_item = { + .ci_namebuf = "thunderbolt", + .ci_type = &tb_root_group_type, + }, + }, +}; + +/** + * tb_configfs_register_group() - Register Thunderbolt ConfigFS group + * @group: Group to register. + * + * Registers the new @group under Thunderbolt subsystem ConfigFS. + * + * Return: 0% in case of success, negative errno otherwise. + */ +int tb_configfs_register_group(struct config_group *group) +{ + return configfs_register_group(&tb_configfs.su_group, group); +} +EXPORT_SYMBOL_GPL(tb_configfs_register_group); + +/** + * tb_configfs_unregister_group() - Unregister previously registered group + * @group: Group to unregister. + */ +void tb_configfs_unregister_group(struct config_group *group) +{ + configfs_unregister_group(group); +} +EXPORT_SYMBOL_GPL(tb_configfs_unregister_group); + +int tb_configfs_init(void) +{ + config_group_init(&tb_configfs.su_group); + mutex_init(&tb_configfs.su_mutex); + return configfs_register_subsystem(&tb_configfs); +} + +void tb_configfs_exit(void) +{ + configfs_unregister_subsystem(&tb_configfs); +} diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index b2fd60fc7bcc..cd47b627f97b 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -56,22 +56,22 @@ struct tb_ctl { #define tb_ctl_WARN(ctl, format, arg...) \ - dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_WARN((ctl)->nhi->dev, format, ## arg) #define tb_ctl_err(ctl, format, arg...) \ - dev_err(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_err((ctl)->nhi->dev, format, ## arg) #define tb_ctl_warn(ctl, format, arg...) \ - dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_warn((ctl)->nhi->dev, format, ## arg) #define tb_ctl_info(ctl, format, arg...) \ - dev_info(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_info((ctl)->nhi->dev, format, ## arg) #define tb_ctl_dbg(ctl, format, arg...) \ - dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_dbg((ctl)->nhi->dev, format, ## arg) #define tb_ctl_dbg_once(ctl, format, arg...) \ - dev_dbg_once(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_dbg_once((ctl)->nhi->dev, format, ## arg) static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); /* Serializes access to request kref_get/put */ @@ -666,8 +666,8 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec, mutex_init(&ctl->request_queue_lock); INIT_LIST_HEAD(&ctl->request_queue); - ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev, - TB_FRAME_SIZE, 4, 0); + ctl->frame_pool = dma_pool_create("thunderbolt_ctl", nhi->dev, + TB_FRAME_SIZE, 4, 0); if (!ctl->frame_pool) goto err; diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 042f6a0d0f7f..f5cf0e177f40 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -366,7 +366,7 @@ static ssize_t sb_regs_write(struct tb_port *port, const struct sb_reg *sb_regs, if (!sb_reg) return -EINVAL; - if (bytes_read > sb_regs->size) + if (bytes_read > sb_reg->size) return -E2BIG; ret = usb4_port_sb_write(port, target, index, sb_reg->reg, data, @@ -956,7 +956,9 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, else if (!strcmp(buf, "stop")) error_counter = USB4_MARGIN_SW_ERROR_COUNTER_STOP; else - return -EINVAL; + goto err_free; + + free_page((unsigned long)buf); scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { if (!margining->software) @@ -966,6 +968,10 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, } return count; + +err_free: + free_page((unsigned long)buf); + return -EINVAL; } static int margining_error_counter_show(struct seq_file *s, void *not_used) @@ -1780,6 +1786,8 @@ static void margining_port_remove(struct tb_port *port) if (!port->usb4) return; + if (!port->usb4->margining) + return; snprintf(dir_name, sizeof(dir_name), "port%d", port->port); parent = debugfs_lookup(dir_name, port->sw->debugfs_dir); @@ -2361,8 +2369,10 @@ static int sb_regs_show(struct tb_port *port, const struct sb_reg *sb_regs, memset(data, 0, sizeof(data)); ret = usb4_port_sb_read(port, target, index, regs->reg, data, regs->size); - if (ret) - return ret; + if (ret) { + seq_printf(s, "0x%02x <not accessible>\n", regs->reg); + continue; + } seq_printf(s, "0x%02x", regs->reg); for (j = 0; j < regs->size; j++) diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index b4aa79d482a0..7877319b1b03 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -87,7 +87,6 @@ static const char * const dma_test_result_names[] = { * @error_code: Error code of the last run * @complete: Used to wait for the Rx to complete * @lock: Lock serializing access to this structure - * @debugfs_dir: dentry of this dma_test */ struct dma_test { const struct tb_service *svc; @@ -108,7 +107,6 @@ struct dma_test { enum dma_test_test_error error_code; struct completion complete; struct mutex lock; - struct dentry *debugfs_dir; }; /* DMA test property directory UUID: 3188cd10-6523-4a5a-a682-fdca07a248d8 */ @@ -157,6 +155,8 @@ static int dma_test_start_rings(struct dma_test *dt) dt->tx_ring = ring; e2e_tx_hop = ring->hop; + tb_ring_throttling(ring, 128000); + ret = tb_xdomain_alloc_out_hopid(xd, -1); if (ret < 0) { dma_test_free_rings(dt); @@ -164,6 +164,7 @@ static int dma_test_start_rings(struct dma_test *dt) } dt->tx_hopid = ret; + } if (dt->packets_to_receive) { @@ -182,6 +183,8 @@ static int dma_test_start_rings(struct dma_test *dt) dt->rx_ring = ring; + tb_ring_throttling(ring, 128000); + ret = tb_xdomain_alloc_in_hopid(xd, -1); if (ret < 0) { dma_test_free_rings(dt); @@ -619,18 +622,18 @@ DEFINE_SHOW_ATTRIBUTE(status); static void dma_test_debugfs_init(struct tb_service *svc) { - struct dma_test *dt = tb_service_get_drvdata(svc); + struct dentry *debugfs_dir; - dt->debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir); + debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir); - debugfs_create_file("lanes", 0600, dt->debugfs_dir, svc, &lanes_fops); - debugfs_create_file("speed", 0600, dt->debugfs_dir, svc, &speed_fops); - debugfs_create_file("packets_to_receive", 0600, dt->debugfs_dir, svc, + debugfs_create_file("lanes", 0600, debugfs_dir, svc, &lanes_fops); + debugfs_create_file("speed", 0600, debugfs_dir, svc, &speed_fops); + debugfs_create_file("packets_to_receive", 0600, debugfs_dir, svc, &packets_to_receive_fops); - debugfs_create_file("packets_to_send", 0600, dt->debugfs_dir, svc, + debugfs_create_file("packets_to_send", 0600, debugfs_dir, svc, &packets_to_send_fops); - debugfs_create_file("status", 0400, dt->debugfs_dir, svc, &status_fops); - debugfs_create_file("test", 0200, dt->debugfs_dir, svc, &test_fops); + debugfs_create_file("status", 0400, debugfs_dir, svc, &status_fops); + 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) @@ -658,7 +661,7 @@ static void dma_test_remove(struct tb_service *svc) struct dma_test *dt = tb_service_get_drvdata(svc); mutex_lock(&dt->lock); - debugfs_remove_recursive(dt->debugfs_dir); + debugfs_lookup_and_remove("dma_test", svc->debugfs_dir); mutex_unlock(&dt->lock); } diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 317780b99992..479fa4d265c2 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -319,12 +319,15 @@ const struct bus_type tb_bus_type = { static void tb_domain_release(struct device *dev) { struct tb *tb = container_of(dev, struct tb, dev); + struct tb_nhi *nhi = tb->nhi; tb_ctl_free(tb->ctl); destroy_workqueue(tb->wq); ida_free(&tb_domain_ida, tb->index); mutex_destroy(&tb->lock); kfree(tb); + + complete(&nhi->domain_released); } const struct device_type tb_domain_type = { @@ -402,7 +405,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize if (!tb->ctl) goto err_destroy_wq; - tb->dev.parent = &nhi->pdev->dev; + tb->dev.parent = nhi->dev; tb->dev.bus = &tb_bus_type; tb->dev.type = &tb_domain_type; tb->dev.groups = domain_attr_groups; @@ -850,10 +853,41 @@ int tb_domain_disconnect_all_paths(struct tb *tb) return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain); } +struct unregister_context { + const struct tb *tb; + int n; +}; + +static int unregister_unplugged_xdomain(struct device *dev, void *data) +{ + struct unregister_context *ctx = data; + struct tb_xdomain *xd; + + xd = tb_to_xdomain(dev); + if (xd && xd->tb == ctx->tb && xd->is_unplugged) { + tb_xdomain_unregister(xd); + ctx->n++; + } + return 0; +} + +int tb_domain_unregister_unplugged_xdomains(struct tb *tb) +{ + struct unregister_context ctx; + + ctx.tb = tb_domain_get(tb); + ctx.n = 0; + bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain); + tb_domain_put(tb); + + return ctx.n; +} + int tb_domain_init(void) { int ret; + tb_configfs_init(); tb_debugfs_init(); tb_acpi_init(); @@ -883,4 +917,5 @@ void tb_domain_exit(void) tb_xdomain_exit(); tb_acpi_exit(); tb_debugfs_exit(); + tb_configfs_exit(); } diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 5477b9437048..5681c17f82ec 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -465,7 +465,7 @@ static void tb_switch_drom_free(struct tb_switch *sw) */ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) { - struct device *dev = &sw->tb->nhi->pdev->dev; + struct device *dev = sw->tb->nhi->dev; int len, res; len = device_property_count_u8(dev, "ThunderboltDROM"); diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 9d95bf3ab44c..10fefac3b1d9 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -587,6 +587,11 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, struct icm_fr_pkg_approve_xdomain request; int ret; + if (atomic_read(&xd->ntunnels) >= 1) { + tb_warn(tb, "only one tunnel is supported by the firmware\n"); + return -EOPNOTSUPP; + } + memset(&request, 0, sizeof(request)); request.hdr.code = ICM_APPROVE_XDOMAIN; request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link; @@ -738,6 +743,7 @@ static void remove_xdomain(struct tb_xdomain *xd) sw = tb_to_switch(xd->dev.parent); tb_port_at(xd->route, sw)->xdomain = NULL; + xd->is_unplugged = true; tb_xdomain_remove(xd); } @@ -1157,6 +1163,11 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, struct icm_tr_pkg_approve_xdomain request; int ret; + if (atomic_read(&xd->ntunnels) >= 1) { + tb_warn(tb, "only one tunnel is supported by the firmware\n"); + return -EOPNOTSUPP; + } + memset(&request, 0, sizeof(request)); request.hdr.code = ICM_APPROVE_XDOMAIN; request.route_hi = upper_32_bits(xd->route); @@ -1455,6 +1466,7 @@ static struct pci_dev *get_upstream_port(struct pci_dev *pdev) static bool icm_ar_is_supported(struct tb *tb) { + struct pci_dev *pdev = to_pci_dev(tb->nhi->dev); struct pci_dev *upstream_port; struct icm *icm = tb_priv(tb); @@ -1472,7 +1484,7 @@ static bool icm_ar_is_supported(struct tb *tb) * Find the upstream PCIe port in case we need to do reset * through its vendor specific registers. */ - upstream_port = get_upstream_port(tb->nhi->pdev); + upstream_port = get_upstream_port(pdev); if (upstream_port) { int cap; @@ -1508,7 +1520,7 @@ static int icm_ar_get_mode(struct tb *tb) } while (--retries); if (!retries) { - dev_err(&nhi->pdev->dev, "ICM firmware not authenticated\n"); + dev_err(nhi->dev, "ICM firmware not authenticated\n"); return -ENODEV; } @@ -1674,11 +1686,11 @@ icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level, static void icm_icl_set_uuid(struct tb *tb) { - struct tb_nhi *nhi = tb->nhi; + struct pci_dev *pdev = to_pci_dev(tb->nhi->dev); u32 uuid[4]; - pci_read_config_dword(nhi->pdev, VS_CAP_10, &uuid[0]); - pci_read_config_dword(nhi->pdev, VS_CAP_11, &uuid[1]); + pci_read_config_dword(pdev, VS_CAP_10, &uuid[0]); + pci_read_config_dword(pdev, VS_CAP_11, &uuid[1]); uuid[2] = 0xffffffff; uuid[3] = 0xffffffff; @@ -1762,6 +1774,8 @@ static void icm_handle_notification(struct work_struct *work) kfree(n->pkg); kfree(n); + + tb_domain_unregister_unplugged_xdomains(tb); } static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, @@ -1853,7 +1867,7 @@ static int icm_firmware_start(struct tb *tb, struct tb_nhi *nhi) if (icm_firmware_running(nhi)) return 0; - dev_dbg(&nhi->pdev->dev, "starting ICM firmware\n"); + dev_dbg(nhi->dev, "starting ICM firmware\n"); ret = icm_firmware_reset(tb, nhi); if (ret) @@ -1948,7 +1962,7 @@ static int icm_firmware_init(struct tb *tb) ret = icm_firmware_start(tb, nhi); if (ret) { - dev_err(&nhi->pdev->dev, "could not start ICM firmware\n"); + dev_err(nhi->dev, "could not start ICM firmware\n"); return ret; } @@ -1980,10 +1994,10 @@ static int icm_firmware_init(struct tb *tb) */ ret = icm_reset_phy_port(tb, 0); if (ret) - dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n"); + dev_warn(nhi->dev, "failed to reset links on port0\n"); ret = icm_reset_phy_port(tb, 1); if (ret) - dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n"); + dev_warn(nhi->dev, "failed to reset links on port1\n"); return 0; } @@ -2112,6 +2126,8 @@ static void icm_rescan_work(struct work_struct *work) if (tb->root_switch) icm_free_unplugged_children(tb->root_switch); mutex_unlock(&tb->lock); + + tb_domain_unregister_unplugged_xdomains(tb); } static void icm_complete(struct tb *tb) @@ -2462,6 +2478,7 @@ static const struct tb_cm_ops icm_icl_ops = { struct tb *icm_probe(struct tb_nhi *nhi) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); struct icm *icm; struct tb *tb; @@ -2473,7 +2490,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) INIT_DELAYED_WORK(&icm->rescan_work, icm_rescan_work); mutex_init(&icm->request_lock); - switch (nhi->pdev->device) { + switch (pdev->device) { case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: icm->can_upgrade_nvm = true; @@ -2579,7 +2596,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) } if (!icm->is_supported || !icm->is_supported(tb)) { - dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n"); + dev_dbg(nhi->dev, "ICM not supported on this controller\n"); tb_domain_put(tb); return NULL; } diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 2bb2e79ca3cb..0f795ea58756 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -2,7 +2,7 @@ /* * Thunderbolt driver - NHI driver * - * The NHI (native host interface) is the pci device that allows us to send and + * The NHI (native host interface) is the device that allows us to send and * receive frames from the thunderbolt bus. * * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> @@ -12,7 +12,6 @@ #include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/errno.h> -#include <linux/pci.h> #include <linux/dma-mapping.h> #include <linux/interrupt.h> #include <linux/iommu.h> @@ -34,19 +33,9 @@ * transferred. */ #define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID -/* - * Minimal number of vectors when we use MSI-X. Two for control channel - * Rx/Tx and the rest four are for cross domain DMA paths. - */ -#define MSIX_MIN_VECS 6 -#define MSIX_MAX_VECS 16 #define NHI_MAILBOX_TIMEOUT 500 /* ms */ -/* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) - static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); @@ -93,7 +82,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) u32 old, new; if (ring->irq > 0) { - u32 step, shift, ivr, misc; + u32 step, shift, ivr, misc, itr; void __iomem *ivr_base; int auto_clear_bit; int index; @@ -131,6 +120,12 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) if (active) ivr |= ring->vector << shift; iowrite32(ivr, ivr_base + step); + + /* Throttling is specified in 256ns increments */ + itr = DIV_ROUND_UP(ring->interval_nsec, 256); + itr &= REG_INT_THROTTLING_RATE_INTERVAL_MASK; + iowrite32(itr, ring->nhi->iobase + REG_INT_THROTTLING_RATE + + ring->vector * 4); } old = ioread32(ring->nhi->iobase + reg); @@ -139,15 +134,14 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) else new = old & ~mask; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "%s interrupt at register %#x bit %d (%#x -> %#x)\n", active ? "enabling" : "disabling", reg, interrupt_bit, old, new); if (new == old) - dev_WARN(&ring->nhi->pdev->dev, - "interrupt for %s %d is already %s\n", - RING_TYPE(ring), ring->hop, - str_enabled_disabled(active)); + dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n", + RING_TYPE(ring), ring->hop, + str_enabled_disabled(active)); if (active) iowrite32(new, ring->nhi->iobase + reg); @@ -160,7 +154,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) * * Use only during init and shutdown. */ -static void nhi_disable_interrupts(struct tb_nhi *nhi) +void nhi_disable_interrupts(struct tb_nhi *nhi) { int i = 0; /* disable interrupts */ @@ -319,6 +313,8 @@ invoke_callback: if (frame->callback) frame->callback(ring, frame, canceled); } + + wake_up(&ring->wait); } int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame) @@ -445,7 +441,7 @@ static void ring_clear_msix(const struct tb_ring *ring) 4 * (ring->nhi->hop_count / 32)); } -static irqreturn_t ring_msix(int irq, void *data) +irqreturn_t ring_msix(int irq, void *data) { struct tb_ring *ring = data; @@ -459,51 +455,6 @@ static irqreturn_t ring_msix(int irq, void *data) return IRQ_HANDLED; } -static int ring_request_msix(struct tb_ring *ring, bool no_suspend) -{ - struct tb_nhi *nhi = ring->nhi; - unsigned long irqflags; - int ret; - - if (!nhi->pdev->msix_enabled) - return 0; - - ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); - if (ret < 0) - return ret; - - ring->vector = ret; - - ret = pci_irq_vector(ring->nhi->pdev, ring->vector); - if (ret < 0) - goto err_ida_remove; - - ring->irq = ret; - - irqflags = no_suspend ? IRQF_NO_SUSPEND : 0; - ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring); - if (ret) - goto err_ida_remove; - - return 0; - -err_ida_remove: - ida_free(&nhi->msix_ida, ring->vector); - - return ret; -} - -static void ring_release_msix(struct tb_ring *ring) -{ - if (ring->irq <= 0) - return; - - free_irq(ring->irq, ring); - ida_free(&ring->nhi->msix_ida, ring->vector); - ring->vector = 0; - ring->irq = 0; -} - static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) { unsigned int start_hop = RING_FIRST_USABLE_HOPID; @@ -512,7 +463,7 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) if (nhi->quirks & QUIRK_E2E) { start_hop = RING_FIRST_USABLE_HOPID + 1; if (ring->flags & RING_FLAG_E2E && !ring->is_tx) { - dev_dbg(&nhi->pdev->dev, "quirking E2E TX HopID %u -> %u\n", + dev_dbg(nhi->dev, "quirking E2E TX HopID %u -> %u\n", ring->e2e_tx_hop, RING_E2E_RESERVED_HOPID); ring->e2e_tx_hop = RING_E2E_RESERVED_HOPID; } @@ -543,23 +494,23 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) } if (ring->hop > 0 && ring->hop < start_hop) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->hop < 0 || ring->hop >= nhi->hop_count) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->is_tx && nhi->tx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "TX hop %d already allocated\n", + dev_warn(nhi->dev, "TX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; } if (!ring->is_tx && nhi->rx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "RX hop %d already allocated\n", + dev_warn(nhi->dev, "RX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; @@ -584,7 +535,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, { struct tb_ring *ring = NULL; - dev_dbg(&nhi->pdev->dev, "allocating %s ring %d of size %d\n", + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", transmit ? "TX" : "RX", hop, size); ring = kzalloc_obj(*ring); @@ -595,6 +546,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, INIT_LIST_HEAD(&ring->queue); INIT_LIST_HEAD(&ring->in_flight); INIT_WORK(&ring->work, ring_work); + init_waitqueue_head(&ring->wait); ring->nhi = nhi; ring->hop = hop; @@ -610,14 +562,16 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->start_poll = start_poll; ring->poll_data = poll_data; - ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev, - size * sizeof(*ring->descriptors), - &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); + ring->descriptors = dma_alloc_coherent(ring->nhi->dev, + size * sizeof(*ring->descriptors), + &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); if (!ring->descriptors) goto err_free_ring; - if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND)) - goto err_free_descs; + if (nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } if (nhi_alloc_hop(nhi, ring)) goto err_release_msix; @@ -625,9 +579,10 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, return ring; err_release_msix: - ring_release_msix(ring); + if (nhi->ops->release_ring_irq) + nhi->ops->release_ring_irq(ring); err_free_descs: - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: @@ -694,10 +649,10 @@ void tb_ring_start(struct tb_ring *ring) if (ring->nhi->going_away) goto err; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "ring already started\n"); + dev_WARN(ring->nhi->dev, "ring already started\n"); goto err; } - dev_dbg(&ring->nhi->pdev->dev, "starting %s %d\n", + dev_dbg(ring->nhi->dev, "starting %s %d\n", RING_TYPE(ring), ring->hop); if (ring->flags & RING_FLAG_FRAME) { @@ -734,11 +689,11 @@ void tb_ring_start(struct tb_ring *ring) hop &= REG_RX_OPTIONS_E2E_HOP_MASK; flags |= hop; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d with TX HopID %d\n", RING_TYPE(ring), ring->hop, ring->e2e_tx_hop); } else { - dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d\n", + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d\n", RING_TYPE(ring), ring->hop); } @@ -754,6 +709,31 @@ err: } EXPORT_SYMBOL_GPL(tb_ring_start); +static bool tb_ring_empty(struct tb_ring *ring) +{ + guard(spinlock_irqsave)(&ring->lock); + return list_empty(&ring->in_flight); +} + +/** + * tb_ring_flush() - Waits for a ring to be empty + * @ring: Ring to wait + * @timeout_msec: Timeout in ms how long to wait. + * + * This can be called before stopping a ring to make sure all the frames + * submitted prior have been completed. + * + * Return: %true if the ring is empty now, %false otherwise. + */ +bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec) +{ + if (!wait_event_timeout(ring->wait, tb_ring_empty(ring), + msecs_to_jiffies(timeout_msec))) + return false; + return tb_ring_empty(ring); +} +EXPORT_SYMBOL_GPL(tb_ring_flush); + /** * tb_ring_stop() - shutdown a ring * @ring: Ring to stop @@ -772,12 +752,12 @@ void tb_ring_stop(struct tb_ring *ring) { spin_lock_irq(&ring->nhi->lock); spin_lock(&ring->lock); - dev_dbg(&ring->nhi->pdev->dev, "stopping %s %d\n", + dev_dbg(ring->nhi->dev, "stopping %s %d\n", RING_TYPE(ring), ring->hop); if (ring->nhi->going_away) goto err; if (!ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n", + dev_WARN(ring->nhi->dev, "%s %d already stopped\n", RING_TYPE(ring), ring->hop); goto err; } @@ -815,6 +795,8 @@ EXPORT_SYMBOL_GPL(tb_ring_stop); */ void tb_ring_free(struct tb_ring *ring) { + struct tb_nhi *nhi = ring->nhi; + spin_lock_irq(&ring->nhi->lock); /* * Dissociate the ring from the NHI. This also ensures that @@ -826,14 +808,15 @@ void tb_ring_free(struct tb_ring *ring) ring->nhi->rx_rings[ring->hop] = NULL; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n", + dev_WARN(ring->nhi->dev, "%s %d still running\n", RING_TYPE(ring), ring->hop); } spin_unlock_irq(&ring->nhi->lock); - ring_release_msix(ring); + if (nhi->ops->release_ring_irq) + nhi->ops->release_ring_irq(ring); - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); @@ -841,7 +824,7 @@ void tb_ring_free(struct tb_ring *ring) ring->descriptors_dma = 0; - dev_dbg(&ring->nhi->pdev->dev, "freeing %s %d\n", RING_TYPE(ring), + dev_dbg(ring->nhi->dev, "freeing %s %d\n", RING_TYPE(ring), ring->hop); /* @@ -855,6 +838,26 @@ void tb_ring_free(struct tb_ring *ring) EXPORT_SYMBOL_GPL(tb_ring_free); /** + * tb_ring_throttling() - Configure throttling for ring interrupt + * @ring: Ring to configure + * @interval_nsec: Interval counter for moderation (in ns), %0 disables + * + * Enables or disables ring interrupt throttling. The ring must be + * stopped for this to be called. Granularity is 256 ns. + * + * Return: %0 on success, negative errno otherwise. + */ +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec) +{ + guard(spinlock_irqsave)(&ring->lock); + if (WARN_ON_ONCE(ring->running)) + return -EBUSY; + ring->interval_nsec = interval_nsec; + return 0; +} +EXPORT_SYMBOL_GPL(tb_ring_throttling); + +/** * nhi_mailbox_cmd() - Send a command through NHI mailbox * @nhi: Pointer to the NHI structure * @cmd: Command to send @@ -912,7 +915,7 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi) return (enum nhi_fw_mode)val; } -static void nhi_interrupt_work(struct work_struct *work) +void nhi_interrupt_work(struct work_struct *work) { struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work); int value = 0; /* Suppress uninitialized usage warning. */ @@ -940,9 +943,7 @@ static void nhi_interrupt_work(struct work_struct *work) if ((value & (1 << (bit % 32))) == 0) continue; if (type == 2) { - dev_warn(&nhi->pdev->dev, - "RX overflow for ring %d\n", - hop); + dev_warn(nhi->dev, "RX overflow for ring %d\n", hop); continue; } if (type == 0) @@ -950,7 +951,7 @@ static void nhi_interrupt_work(struct work_struct *work) else ring = nhi->rx_rings[hop]; if (ring == NULL) { - dev_warn(&nhi->pdev->dev, + dev_warn(nhi->dev, "got interrupt for inactive %s ring %d\n", type ? "RX" : "TX", hop); @@ -964,7 +965,7 @@ static void nhi_interrupt_work(struct work_struct *work) spin_unlock_irq(&nhi->lock); } -static irqreturn_t nhi_msi(int irq, void *data) +irqreturn_t nhi_msi(int irq, void *data) { struct tb_nhi *nhi = data; schedule_work(&nhi->interrupt_work); @@ -973,8 +974,7 @@ static irqreturn_t nhi_msi(int irq, void *data) static int __nhi_suspend_noirq(struct device *dev, bool wakeup) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -982,7 +982,7 @@ static int __nhi_suspend_noirq(struct device *dev, bool wakeup) if (ret) return ret; - if (nhi->ops && nhi->ops->suspend_noirq) { + if (nhi->ops->suspend_noirq) { ret = nhi->ops->suspend_noirq(tb->nhi, wakeup); if (ret) return ret; @@ -998,21 +998,19 @@ static int nhi_suspend_noirq(struct device *dev) static int nhi_freeze_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_freeze_noirq(tb); } static int nhi_thaw_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_thaw_noirq(tb); } -static bool nhi_wake_supported(struct pci_dev *pdev) +static bool nhi_wake_supported(struct device *dev) { u8 val; @@ -1020,7 +1018,7 @@ static bool nhi_wake_supported(struct pci_dev *pdev) * If power rails are sustainable for wakeup from S4 this * property is set by the BIOS. */ - if (!device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val)) + if (!device_property_read_u8(dev, "WAKE_SUPPORTED", &val)) return !!val; return true; @@ -1028,33 +1026,15 @@ static bool nhi_wake_supported(struct pci_dev *pdev) static int nhi_poweroff_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); bool wakeup; - wakeup = device_may_wakeup(dev) && nhi_wake_supported(pdev); + wakeup = device_may_wakeup(dev) && nhi_wake_supported(dev); return __nhi_suspend_noirq(dev, wakeup); } -static void nhi_enable_int_throttling(struct tb_nhi *nhi) -{ - /* Throttling is specified in 256ns increments */ - u32 throttle = DIV_ROUND_UP(128 * NSEC_PER_USEC, 256); - unsigned int i; - - /* - * Configure interrupt throttling for all vectors even if we - * only use few. - */ - for (i = 0; i < MSIX_MAX_VECS; i++) { - u32 reg = REG_INT_THROTTLING_RATE + i * 4; - iowrite32(throttle, nhi->iobase + reg); - } -} - static int nhi_resume_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1063,15 +1043,12 @@ static int nhi_resume_noirq(struct device *dev) * unplugged last device which causes the host controller to go * away on PCs. */ - if (!pci_device_is_present(pdev)) { + if ((nhi->ops->is_present && !nhi->ops->is_present(nhi))) { nhi->going_away = true; - } else { - if (nhi->ops && nhi->ops->resume_noirq) { - ret = nhi->ops->resume_noirq(nhi); - if (ret) - return ret; - } - nhi_enable_int_throttling(tb->nhi); + } else if (nhi->ops->resume_noirq) { + ret = nhi->ops->resume_noirq(nhi); + if (ret) + return ret; } return tb_domain_resume_noirq(tb); @@ -1079,32 +1056,29 @@ static int nhi_resume_noirq(struct device *dev) static int nhi_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_suspend(tb); } static void nhi_complete(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); /* * If we were runtime suspended when system suspend started, * schedule runtime resume now. It should bring the domain back * to functional state. */ - if (pm_runtime_suspended(&pdev->dev)) - pm_runtime_resume(&pdev->dev); + if (pm_runtime_suspended(dev)) + pm_runtime_resume(dev); else tb_domain_complete(tb); } static int nhi_runtime_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1112,7 +1086,7 @@ static int nhi_runtime_suspend(struct device *dev) if (ret) return ret; - if (nhi->ops && nhi->ops->runtime_suspend) { + if (nhi->ops->runtime_suspend) { ret = nhi->ops->runtime_suspend(tb->nhi); if (ret) return ret; @@ -1122,115 +1096,39 @@ static int nhi_runtime_suspend(struct device *dev) static int nhi_runtime_resume(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; - if (nhi->ops && nhi->ops->runtime_resume) { + if (nhi->ops->runtime_resume) { ret = nhi->ops->runtime_resume(nhi); if (ret) return ret; } - nhi_enable_int_throttling(nhi); return tb_domain_runtime_resume(tb); } -static void nhi_shutdown(struct tb_nhi *nhi) +void nhi_shutdown(struct tb_nhi *nhi) { int i; - dev_dbg(&nhi->pdev->dev, "shutdown\n"); + dev_dbg(nhi->dev, "shutdown\n"); for (i = 0; i < nhi->hop_count; i++) { if (nhi->tx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "TX ring %d is still active\n", i); if (nhi->rx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "RX ring %d is still active\n", i); } nhi_disable_interrupts(nhi); - /* - * We have to release the irq before calling flush_work. Otherwise an - * already executing IRQ handler could call schedule_work again. - */ - if (!nhi->pdev->msix_enabled) { - devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi); - flush_work(&nhi->interrupt_work); - } - ida_destroy(&nhi->msix_ida); - if (nhi->ops && nhi->ops->shutdown) + if (nhi->ops->shutdown) nhi->ops->shutdown(nhi); } -static void nhi_check_quirks(struct tb_nhi *nhi) -{ - if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL) { - /* - * Intel hardware supports auto clear of the interrupt - * status register right after interrupt is being - * issued. - */ - nhi->quirks |= QUIRK_AUTO_CLEAR_INT; - - switch (nhi->pdev->device) { - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: - /* - * Falcon Ridge controller needs the end-to-end - * flow control workaround to avoid losing Rx - * packets when RING_FLAG_E2E is set. - */ - nhi->quirks |= QUIRK_E2E; - break; - } - } -} - -static int nhi_check_iommu_pdev(struct pci_dev *pdev, void *data) -{ - if (!pdev->external_facing || - !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) - return 0; - *(bool *)data = true; - return 1; /* Stop walking */ -} - -static void nhi_check_iommu(struct tb_nhi *nhi) -{ - struct pci_bus *bus = nhi->pdev->bus; - bool port_ok = false; - - /* - * Ideally what we'd do here is grab every PCI device that - * represents a tunnelling adapter for this NHI and check their - * status directly, but unfortunately USB4 seems to make it - * obnoxiously difficult to reliably make any correlation. - * - * So for now we'll have to bodge it... Hoping that the system - * is at least sane enough that an adapter is in the same PCI - * segment as its NHI, if we can find *something* on that segment - * which meets the requirements for Kernel DMA Protection, we'll - * take that to imply that firmware is aware and has (hopefully) - * done the right thing in general. We need to know that the PCI - * layer has seen the ExternalFacingPort property which will then - * inform the IOMMU layer to enforce the complete "untrusted DMA" - * flow, but also that the IOMMU driver itself can be trusted not - * to have been subverted by a pre-boot DMA attack. - */ - while (bus->parent) - bus = bus->parent; - - pci_walk_bus(bus, nhi_check_iommu_pdev, &port_ok); - - nhi->iommu_dma_protection = port_ok; - dev_dbg(&nhi->pdev->dev, "IOMMU DMA protection is %s\n", - str_enabled_disabled(port_ok)); -} - static void nhi_reset(struct tb_nhi *nhi) { ktime_t timeout; @@ -1242,7 +1140,7 @@ static void nhi_reset(struct tb_nhi *nhi) return; if (!host_reset) { - dev_dbg(&nhi->pdev->dev, "skipping host router reset\n"); + dev_dbg(nhi->dev, "skipping host router reset\n"); return; } @@ -1253,64 +1151,13 @@ static void nhi_reset(struct tb_nhi *nhi) do { val = ioread32(nhi->iobase + REG_RESET); if (!(val & REG_RESET_HRR)) { - dev_warn(&nhi->pdev->dev, "host router reset successful\n"); + dev_warn(nhi->dev, "host router reset successful\n"); return; } usleep_range(10, 20); } while (ktime_before(ktime_get(), timeout)); - dev_warn(&nhi->pdev->dev, "timeout resetting host router\n"); -} - -static int nhi_init_msi(struct tb_nhi *nhi) -{ - struct pci_dev *pdev = nhi->pdev; - struct device *dev = &pdev->dev; - int res, irq, nvec; - - /* In case someone left them on. */ - nhi_disable_interrupts(nhi); - - nhi_enable_int_throttling(nhi); - - ida_init(&nhi->msix_ida); - - /* - * The NHI has 16 MSI-X vectors or a single MSI. We first try to - * get all MSI-X vectors and if we succeed, each ring will have - * one MSI-X. If for some reason that does not work out, we - * fallback to a single MSI. - */ - nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS, - PCI_IRQ_MSIX); - if (nvec < 0) { - nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); - if (nvec < 0) - return nvec; - - INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); - - irq = pci_irq_vector(nhi->pdev, 0); - if (irq < 0) - return irq; - - 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 0; -} - -static bool nhi_imr_valid(struct pci_dev *pdev) -{ - u8 val; - - if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) - return !!val; - - return true; + dev_warn(nhi->dev, "timeout resetting host router\n"); } static struct tb *nhi_select_cm(struct tb_nhi *nhi) @@ -1336,62 +1183,47 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi) return tb; } -static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) +int nhi_probe(struct tb_nhi *nhi) { - struct device *dev = &pdev->dev; - struct tb_nhi *nhi; + struct device *dev = nhi->dev; struct tb *tb; int res; - if (!nhi_imr_valid(pdev)) - return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); + if (!nhi->ops) + return dev_err_probe(dev, -EINVAL, "NHI ops not set\n"); - res = pcim_enable_device(pdev); - if (res) - return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); - - nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL); - if (!nhi) - return -ENOMEM; - - nhi->pdev = pdev; - nhi->ops = (const struct tb_nhi_ops *)id->driver_data; - - nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); - res = PTR_ERR_OR_ZERO(nhi->iobase); - if (res) - return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); + if (!nhi->ops->init_interrupts) + return dev_err_probe(dev, -EINVAL, "missing required NHI ops\n"); nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); - nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->tx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->tx_rings), GFP_KERNEL); - nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->rx_rings), GFP_KERNEL); if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; - nhi_check_quirks(nhi); - nhi_check_iommu(nhi); nhi_reset(nhi); - res = nhi_init_msi(nhi); + /* In case someone left them on. */ + nhi_disable_interrupts(nhi); + + res = nhi->ops->init_interrupts(nhi); if (res) - return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); + return dev_err_probe(dev, res, "cannot enable interrupts, aborting\n"); spin_lock_init(&nhi->lock); - res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + res = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (res) return dev_err_probe(dev, res, "failed to set DMA mask\n"); - pci_set_master(pdev); - - if (nhi->ops && nhi->ops->init) { + if (nhi->ops->init) { res = nhi->ops->init(nhi); if (res) - return res; + return dev_err_probe(dev, res, "NHI specific init failed\n"); } tb = nhi_select_cm(nhi); @@ -1401,6 +1233,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + init_completion(&nhi->domain_released); + res = tb_domain_add(tb, host_reset); if (res) { /* @@ -1408,40 +1242,28 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) * activated. Do a proper shutdown. */ tb_domain_put(tb); + wait_for_completion(&nhi->domain_released); nhi_shutdown(nhi); - return res; + return dev_err_probe(dev, res, "failed to add domain\n"); } - pci_set_drvdata(pdev, tb); + dev_set_drvdata(dev, tb); - device_wakeup_enable(&pdev->dev); + device_wakeup_enable(dev); - pm_runtime_allow(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_put_autosuspend(&pdev->dev); + pm_runtime_allow(dev); + pm_runtime_set_autosuspend_delay(dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_put_autosuspend(dev); return 0; } -static void nhi_remove(struct pci_dev *pdev) -{ - struct tb *tb = pci_get_drvdata(pdev); - struct tb_nhi *nhi = tb->nhi; - - pm_runtime_get_sync(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_forbid(&pdev->dev); - - tb_domain_remove(tb); - nhi_shutdown(nhi); -} - /* * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable * the tunnels asap. A corresponding pci quirk blocks the downstream bridges * resume_noirq until we are done. */ -static const struct dev_pm_ops nhi_pm_ops = { +const struct dev_pm_ops nhi_pm_ops = { .suspend_noirq = nhi_suspend_noirq, .resume_noirq = nhi_resume_noirq, .freeze_noirq = nhi_freeze_noirq, /* @@ -1457,129 +1279,3 @@ static const struct dev_pm_ops nhi_pm_ops = { .runtime_suspend = nhi_runtime_suspend, .runtime_resume = nhi_runtime_resume, }; - -static struct pci_device_id nhi_ids[] = { - /* - * We have to specify class, the TB bridges use the same device and - * vendor (sub)id on gen 1 and gen 2 controllers. - */ - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE, - .subvendor = 0x2222, .subdevice = 0x1111, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C, - .subvendor = 0x2222, .subdevice = 0x1111, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI, - .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI, - .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - }, - - /* Thunderbolt 3 */ - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - /* Thunderbolt 4 */ - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, - - /* Any USB4 compliant host */ - { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, - - { 0,} -}; - -MODULE_DEVICE_TABLE(pci, nhi_ids); -MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); -MODULE_LICENSE("GPL"); - -static struct pci_driver nhi_driver = { - .name = "thunderbolt", - .id_table = nhi_ids, - .probe = nhi_probe, - .remove = nhi_remove, - .shutdown = nhi_remove, - .driver.pm = &nhi_pm_ops, -}; - -static int __init nhi_init(void) -{ - int ret; - - ret = tb_domain_init(); - if (ret) - return ret; - ret = pci_register_driver(&nhi_driver); - if (ret) - tb_domain_exit(); - return ret; -} - -static void __exit nhi_unload(void) -{ - pci_unregister_driver(&nhi_driver); - tb_domain_exit(); -} - -rootfs_initcall(nhi_init); -module_exit(nhi_unload); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 24ac4246d0ca..d488eadadfce 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -29,6 +29,14 @@ enum nhi_mailbox_cmd { int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data); enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); +void nhi_enable_int_throttling(struct tb_nhi *nhi); +void nhi_disable_interrupts(struct tb_nhi *nhi); +void nhi_interrupt_work(struct work_struct *work); +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); +extern const struct dev_pm_ops nhi_pm_ops; /** * struct tb_nhi_ops - NHI specific optional operations @@ -38,6 +46,12 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); * @runtime_suspend: NHI specific runtime_suspend hook * @runtime_resume: NHI specific runtime_resume hook * @shutdown: NHI specific shutdown + * @pre_nvm_auth: hook to run before Thunderbolt 3 NVM authentication + * @post_nvm_auth: hook to run after Thunderbolt 3 NVM authentication + * @request_ring_irq: NHI specific interrupt retrieval hook + * @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 */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -46,10 +60,14 @@ struct tb_nhi_ops { int (*runtime_suspend)(struct tb_nhi *nhi); int (*runtime_resume)(struct tb_nhi *nhi); void (*shutdown)(struct tb_nhi *nhi); + void (*pre_nvm_auth)(struct tb_nhi *nhi); + void (*post_nvm_auth)(struct tb_nhi *nhi); + int (*request_ring_irq)(struct tb_ring *ring, bool no_suspend); + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); }; -extern const struct tb_nhi_ops icl_nhi_ops; - /* * PCI IDs used in this driver from Win Ridge forward. There is no * need for the PCI quirk anymore as we will use ICM also on Apple @@ -100,4 +118,15 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 +/* Host interface quirks */ +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) + +/* + * Minimal number of vectors when we use MSI-X. Two for control channel + * Rx/Tx and the rest four are for cross domain DMA paths. + */ +#define MSIX_MIN_VECS 6 +#define MSIX_MAX_VECS 16 + #endif diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c deleted file mode 100644 index 96da07e88c52..000000000000 --- a/drivers/thunderbolt/nhi_ops.c +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * NHI specific operations - * - * Copyright (C) 2019, Intel Corporation - * Author: Mika Westerberg <mika.westerberg@linux.intel.com> - */ - -#include <linux/delay.h> -#include <linux/suspend.h> - -#include "nhi.h" -#include "nhi_regs.h" -#include "tb.h" - -/* Ice Lake specific NHI operations */ - -#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ - -static int check_for_device(struct device *dev, void *data) -{ - return tb_is_switch(dev); -} - -static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) -{ - struct tb *tb = pci_get_drvdata(nhi->pdev); - int ret; - - ret = device_for_each_child(&tb->root_switch->dev, NULL, - check_for_device); - return ret > 0; -} - -static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) -{ - u32 vs_cap; - - /* - * The Thunderbolt host controller is present always in Ice Lake - * but the firmware may not be loaded and running (depending - * whether there is device connected and so on). Each time the - * controller is used we need to "Force Power" it first and wait - * for the firmware to indicate it is up and running. This "Force - * Power" is really not about actually powering on/off the - * controller so it is accessible even if "Force Power" is off. - * - * The actual power management happens inside shared ACPI power - * resources using standard ACPI methods. - */ - pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap); - if (power) { - vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; - vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; - vs_cap |= VS_CAP_22_FORCE_POWER; - } else { - vs_cap &= ~VS_CAP_22_FORCE_POWER; - } - pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap); - - if (power) { - unsigned int retries = 350; - u32 val; - - /* Wait until the firmware tells it is up and running */ - do { - pci_read_config_dword(nhi->pdev, VS_CAP_9, &val); - if (val & VS_CAP_9_FW_READY) - return 0; - usleep_range(3000, 3100); - } while (--retries); - - return -ETIMEDOUT; - } - - return 0; -} - -static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) -{ - u32 data; - - data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; - pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID); -} - -static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) -{ - unsigned long end; - u32 data; - - if (!timeout) - goto clear; - - end = jiffies + msecs_to_jiffies(timeout); - do { - pci_read_config_dword(nhi->pdev, VS_CAP_18, &data); - if (data & VS_CAP_18_DONE) - goto clear; - usleep_range(1000, 1100); - } while (time_before(jiffies, end)); - - return -ETIMEDOUT; - -clear: - /* Clear the valid bit */ - pci_write_config_dword(nhi->pdev, VS_CAP_19, 0); - return 0; -} - -static void icl_nhi_set_ltr(struct tb_nhi *nhi) -{ - u32 max_ltr, ltr; - - pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr); - max_ltr &= 0xffff; - /* Program the same value for both snoop and no-snoop */ - ltr = max_ltr << 16 | max_ltr; - pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr); -} - -static int icl_nhi_suspend(struct tb_nhi *nhi) -{ - struct tb *tb = pci_get_drvdata(nhi->pdev); - int ret; - - if (icl_nhi_is_device_connected(nhi)) - return 0; - - if (tb_switch_is_icm(tb->root_switch)) { - /* - * If there is no device connected we need to perform - * both: a handshake through LC mailbox and force power - * down before entering D3. - */ - icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); - ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); - if (ret) - return ret; - } - - return icl_nhi_force_power(nhi, false); -} - -static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) -{ - struct tb *tb = pci_get_drvdata(nhi->pdev); - enum icl_lc_mailbox_cmd cmd; - - if (!pm_suspend_via_firmware()) - return icl_nhi_suspend(nhi); - - if (!tb_switch_is_icm(tb->root_switch)) - return 0; - - cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; - icl_nhi_lc_mailbox_cmd(nhi, cmd); - return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); -} - -static int icl_nhi_resume(struct tb_nhi *nhi) -{ - int ret; - - ret = icl_nhi_force_power(nhi, true); - if (ret) - return ret; - - icl_nhi_set_ltr(nhi); - return 0; -} - -static void icl_nhi_shutdown(struct tb_nhi *nhi) -{ - icl_nhi_force_power(nhi, false); -} - -const struct tb_nhi_ops icl_nhi_ops = { - .init = icl_nhi_resume, - .suspend_noirq = icl_nhi_suspend_noirq, - .resume_noirq = icl_nhi_resume, - .runtime_suspend = icl_nhi_suspend, - .runtime_resume = icl_nhi_resume, - .shutdown = icl_nhi_shutdown, -}; diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index cf5222bee971..d6a197fabc74 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -101,7 +101,8 @@ struct ring_desc { #define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208 -#define REG_INT_THROTTLING_RATE 0x38c00 +#define REG_INT_THROTTLING_RATE 0x38c00 +#define REG_INT_THROTTLING_RATE_INTERVAL_MASK GENMASK(15, 0) /* Interrupt Vector Allocation */ #define REG_INT_VEC_ALLOC_BASE 0x38c40 diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index 8713ea0f47c1..b2c322e76b8a 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -412,7 +412,8 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, * in the USB4 spec so we clear them * only for pre-USB4 adapters. */ - if (!tb_switch_is_usb4(port->sw)) { + if (tb_port_is_null(port) || + !tb_switch_is_usb4(port->sw)) { hop.ingress_fc = 0; hop.ingress_shared_buffer = 0; } @@ -483,7 +484,7 @@ void tb_path_deactivate(struct tb_path *path) * tb_path_activate() - activate a path * @path: Path to activate * - * Activate a path starting with the last hop and iterating backwards. The + * Activate a path starting with the first hop and ending on the last hop. The * caller must fill path->hops before calling tb_path_activate(). * * Return: %0 on success, negative errno otherwise. @@ -525,22 +526,25 @@ int tb_path_activate(struct tb_path *path) } /* Activate hops. */ - for (i = path->path_length - 1; i >= 0; i--) { + for (i = 0; i < path->path_length; i++) { struct tb_regs_hop hop = { 0 }; /* If it is left active deactivate it first */ __tb_path_deactivate_hop(path->hops[i].in_port, path->hops[i].in_hop_index, path->clear_fc); - /* dword 0 */ + /* Needed for USB4 routers, read path config space before write */ + res = tb_port_read(path->hops[i].in_port, &hop, TB_CFG_HOPS, + 2 * path->hops[i].in_hop_index, 2); + if (res) + goto err; + hop.next_hop = path->hops[i].next_hop_index; hop.out_port = path->hops[i].out_port->port; - hop.initial_credits = path->hops[i].initial_credits; hop.pmps = path->hops[i].pm_support; hop.unknown1 = 0; hop.enable = 1; - /* dword 1 */ out_mask = (i == path->path_length - 1) ? TB_PATH_DESTINATION : TB_PATH_INTERNAL; in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL; @@ -550,12 +554,21 @@ int tb_path_activate(struct tb_path *path) hop.drop_packages = path->drop_packages; hop.counter = path->hops[i].in_counter_index; hop.counter_enable = path->hops[i].in_counter_index != -1; - hop.ingress_fc = path->ingress_fc_enable & in_mask; hop.egress_fc = path->egress_fc_enable & out_mask; - hop.ingress_shared_buffer = path->ingress_shared_buffer - & in_mask; - hop.egress_shared_buffer = path->egress_shared_buffer - & out_mask; + hop.egress_shared_buffer = path->egress_shared_buffer & out_mask; + /* + * Protocol adapters IFC and ISE bits, and Path Credits + * Allocated are vendor defined in the USB4 spec so we + * program them only for pre-USB4 and lane adapters. + */ + if (tb_port_is_null(path->hops[i].in_port) || + !tb_switch_is_usb4(path->hops[i].in_port->sw)) { + hop.initial_credits = path->hops[i].initial_credits; + hop.ingress_fc = path->ingress_fc_enable & in_mask; + hop.ingress_shared_buffer = + path->ingress_shared_buffer & in_mask; + } + hop.unknown3 = 0; tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i); @@ -563,7 +576,7 @@ int tb_path_activate(struct tb_path *path) res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, 2 * path->hops[i].in_hop_index, 2); if (res) { - __tb_path_deactivate_hops(path, i); + __tb_path_deactivate_hops(path, 0); __tb_path_deallocate_nfc(path, 0); goto err; } diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c new file mode 100644 index 000000000000..bbd186c29ef7 --- /dev/null +++ b/drivers/thunderbolt/pci.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Thunderbolt driver - PCI NHI driver + * + * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> + * Copyright (C) 2018, Intel Corporation + */ + +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/iommu.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/property.h> +#include <linux/string_helpers.h> +#include <linux/suspend.h> + +#include "nhi.h" +#include "nhi_regs.h" +#include "tb.h" + +/** + * struct tb_nhi_pci - NHI device connected over PCIe + * @nhi: NHI device + * @msix_ida: Used to allocate MSI-X vectors for rings + */ +struct tb_nhi_pci { + struct tb_nhi nhi; + struct ida msix_ida; +}; + +static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi) +{ + return container_of(nhi, struct tb_nhi_pci, nhi); +} + +static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) +{ + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { + /* + * Intel hardware supports auto clear of the interrupt + * status register right after interrupt is being + * issued. + */ + nhi->quirks |= QUIRK_AUTO_CLEAR_INT; + + switch (pdev->device) { + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: + /* + * Falcon Ridge controller needs the end-to-end + * flow control workaround to avoid losing Rx + * packets when RING_FLAG_E2E is set. + */ + nhi->quirks |= QUIRK_E2E; + break; + } + } +} + +static int nhi_pci_check_iommu_pdev(struct pci_dev *pdev, void *data) +{ + if (!pdev->external_facing || + !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) + return 0; + *(bool *)data = true; + return 1; /* Stop walking */ +} + +static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci) +{ + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_bus *bus = pdev->bus; + bool port_ok = false; + + /* + * Ideally what we'd do here is grab every PCI device that + * represents a tunnelling adapter for this NHI and check their + * status directly, but unfortunately USB4 seems to make it + * obnoxiously difficult to reliably make any correlation. + * + * So for now we'll have to bodge it... Hoping that the system + * is at least sane enough that an adapter is in the same PCI + * segment as its NHI, if we can find *something* on that segment + * which meets the requirements for Kernel DMA Protection, we'll + * take that to imply that firmware is aware and has (hopefully) + * done the right thing in general. We need to know that the PCI + * layer has seen the ExternalFacingPort property which will then + * inform the IOMMU layer to enforce the complete "untrusted DMA" + * flow, but also that the IOMMU driver itself can be trusted not + * to have been subverted by a pre-boot DMA attack. + */ + while (bus->parent) + bus = bus->parent; + + pci_walk_bus(bus, nhi_pci_check_iommu_pdev, &port_ok); + + nhi->iommu_dma_protection = port_ok; + dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n", + str_enabled_disabled(port_ok)); +} + +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); + + /* + * The NHI has 16 MSI-X vectors or a single MSI. We first try to + * get all MSI-X vectors and if we succeed, each ring will have + * one MSI-X. If for some reason that does not work out, we + * fallback to a single MSI. + */ + nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS, + PCI_IRQ_MSIX); + if (nvec < 0) { + nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + if (nvec < 0) + return nvec; + + INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); + + irq = pci_irq_vector(pdev, 0); + if (irq < 0) + return irq; + + 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 0; +} + +static bool nhi_pci_imr_valid(struct pci_dev *pdev) +{ + u8 val; + + if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) + return !!val; + + return true; +} + +static void nhi_pci_start_dma_port(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_dev *root_port; + + /* + * During host router NVM upgrade we should not allow root port to + * go into D3cold because some root ports cannot trigger PME + * itself. To be on the safe side keep the root port in D0 during + * the whole upgrade process. + */ + root_port = pcie_find_root_port(pdev); + if (root_port) + pm_runtime_get_noresume(&root_port->dev); +} + +static void nhi_pci_complete_dma_port(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_dev *root_port; + + root_port = pcie_find_root_port(pdev); + if (root_port) + pm_runtime_put(&root_port->dev); +} + +static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend) +{ + struct tb_nhi *nhi = ring->nhi; + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + unsigned long irqflags; + int ret; + + if (!pdev->msix_enabled) + return 0; + + ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); + if (ret < 0) + return ret; + + ring->vector = ret; + + ret = pci_irq_vector(pdev, ring->vector); + if (ret < 0) + goto err_ida_remove; + + ring->irq = ret; + + irqflags = no_suspend ? IRQF_NO_SUSPEND : 0; + ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring); + if (ret) + goto err_ida_remove; + + return 0; + +err_ida_remove: + ida_free(&nhi_pci->msix_ida, ring->vector); + + return ret; +} + +static void nhi_pci_ring_release_msix(struct tb_ring *ring) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi); + + if (ring->irq <= 0) + return; + + free_irq(ring->irq, ring); + ida_free(&nhi_pci->msix_ida, ring->vector); + ring->vector = 0; + ring->irq = 0; +} + +static void nhi_pci_shutdown(struct tb_nhi *nhi) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + /* + * We have to release the irq before calling flush_work. Otherwise an + * already executing IRQ handler could call schedule_work again. + */ + if (!pdev->msix_enabled) { + devm_free_irq(nhi->dev, pdev->irq, nhi); + flush_work(&nhi->interrupt_work); + } + ida_destroy(&nhi_pci->msix_ida); +} + +static bool nhi_pci_is_present(struct tb_nhi *nhi) +{ + return pci_device_is_present(to_pci_dev(nhi->dev)); +} + +static const struct tb_nhi_ops pci_nhi_default_ops = { + .pre_nvm_auth = nhi_pci_start_dma_port, + .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, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +}; + +/* Ice Lake specific NHI operations */ + +#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ + +static int check_for_device(struct device *dev, void *data) +{ + return tb_is_switch(dev); +} + +static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + int ret; + + ret = device_for_each_child(&tb->root_switch->dev, NULL, + check_for_device); + return ret > 0; +} + +static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 vs_cap; + + /* + * The Thunderbolt host controller is present always in Ice Lake + * but the firmware may not be loaded and running (depending + * whether there is device connected and so on). Each time the + * controller is used we need to "Force Power" it first and wait + * for the firmware to indicate it is up and running. This "Force + * Power" is really not about actually powering on/off the + * controller so it is accessible even if "Force Power" is off. + * + * The actual power management happens inside shared ACPI power + * resources using standard ACPI methods. + */ + pci_read_config_dword(pdev, VS_CAP_22, &vs_cap); + if (power) { + vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; + vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; + vs_cap |= VS_CAP_22_FORCE_POWER; + } else { + vs_cap &= ~VS_CAP_22_FORCE_POWER; + } + pci_write_config_dword(pdev, VS_CAP_22, vs_cap); + + if (power) { + unsigned int retries = 350; + u32 val; + + /* Wait until the firmware tells it is up and running */ + do { + pci_read_config_dword(pdev, VS_CAP_9, &val); + if (val & VS_CAP_9_FW_READY) + return 0; + usleep_range(3000, 3100); + } while (--retries); + + return -ETIMEDOUT; + } + + return 0; +} + +static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 data; + + data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; + pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID); +} + +static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + unsigned long end; + u32 data; + + if (!timeout) + goto clear; + + end = jiffies + msecs_to_jiffies(timeout); + do { + pci_read_config_dword(pdev, VS_CAP_18, &data); + if (data & VS_CAP_18_DONE) + goto clear; + usleep_range(1000, 1100); + } while (time_before(jiffies, end)); + + return -ETIMEDOUT; + +clear: + /* Clear the valid bit */ + pci_write_config_dword(pdev, VS_CAP_19, 0); + return 0; +} + +static void icl_nhi_set_ltr(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 max_ltr, ltr; + + pci_read_config_dword(pdev, VS_CAP_16, &max_ltr); + max_ltr &= 0xffff; + /* Program the same value for both snoop and no-snoop */ + ltr = max_ltr << 16 | max_ltr; + pci_write_config_dword(pdev, VS_CAP_15, ltr); +} + +static int icl_nhi_suspend(struct tb_nhi *nhi) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + int ret; + + if (icl_nhi_is_device_connected(nhi)) + return 0; + + if (tb_switch_is_icm(tb->root_switch)) { + /* + * If there is no device connected we need to perform + * both: a handshake through LC mailbox and force power + * down before entering D3. + */ + icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); + ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); + if (ret) + return ret; + } + + return icl_nhi_force_power(nhi, false); +} + +static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + enum icl_lc_mailbox_cmd cmd; + + if (!pm_suspend_via_firmware()) + return icl_nhi_suspend(nhi); + + if (!tb_switch_is_icm(tb->root_switch)) + return 0; + + cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; + icl_nhi_lc_mailbox_cmd(nhi, cmd); + return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); +} + +static int icl_nhi_resume(struct tb_nhi *nhi) +{ + int ret; + + ret = icl_nhi_force_power(nhi, true); + if (ret) + return ret; + + icl_nhi_set_ltr(nhi); + return 0; +} + +static void icl_nhi_shutdown(struct tb_nhi *nhi) +{ + nhi_pci_shutdown(nhi); + + icl_nhi_force_power(nhi, false); +} + +static const struct tb_nhi_ops icl_nhi_ops = { + .init = icl_nhi_resume, + .suspend_noirq = icl_nhi_suspend_noirq, + .resume_noirq = icl_nhi_resume, + .runtime_suspend = icl_nhi_suspend, + .runtime_resume = icl_nhi_resume, + .shutdown = icl_nhi_shutdown, + .pre_nvm_auth = nhi_pci_start_dma_port, + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +}; + +static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct tb_nhi_pci *nhi_pci; + struct tb_nhi *nhi; + int res; + + if (!nhi_pci_imr_valid(pdev)) + return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); + + res = pcim_enable_device(pdev); + if (res) + return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); + + nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL); + if (!nhi_pci) + return -ENOMEM; + + nhi = &nhi_pci->nhi; + nhi->dev = dev; + nhi->ops = (const struct tb_nhi_ops *)id->driver_data ?: &pci_nhi_default_ops; + + nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); + res = PTR_ERR_OR_ZERO(nhi->iobase); + if (res) + return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); + + nhi_pci_check_quirks(nhi_pci); + nhi_pci_check_iommu(nhi_pci); + + pci_set_master(pdev); + + return nhi_probe(&nhi_pci->nhi); +} + +static void nhi_pci_remove(struct pci_dev *pdev) +{ + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); + + tb_domain_remove(tb); + wait_for_completion(&nhi->domain_released); + nhi_shutdown(nhi); +} + +static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and + * vendor (sub)id on gen 1 and gen 2 controllers. + */ + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI, + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI, + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, + }, + + /* Thunderbolt 3 */ + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + /* Thunderbolt 4 */ + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, + + /* Any USB4 compliant host */ + { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, + + { 0,} +}; + +MODULE_DEVICE_TABLE(pci, nhi_ids); +MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); +MODULE_LICENSE("GPL"); + +static struct pci_driver nhi_driver = { + .name = "thunderbolt", + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, + .shutdown = nhi_pci_remove, + .driver.pm = &nhi_pm_ops, +}; + +static int __init nhi_init(void) +{ + int ret; + + ret = tb_domain_init(); + if (ret) + return ret; + + ret = pci_register_driver(&nhi_driver); + if (ret) + tb_domain_exit(); + + return ret; +} + +static void __exit nhi_unload(void) +{ + pci_unregister_driver(&nhi_driver); + tb_domain_exit(); +} + +rootfs_initcall(nhi_init); +module_exit(nhi_unload); diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c index 59beab43f90a..df18a6d69d8b 100644 --- a/drivers/thunderbolt/property.c +++ b/drivers/thunderbolt/property.c @@ -40,6 +40,7 @@ struct tb_property_dir_entry { static struct tb_property_dir *__tb_property_parse_dir(const u32 *block, size_t block_len, unsigned int dir_offset, size_t dir_len, bool is_root, unsigned int depth); +static struct tb_property *tb_property_copy(const struct tb_property *property); static inline void parse_dwdata(void *dst, const void *src, size_t dwords) { @@ -527,17 +528,9 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, return ret < 0 ? ret : 0; } -/** - * tb_property_copy_dir() - Take a deep copy of directory - * @dir: Directory to copy - * - * The resulting directory needs to be released by calling tb_property_free_dir(). - * - * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. - */ -struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) +static struct tb_property_dir *copy_dir(const struct tb_property_dir *dir) { - struct tb_property *property, *p = NULL; + struct tb_property *property, *p; struct tb_property_dir *d; if (!dir) @@ -548,57 +541,132 @@ struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) return NULL; list_for_each_entry(property, &dir->properties, list) { - struct tb_property *p; - - p = tb_property_alloc(property->key, property->type); + p = tb_property_copy(property); if (!p) goto err_free; + list_add_tail(&p->list, &d->properties); + } - p->length = property->length; + return d; - switch (property->type) { - case TB_PROPERTY_TYPE_DIRECTORY: - p->value.dir = tb_property_copy_dir(property->value.dir); - if (!p->value.dir) - goto err_free; - break; +err_free: + tb_property_free_dir(d); + return NULL; +} - case TB_PROPERTY_TYPE_DATA: - p->value.data = kmemdup(property->value.data, - property->length * 4, - GFP_KERNEL); - if (!p->value.data) - goto err_free; - break; +static struct tb_property *tb_property_copy(const struct tb_property *property) +{ + struct tb_property *p; - case TB_PROPERTY_TYPE_TEXT: - p->value.text = kzalloc(p->length * 4, GFP_KERNEL); - if (!p->value.text) - goto err_free; - strcpy(p->value.text, property->value.text); - break; + p = tb_property_alloc(property->key, property->type); + if (!p) + return NULL; - case TB_PROPERTY_TYPE_VALUE: - p->value.immediate = property->value.immediate; - break; + p->length = property->length; + switch (property->type) { + case TB_PROPERTY_TYPE_DIRECTORY: + p->value.dir = copy_dir(property->value.dir); + if (!p->value.dir) + goto err_free; + break; - default: - break; - } + case TB_PROPERTY_TYPE_DATA: + p->value.data = kmemdup(property->value.data, + property->length * 4, + GFP_KERNEL); + if (!p->value.data) + goto err_free; + break; - list_add_tail(&p->list, &d->properties); + case TB_PROPERTY_TYPE_TEXT: + p->value.text = kzalloc(p->length * 4, GFP_KERNEL); + if (!p->value.text) + goto err_free; + strcpy(p->value.text, property->value.text); + break; + + case TB_PROPERTY_TYPE_VALUE: + p->value.immediate = property->value.immediate; + break; + + default: + break; } - return d; + return p; err_free: kfree(p); - tb_property_free_dir(d); - return NULL; } /** + * tb_property_copy_dir() - Take a deep copy of directory + * @dir: Directory to copy + * + * The resulting directory needs to be released by calling tb_property_free_dir(). + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. + */ +struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) +{ + return copy_dir(dir); +} +EXPORT_SYMBOL_GPL(tb_property_copy_dir); + +/** + * tb_property_merge_dir() - Merges directory into parent + * @parent: Directory to merge @dir + * @dir: Directory that is merged + * @replace: Replace existing entries + * + * This will merge @dir into @parent. Both must have same UUID. The + * properties in @dir will overwrite overlapping properties in @parent + * if @replace is %true. Contents of @dir is copied (so if it is not + * needed afterwards it needs to relesed by calling tb_property_free_dir()). + */ +int tb_property_merge_dir(struct tb_property_dir *parent, + const struct tb_property_dir *dir, + bool replace) +{ + const struct tb_property *property; + + if (WARN_ON(parent == dir)) + return -EINVAL; + + if (!uuid_equal(parent->uuid, dir->uuid)) + return -EINVAL; + + list_for_each_entry(property, &dir->properties, list) { + struct tb_property *p, *tmp; + + tmp = tb_property_copy(property); + if (!tmp) + return -ENOMEM; + + p = tb_property_find(parent, property->key, property->type); + if (p) { + if (replace) { + /* + * Found existing property in parent so + * replace with the new one. + */ + list_replace(&p->list, &tmp->list); + tb_property_free(p); + } else { + tb_property_free(tmp); + continue; + } + } else { + list_add_tail(&tmp->list, &parent->properties); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(tb_property_merge_dir); + +/** * tb_property_add_immediate() - Add immediate property to directory * @parent: Directory to add the property * @key: Key for the property diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c new file mode 100644 index 000000000000..c1f5c55583d0 --- /dev/null +++ b/drivers/thunderbolt/stream.c @@ -0,0 +1,1698 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Stream data over Thunderbolt/USB4 cable + * + * Copyright (C) 2026, Intel Corporation + * Authors: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + */ + +#define pr_fmt(fmt) "tbstream: " fmt + +#include <linux/configfs.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/sizes.h> +#include <linux/thunderbolt.h> +#include <linux/uaccess.h> +#include <linux/uio.h> +#include <linux/uuid.h> +#include <linux/wait.h> + +/* + * USB4STREAM - Stream data directly over Thunderbolt/USB4 cable + * + * HopIDs are configured by the user. In Linux this is done through + * ConfigFS. Once that is done paths are be established the first time + * the stream is opened. Typically the read side is opened first to make + * sure all the data will be received. + * + * End-to-end flow control is mandatory on both sides. + * + * Data is sent to the other side as tunneled DATA packets. All the data + * is owned by the user and passed as-is from the writer to the reader. + * + * Once the stream device is closed, a CLOSE packet is sent to the peer + * so it can take the necessary action. On Linux this typically results + * in EOF being returned to the reader. + * + * Tunneled packet types: + * + * +-------+---------+------------------+ + * | PDF | Type | Payload size | + * +-------+---------+------------------+ + * | 2 | DATA | up to 4 KiB | + * | 3 | CLOSE | up to 256 bytes | + * +-------+---------+------------------+ + * + * Each stream can optionally publish configuration values under its own + * XDomain property directory. The name of the directory is the name of + * the stream in question and the UUID is up to the stream. For example + * if the stream exposes video output then the directory name could be + * "video". + * + * Below values are reserved and can be used by the stream: + * + * +----------+-----------+-------------------------+ + * | Key | Type | Contents | + * +----------+-----------+-------------------------+ + * | inhopid | IMMEDIATE | Configured input HopID | + * | outhopid | IMMEDIATE | Configured output HopID | + * +----------+-----------+-------------------------+ + * + * It is allowed to add more stream specific properties as well if the + * above are not enough. + */ + +#define TBSTREAM_DEV_RING_SIZE 256 +#define TBSTREAM_DEV_MIN_RING_SIZE 32 +#define TBSTREAM_DEV_MAX_RING_SIZE 4096 +#define TBSTREAM_DEV_THROTTLING 8192 +#define TBSTREAM_DEV_MAX_THROTTLING 16776960 + +/** + * enum tbstream_frame_pdf - PDF numbers for tunneled frames + * @TBSTREAM_FRAME_START: PDF of the start of the frame + * @TBSTREAM_DATA: PDF of the DATA frame + * @TBSTREAM_CLOSE: PDF of the CLOSE frame + */ +enum tbstream_frame_pdf { + TBSTREAM_FRAME_START = 1, + TBSTREAM_DATA, + TBSTREAM_CLOSE, +}; + +/** + * struct tbstream_frame - Frame submitted to/from the rings + * @sdev: Pointer to the stream device + * @page: Page holding the packet + * @offset: Offset inside @page if partial read is done + * @completed: %true if the RX frame is completed + * @frame: Underlying frame structure + */ +struct tbstream_frame { + struct tbstream_dev *sdev; + struct page *page; + unsigned int offset; + bool completed; + struct ring_frame frame; +}; + +/** + * struct tbstream_ring - Stream RX/TX ring structure + * @ring: Pointer to the API ring + * @prod: Current value of producer + * @cons: Current value of consumer + * @frames: Holds the ring frames + */ +struct tbstream_ring { + struct tb_ring *ring; + unsigned long prod; + unsigned long cons; + struct tbstream_frame *frames; +}; + +/** + * struct tbstream_dev - Stream character device + * @group: ConfigFS group for this device + * @stream: Pointer to the stream if it is attached (%NULL otherwise) + * @misc: Character device used for tunneling + * @kref: Reference count + * @index: Unique identifier for the character device + * @in_hopid: In HopID + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. + * @wait: Waitqueue for open, read and write + * @lock: Lock protecting this structure + * @tx_ring: Transmit ring + * @rx_ring: Receive ring + * @list: Stream devices are linked through this + */ +struct tbstream_dev { + struct config_group group; + struct tbstream *stream; + struct miscdevice misc; + struct kref kref; + int index; + int in_hopid; + int out_hopid; + unsigned int ring_size; + unsigned int throttling; + int users; + bool closed; + bool removed; + wait_queue_head_t wait; + struct mutex lock; + struct tbstream_ring tx_ring; + struct tbstream_ring rx_ring; + struct list_head list; +}; + +/** + * struct tbstream_group - Config group for stream + * @group: ConfigFS group for @stream + * @stream: Stream the ConfigFS group is attached to. %NULL if there is + * no stream attached. + * @lock: Lock protecting this structure + * @dev_list: List of stream devices + * + * This is the ConfigFS directory for one connection to another host. + * There can be several &struct stream_dev linked through @dev_list of + * this structure. Reference count managed through @group. + */ +struct tbstream_group { + struct config_group group; + struct tbstream *stream; + struct mutex lock; + struct list_head dev_list; +}; + +/** + * struct tbstream - Stream service private data + * @kref: Reference count + * @svc: Pointer to the service device + * @list: Streams are linked through this in @stream_list + * + * This represents the actual physical connection between two hosts. + */ +struct tbstream { + struct kref kref; + struct tb_service *svc; + struct list_head list; +}; + +static DEFINE_IDA(tbstream_indices); + +/* Protects tbstream_list */ +static DEFINE_MUTEX(tbstream_lock); +static LIST_HEAD(tbstream_list); + +/* Serializes tbstream_get()/put() */ +static DEFINE_MUTEX(tbstream_kref_lock); + +/* Serializes tbstream_dev_get()/put() */ +static DEFINE_MUTEX(tbstream_dev_kref_lock); + +/* Stream property directory UUID: 3a1cb984-c4d9-4469-a277-ce2fdfd11f0d */ +static const uuid_t tbstream_dir_uuid = + UUID_INIT(0x3a1cb984, 0xc4d9, 0x4469, + 0xa2, 0x77, 0xce, 0x2f, 0xdf, 0xd1, 0x1f, 0x0d); + +static struct tb_property_dir *tbstream_dir; + +static void tbstream_release(struct kref *kref) +{ + struct tbstream *stream = container_of(kref, typeof(*stream), kref); + + tb_service_put(stream->svc); + kfree(stream); +} + +static void tbstream_put(struct tbstream *stream) +{ + if (stream) { + guard(mutex)(&tbstream_kref_lock); + kref_put(&stream->kref, tbstream_release); + } +} + +static struct tbstream *tbstream_get(struct tbstream *stream) +{ + if (stream) { + guard(mutex)(&tbstream_kref_lock); + kref_get(&stream->kref); + } + return stream; +} + +static inline bool tbstream_valid(const struct tbstream *stream) +{ + if (stream) + return !tb_service_parent(stream->svc)->is_unplugged; + return false; +} + +static void tbstream_ring_free(struct tbstream_ring *ring) +{ + struct device *dma_dev = tb_ring_dma_device(ring->ring); + enum dma_data_direction dir; + int i; + + if (ring->ring->is_tx) + dir = DMA_TO_DEVICE; + else + dir = DMA_FROM_DEVICE; + + for (i = 0; i < tb_ring_size(ring->ring); i++) { + struct tbstream_frame *sf = &ring->frames[i]; + + if (sf->frame.buffer_phy) + dma_unmap_page(dma_dev, sf->frame.buffer_phy, + tb_ring_frame_size(&sf->frame), dir); + sf->frame.buffer_phy = 0; + if (sf->page) + __free_page(sf->page); + sf->page = NULL; + } + + ring->prod = 0; + ring->cons = 0; + kfree(ring->frames); +} + +static inline bool tbstream_ring_available(const struct tbstream_ring *ring) +{ + return ring->prod > ring->cons; +} + +static inline struct tb_xdomain *tbstream_dev_xdomain(struct tbstream_dev *sdev) +{ + if (sdev->stream) + return tb_service_parent(sdev->stream->svc); + return NULL; +} + +static void tbstream_dev_release(struct kref *kref) +{ + struct tbstream_dev *sdev = container_of(kref, struct tbstream_dev, kref); + + if (sdev->stream) { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + + if (sdev->out_hopid > 0) + tb_xdomain_release_out_hopid(xd, sdev->out_hopid); + if (sdev->in_hopid > 0) + tb_xdomain_release_in_hopid(xd, sdev->in_hopid); + + tbstream_put(sdev->stream); + } + ida_free(&tbstream_indices, sdev->index); + kfree(sdev->misc.name); + kfree(sdev); +} + +static inline void tbstream_dev_put(struct tbstream_dev *sdev) +{ + guard(mutex)(&tbstream_dev_kref_lock); + kref_put(&sdev->kref, tbstream_dev_release); +} + +static inline struct tbstream_dev *tbstream_dev_get(struct tbstream_dev *sdev) +{ + guard(mutex)(&tbstream_dev_kref_lock); + kref_get(&sdev->kref); + return sdev; +} + +static inline struct tbstream_dev *to_tbstream_dev(struct miscdevice *misc) +{ + return container_of(misc, struct tbstream_dev, misc); +} + +static inline int tbstream_dev_valid(const struct tbstream_dev *sdev) +{ + const struct tbstream *stream = sdev->stream; + + if (!tbstream_valid(stream)) + return -ENXIO; + if (sdev->in_hopid <= 0 || sdev->out_hopid <= 0) + return -EINVAL; + return 0; +} + +static inline bool tbstream_dev_removed(const struct tbstream_dev *sdev) +{ + return sdev->removed; +} + +static inline bool tbstream_dev_closed(const struct tbstream_dev *sdev) +{ + return sdev->closed; +} + +static void +tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame); + struct tbstream_dev *sdev = sf->sdev; + + if (canceled) + return; + + sf->completed = true; + sdev->rx_ring.prod++; + + if (sf->frame.flags & RING_DESC_CRC_ERROR) + pr_warn("RX CRC error\n"); + else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN) + pr_warn("RX buffer overrun\n"); + else + wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM); +} + +static struct tbstream_frame * +tbstream_dev_completed_rx(struct tbstream_dev *sdev) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring); + struct tbstream_frame *sf; + int index; + + index = sdev->rx_ring.cons % tb_ring_size(sdev->rx_ring.ring); + sf = &sdev->rx_ring.frames[index]; + if (!sf->completed) + return NULL; + + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, + tb_ring_frame_size(&sf->frame), + DMA_FROM_DEVICE); + return sf; +} + +static int tbstream_dev_consume_rx(struct tbstream_dev *sdev) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring); + struct tbstream_frame *sf; + int index; + + index = sdev->rx_ring.cons % tb_ring_size(sdev->rx_ring.ring); + sdev->rx_ring.cons++; + + sf = &sdev->rx_ring.frames[index]; + sf->completed = false; + sf->offset = 0; + sf->frame.size = 0; + + dma_sync_single_for_device(dma_dev, sf->frame.buffer_phy, + tb_ring_frame_size(&sf->frame), + DMA_FROM_DEVICE); + + return tb_ring_rx(sdev->rx_ring.ring, &sf->frame); +} + +static int tbstream_dev_alloc_rx_buffers(struct tbstream_dev *sdev) +{ + size_t ring_size = tb_ring_size(sdev->rx_ring.ring); + int i; + + sdev->rx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame), + GFP_KERNEL); + if (!sdev->rx_ring.frames) + return -ENOMEM; + + for (i = 0; i < ring_size; i++) { + struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring); + struct tbstream_frame *sf = &sdev->rx_ring.frames[i]; + dma_addr_t dma_addr; + + sf->page = alloc_page(GFP_KERNEL); + if (!sf->page) + return -ENOMEM; + + dma_addr = dma_map_page(dma_dev, sf->page, 0, TB_MAX_FRAME_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(dma_dev, dma_addr)) { + __free_page(sf->page); + sf->page = NULL; + return -ENOMEM; + } + + sf->sdev = sdev; + sf->frame.callback = tbstream_dev_rx_callback; + sf->frame.buffer_phy = dma_addr; + + tb_ring_rx(sdev->rx_ring.ring, &sf->frame); + } + + sdev->rx_ring.cons = 0; + sdev->rx_ring.prod = 0; + return 0; +} + +static void +tbstream_dev_tx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame); + struct tbstream_dev *sdev = sf->sdev; + + if (canceled) + return; + + sdev->tx_ring.prod++; + if (sf->frame.eof == TBSTREAM_DATA) + wake_up_interruptible_poll(&sdev->wait, EPOLLOUT | EPOLLWRNORM); +} + +static int tbstream_dev_alloc_tx_buffers(struct tbstream_dev *sdev) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->tx_ring.ring); + size_t ring_size = tb_ring_size(sdev->tx_ring.ring); + int i; + + sdev->tx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame), + GFP_KERNEL); + if (!sdev->tx_ring.frames) + return -ENOMEM; + + for (i = 0; i < ring_size; i++) { + struct tbstream_frame *sf = &sdev->tx_ring.frames[i]; + dma_addr_t dma_addr; + + sf->page = alloc_page(GFP_KERNEL); + if (!sf->page) + return -ENOMEM; + + dma_addr = dma_map_page(dma_dev, sf->page, 0, TB_MAX_FRAME_SIZE, + DMA_TO_DEVICE); + if (dma_mapping_error(dma_dev, dma_addr)) { + __free_page(sf->page); + sf->page = NULL; + return -ENOMEM; + } + + sf->sdev = sdev; + sf->frame.callback = tbstream_dev_tx_callback; + sf->frame.buffer_phy = dma_addr; + sf->frame.sof = TBSTREAM_FRAME_START; + } + + sdev->tx_ring.cons = 0; + sdev->tx_ring.prod = ring_size - 1; + return 0; +} + +static struct tbstream_frame * +tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, + struct iov_iter *from, size_t size) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->tx_ring.ring); + struct tbstream_frame *sf; + int index; + + if (!tbstream_ring_available(&sdev->tx_ring)) + return ERR_PTR(-ENOBUFS); + + index = sdev->tx_ring.cons % tb_ring_size(sdev->tx_ring.ring); + sdev->tx_ring.cons++; + + sf = &sdev->tx_ring.frames[index]; + sf->frame.size = size < TB_MAX_FRAME_SIZE ? size : 0; + sf->frame.eof = 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) + return ERR_PTR(-EFAULT); + } else { + memset(page_address(sf->page), 0, size); + } + dma_sync_single_for_device(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + return sf; +} + +static int +tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + size_t size) +{ + struct tbstream_frame *sf; + + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_DATA, from, size); + if (IS_ERR(sf)) + return PTR_ERR(sf); + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); +} + +static int tbstream_dev_send_close(struct tbstream_dev *sdev) +{ + struct tbstream_frame *sf; + + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); +} + +static int tbstream_dev_start(struct tbstream_dev *sdev) +{ + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + 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 (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; + + ret = tbstream_dev_alloc_tx_buffers(sdev); + if (ret) + goto err_free_tx; + + e2e_tx_hop = ring->hop; + 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); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; + } + sdev->rx_ring.ring = ring; + + ret = tb_xdomain_enable_paths(xd, sdev->out_hopid, + sdev->tx_ring.ring->hop, + sdev->in_hopid, + sdev->rx_ring.ring->hop); + if (ret) + goto err_free_rx; + + tb_ring_throttling(sdev->tx_ring.ring, sdev->throttling); + tb_ring_throttling(sdev->rx_ring.ring, sdev->throttling); + + tb_ring_start(sdev->tx_ring.ring); + tb_ring_start(sdev->rx_ring.ring); + + ret = tbstream_dev_alloc_rx_buffers(sdev); + if (ret) + goto err_stop; + return 0; + +err_stop: + tb_ring_stop(sdev->rx_ring.ring); + tb_ring_stop(sdev->tx_ring.ring); +err_free_rx: + tb_ring_free(sdev->rx_ring.ring); +err_free_tx_buffers: + tbstream_ring_free(&sdev->tx_ring); +err_free_tx: + tb_ring_free(sdev->tx_ring.ring); + + return ret; +} + +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); + + xd = tbstream_dev_xdomain(sdev); + if (xd) { + tb_xdomain_disable_paths(xd, sdev->out_hopid, + sdev->tx_ring.ring->hop, + sdev->in_hopid, + sdev->rx_ring.ring->hop); + } + + tbstream_ring_free(&sdev->rx_ring); + tb_ring_free(sdev->rx_ring.ring); + sdev->rx_ring.ring = NULL; + tbstream_ring_free(&sdev->tx_ring); + tb_ring_free(sdev->tx_ring.ring); + sdev->tx_ring.ring = NULL; +} + +static ssize_t +tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) +{ + struct file *file = kiocb->ki_filp; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; + + ret = tbstream_dev_valid(sdev); + if (ret) + return ret; + + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + + 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; + + ret = tbstream_dev_valid(sdev); + 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)) { + struct tbstream_frame *sf; + size_t size, sf_size; + + sf = tbstream_dev_completed_rx(sdev); + if (!sf) + break; + /* + * CLOSE tunneled packet. If userspace already read + * something then we stop processing now and return + * those bytes. Next time the first frame will be CLOSE + * in which case we return EOF to the user. + */ + if (sf->frame.eof == TBSTREAM_CLOSE) { + if (!nbytes) { + tbstream_dev_consume_rx(sdev); + sdev->closed = true; + } + break; + } + + sf_size = tb_ring_frame_size(&sf->frame); + size = min(iov_iter_count(to) - nbytes, sf_size); + + if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { + ret = -EFAULT; + break; + } + + /* + * If not all data from the frame is read so leave it in + * place and update the offset accordingly so next read + * gets the rest. + */ + if (size < sf_size) { + sf->offset += size; + sf->frame.size = sf_size - size; + } else { + ret = tbstream_dev_consume_rx(sdev); + if (ret) + break; + } + + nbytes += size; + } + + mutex_unlock(&sdev->lock); + if (ret) + return ret; + return nbytes; +} + +static ssize_t +tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) +{ + struct file *file = kiocb->ki_filp; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; + + ret = tbstream_dev_valid(sdev); + if (ret) + return ret; + + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + + 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; + + ret = tbstream_dev_valid(sdev); + 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)) { + size_t size; + + size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); + ret = tbstream_dev_send_data(sdev, from, size); + if (ret) { + /* + * If there are no more buffers we are done for + * this write. + */ + if (ret == -ENOBUFS) + ret = 0; + break; + } + + nbytes += size; + } + + mutex_unlock(&sdev->lock); + if (ret) + return ret; + return nbytes; +} + +static __poll_t +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; + + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { + mask |= EPOLLHUP | EPOLLERR; + } else { + if (tbstream_ring_available(&sdev->tx_ring)) + mask |= EPOLLOUT | EPOLLWRNORM; + if (tbstream_ring_available(&sdev->rx_ring)) + mask |= EPOLLIN | EPOLLRDNORM; + } + return mask; +} + +static int tbstream_dev_fops_open(struct inode *inode, struct file *file) +{ + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + int ret; + + tbstream_dev_get(sdev); + + if (mutex_lock_interruptible(&sdev->lock)) { + tbstream_dev_put(sdev); + return -ERESTARTSYS; + } + + /* + * If there is no stream attached yet, block until it appears + * unless this is opened in non-blocking mode. + */ + while ((ret = tbstream_dev_valid(sdev))) { + mutex_unlock(&sdev->lock); + + if (ret != -ENXIO || (file->f_flags & O_NONBLOCK)) + goto err_put; + + ret = wait_event_interruptible(sdev->wait, + tbstream_dev_valid(sdev) == 0 || + tbstream_dev_removed(sdev)); + if (ret) + goto err_put; + + if (tbstream_dev_removed(sdev)) { + ret = -ENXIO; + goto err_put; + } + + if (mutex_lock_interruptible(&sdev->lock)) { + ret = -ERESTARTSYS; + goto err_put; + } + } + + /* Only on first open we allocate rings and enable paths */ + if (!sdev->users++) { + ret = tbstream_dev_start(sdev); + if (ret) { + sdev->users--; + goto err_unlock; + } + sdev->closed = false; + } + + mutex_unlock(&sdev->lock); + return 0; + +err_unlock: + mutex_unlock(&sdev->lock); +err_put: + tbstream_dev_put(sdev); + + return ret; +} + +static int tbstream_dev_fops_release(struct inode *inode, struct file *file) +{ + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + + mutex_lock(&sdev->lock); + if (--sdev->users == 0) { + /* + * Send CLOSE tunneled packet to notify the other end + * that we are closing the file. We do this twice if the + * first one fails. + */ + tbstream_dev_send_close(sdev); + tbstream_dev_stop(sdev); + } + mutex_unlock(&sdev->lock); + + tbstream_dev_put(sdev); + return 0; +} + +static const struct file_operations tbstream_dev_fops = { + .owner = THIS_MODULE, + .llseek = noop_llseek, + .read_iter = tbstream_dev_fops_read_iter, + .write_iter = tbstream_dev_fops_write_iter, + .poll = tbstream_dev_fops_poll, + .open = tbstream_dev_fops_open, + .release = tbstream_dev_fops_release, +}; + +static inline struct tbstream_dev * +tbstream_dev_from_group(struct config_group *group) +{ + return container_of(group, struct tbstream_dev, group); +} + +static ssize_t tbstream_dev_index_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, "%d\n", sdev->index); +} +CONFIGFS_ATTR_RO(tbstream_dev_, index); + +static ssize_t tbstream_dev_in_hopid_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, "%d\n", sdev->in_hopid); +} + +/* svc->lock must be held */ +static void service_remove_properties(struct tb_service *svc, const char *name) +{ + struct tb_property *p; + + if (!svc->local_properties) + return; + + p = tb_property_find(svc->local_properties, name, + TB_PROPERTY_TYPE_DIRECTORY); + if (p) { + tb_property_free_dir(p->value.dir); + tb_property_remove(p); + + dev_dbg(&svc->dev, "removed local directory %s\n", name); + + /* + * Is the service directory empty already? If it is then + * we can release it as well. + */ + tb_property_for_each(svc->local_properties, p) { + if (p->type == TB_PROPERTY_TYPE_DIRECTORY) + return; + } + + tb_property_free_dir(svc->local_properties); + svc->local_properties = NULL; + } +} + +static int service_update_properties(struct tb_service *svc, const char *name, + int in_hopid, int out_hopid) +{ + struct tb_property_dir *dir; + struct tb_property *p; + + guard(mutex)(&svc->lock); + + if (in_hopid < 8 || out_hopid < 8) { + service_remove_properties(svc, name); + return 0; + } + + if (!svc->local_properties) { + /* + * Add the service directory first time we + * populate the entries. + */ + svc->local_properties = tb_property_copy_dir(tbstream_dir); + if (!svc->local_properties) + return -ENOMEM; + } + + p = tb_property_find(svc->local_properties, name, + TB_PROPERTY_TYPE_DIRECTORY); + if (p) { + dir = p->value.dir; + + p = tb_property_find(dir, "inhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate != in_hopid) + p->value.immediate = in_hopid; + p = tb_property_find(dir, "outhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate != out_hopid) + p->value.immediate = out_hopid; + + dev_dbg(&svc->dev, + "updated local directory %s: in HopID %d, out HopID %d\n", + name, in_hopid, out_hopid); + } else { + uuid_t uuid; + int ret; + + uuid_gen(&uuid); + dir = tb_property_create_dir(&uuid); + if (!dir) + return -ENOMEM; + + tb_property_add_immediate(dir, "inhopid", in_hopid); + tb_property_add_immediate(dir, "outhopid", out_hopid); + + ret = tb_property_add_dir(svc->local_properties, name, dir); + if (ret) { + tb_property_free_dir(dir); + return ret; + } + + dev_dbg(&svc->dev, + "added local directory %s: in HopID %d, out HopID %d\n", + name, in_hopid, out_hopid); + } + + return 0; +} + +static int tbstream_dev_update_properties(struct tbstream_dev *sdev) +{ + struct tbstream *stream; + int ret; + + stream = tbstream_get(sdev->stream); + if (!stream) + return 0; + + ret = service_update_properties(stream->svc, + config_item_name(&sdev->group.cg_item), + sdev->in_hopid, sdev->out_hopid); + if (!ret) + tb_service_properties_changed(stream->svc); + + tbstream_put(stream); + return ret; +} + +static int tbstream_dev_alloc_in_hopid(struct tbstream_dev *sdev, int hopid) +{ + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + int ret; + + if (sdev->in_hopid > 0 && sdev->in_hopid != hopid) + tb_xdomain_release_in_hopid(xd, sdev->in_hopid); + if (!hopid) { + sdev->in_hopid = hopid; + return 0; + } + ret = tb_xdomain_alloc_in_hopid(xd, hopid); + if (ret < 0) + return ret; + /* + * If specific HopID was asked by the user and we did not get + * that one then release and return error instead. + */ + if (hopid > 0 && hopid != ret) { + tb_xdomain_release_in_hopid(xd, ret); + return -EBUSY; + } + sdev->in_hopid = ret; + return 0; +} + +static int tbstream_dev_alloc_out_hopid(struct tbstream_dev *sdev, int hopid) +{ + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + int ret; + + if (sdev->out_hopid > 0 && sdev->out_hopid != hopid) + tb_xdomain_release_out_hopid(xd, sdev->out_hopid); + if (!hopid) { + sdev->out_hopid = hopid; + return 0; + } + ret = tb_xdomain_alloc_out_hopid(xd, hopid); + if (ret < 0) + return ret; + if (hopid > 0 && hopid != ret) { + tb_xdomain_release_out_hopid(xd, ret); + return -EBUSY; + } + sdev->out_hopid = ret; + return 0; +} + +static ssize_t +tbstream_dev_in_hopid_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); + int ret, in_hopid; + + ret = kstrtoint(buf, 0, &in_hopid); + if (ret) + return ret; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + if (sdev->stream) { + ret = tbstream_dev_alloc_in_hopid(sdev, in_hopid); + if (ret) + return ret; + ret = tbstream_dev_update_properties(sdev); + } else { + sdev->in_hopid = in_hopid; + } + return ret ? ret : count; +} +CONFIGFS_ATTR(tbstream_dev_, in_hopid); + +static ssize_t tbstream_dev_out_hopid_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, "%d\n", sdev->out_hopid); +} + +static ssize_t +tbstream_dev_out_hopid_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); + int ret, out_hopid; + + ret = kstrtoint(buf, 0, &out_hopid); + if (ret) + return ret; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + if (sdev->stream) { + ret = tbstream_dev_alloc_out_hopid(sdev, out_hopid); + if (ret) + return ret; + ret = tbstream_dev_update_properties(sdev); + } else { + sdev->out_hopid = out_hopid; + } + return ret ? ret : count; +} +CONFIGFS_ATTR(tbstream_dev_, out_hopid); + +static ssize_t tbstream_dev_ring_size_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->ring_size); +} + +static ssize_t +tbstream_dev_ring_size_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); + unsigned int ring_size; + int ret; + + ret = kstrtouint(buf, 0, &ring_size); + if (ret) + return ret; + + if (ring_size < TBSTREAM_DEV_MIN_RING_SIZE || + ring_size > TBSTREAM_DEV_MAX_RING_SIZE) + return -EINVAL; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + sdev->ring_size = ring_size; + return count; +} +CONFIGFS_ATTR(tbstream_dev_, ring_size); + +static ssize_t tbstream_dev_throttling_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->throttling); +} + +static ssize_t +tbstream_dev_throttling_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); + unsigned int throttling; + int ret; + + ret = kstrtouint(buf, 0, &throttling); + if (ret) + return ret; + + if (throttling > TBSTREAM_DEV_MAX_THROTTLING) + return -EINVAL; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + sdev->throttling = throttling; + return count; +} +CONFIGFS_ATTR(tbstream_dev_, throttling); + +static struct configfs_attribute *tbstream_dev_attrs[] = { + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, + &tbstream_dev_attr_ring_size, + &tbstream_dev_attr_throttling, + NULL, +}; + +static void tbstream_dev_item_release(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + misc_deregister(&sdev->misc); + tbstream_dev_put(sdev); +} + +static struct configfs_item_operations tbstream_dev_item_ops = { + .release = tbstream_dev_item_release, +}; + +static const struct config_item_type tbstream_dev_type = { + .ct_owner = THIS_MODULE, + .ct_item_ops = &tbstream_dev_item_ops, + .ct_attrs = tbstream_dev_attrs, +}; + +static void service_get_hopids(struct tb_service *svc, const char *name, + int *in_hopid, int *out_hopid) +{ + struct tb_property_dir *dir; + struct tb_property *p; + + guard(mutex)(&svc->lock); + + /* See if we have directory entry with the matching name */ + p = tb_property_find(svc->remote_properties, name, + TB_PROPERTY_TYPE_DIRECTORY); + if (!p) + return; + + dir = p->value.dir; + + /* + * We need to reverse the HopIDs on our end so that in becomes + * out and vice versa. + */ + p = tb_property_find(dir, "inhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate >= 8) + *out_hopid = p->value.immediate; + p = tb_property_find(dir, "outhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate >= 8) + *in_hopid = p->value.immediate; +} + +static void +tbstream_dev_attach_stream(struct tbstream_dev *sdev, struct tbstream_group *sg) +{ + const char *name = config_item_name(&sdev->group.cg_item); + struct tbstream *stream; + + stream = tbstream_get(sg->stream); + if (!stream) + return; + + scoped_guard(mutex, &sdev->lock) { + sdev->stream = stream; + /* + * If there is no existing configuration (or automatic + * configuration is being used) check if the other side + * has configuration for this and use it. + */ + if (sdev->in_hopid <= 0 && sdev->out_hopid <= 0) + service_get_hopids(stream->svc, name, &sdev->in_hopid, + &sdev->out_hopid); + if (sdev->in_hopid) + tbstream_dev_alloc_in_hopid(sdev, sdev->in_hopid); + if (sdev->out_hopid) + tbstream_dev_alloc_out_hopid(sdev, sdev->out_hopid); + } + + service_update_properties(stream->svc, name, sdev->in_hopid, + sdev->out_hopid); + tb_service_properties_changed(stream->svc); + + /* Notify any openerers that the stream is now attached */ + wake_up_interruptible(&sdev->wait); +} + +static void tbstream_dev_detach_stream(struct tbstream_dev *sdev) +{ + const char *name = config_item_name(&sdev->group.cg_item); + struct tbstream *stream; + struct tb_xdomain *xd; + + scoped_guard(mutex, &sdev->lock) { + stream = sdev->stream; + if (!stream) + return; + sdev->stream = NULL; + xd = tb_service_parent(stream->svc); + if (sdev->out_hopid > 0) + tb_xdomain_release_out_hopid(xd, sdev->out_hopid); + if (sdev->in_hopid > 0) + tb_xdomain_release_in_hopid(xd, sdev->in_hopid); + } + + service_update_properties(stream->svc, name, 0, 0); + tb_service_properties_changed(stream->svc); + + tbstream_put(stream); + + /* Notify any task that the stream is not valid anymore */ + wake_up_interruptible_poll(&sdev->wait, EPOLLHUP | EPOLLERR); +} + +static inline struct tbstream_group * +to_tbstream_group(struct config_group *group) +{ + return container_of(group, struct tbstream_group, group); +} + +static struct config_group * +tbstream_dev_make_group(struct config_group *group, const char *name) +{ + struct tbstream_group *sg = to_tbstream_group(group); + struct tbstream_dev *sdev; + int ret, index; + + /* + * We want the names to be suitable for passing as property + * directory names. + */ + if (strlen(name) > TB_PROPERTY_KEY_SIZE) + return ERR_PTR(-ENAMETOOLONG); + + sdev = kzalloc_obj(*sdev, GFP_KERNEL); + if (!sdev) + return ERR_PTR(-ENOMEM); + + index = ida_alloc(&tbstream_indices, GFP_KERNEL); + if (index < 0) { + kfree(sdev); + return ERR_PTR(index); + } + + sdev->index = index; + sdev->ring_size = TBSTREAM_DEV_RING_SIZE; + sdev->throttling = TBSTREAM_DEV_THROTTLING; + mutex_init(&sdev->lock); + init_waitqueue_head(&sdev->wait); + INIT_LIST_HEAD(&sdev->list); + /* This point forward tbstream_dev_put() must be used to release sdev */ + kref_init(&sdev->kref); + + config_group_init_type_name(&sdev->group, name, &tbstream_dev_type); + + scoped_guard(mutex, &sg->lock) + list_add_tail(&sdev->list, &sg->dev_list); + + tbstream_dev_attach_stream(sdev, sg); + + sdev->misc.name = kasprintf(GFP_KERNEL, "tbstream%d", index); + sdev->misc.minor = MISC_DYNAMIC_MINOR; + sdev->misc.fops = &tbstream_dev_fops; + + ret = misc_register(&sdev->misc); + if (ret) { + tbstream_dev_detach_stream(sdev); + scoped_guard(mutex, &sg->lock) + list_del(&sdev->list); + /* Calls tbstream_dev_put() */ + config_group_put(&sdev->group); + return ERR_PTR(ret); + } + + return &sdev->group; +} + +static void +tbstream_dev_drop_item(struct config_group *group, struct config_item *item) +{ + struct config_group *sdev_group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(sdev_group); + struct tbstream_group *sg = to_tbstream_group(group); + + scoped_guard(mutex, &sg->lock) + list_del(&sdev->list); + /* Notify any task that the underlying group was removed */ + sdev->removed = true; + wake_up_interruptible_poll(&sdev->wait, EPOLLHUP | EPOLLERR); + config_item_put(item); +} + +static struct configfs_group_operations tbstream_dev_group_ops = { + .make_group = tbstream_dev_make_group, + .drop_item = tbstream_dev_drop_item, +}; + +static void tbstream_item_release(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + struct tbstream_group *sg = to_tbstream_group(group); + + tbstream_put(sg->stream); + kfree(sg); +} + +static struct configfs_item_operations tbstream_item_ops = { + .release = tbstream_item_release, +}; + +static const struct config_item_type tbstream_dev_group_type = { + .ct_owner = THIS_MODULE, + .ct_group_ops = &tbstream_dev_group_ops, + .ct_item_ops = &tbstream_item_ops, +}; + +static struct config_group * +tbstream_make_group(struct config_group *group, const char *name) +{ + struct tbstream_group *sg; + struct tbstream *stream; + int domain, index; + u64 route; + + /* Make sure the format is correct */ + if (sscanf(name, "%u-%llx.%u", &domain, &route, &index) != 3) + return ERR_PTR(-EINVAL); + + sg = kzalloc_obj(*sg, GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + mutex_init(&sg->lock); + INIT_LIST_HEAD(&sg->dev_list); + + guard(mutex)(&tbstream_lock); + list_for_each_entry(stream, &tbstream_list, list) { + tbstream_get(stream); + if (sysfs_streq(name, dev_name(&stream->svc->dev))) { + sg->stream = stream; + break; + } + tbstream_put(stream); + } + + config_group_init_type_name(&sg->group, name, &tbstream_dev_group_type); + return &sg->group; +} + +static struct configfs_group_operations tbstream_group_ops = { + .make_group = tbstream_make_group, +}; + +static const struct config_item_type tbstream_group_type = { + .ct_owner = THIS_MODULE, + .ct_group_ops = &tbstream_group_ops, +}; + +static struct config_group tbstream_group = { + .cg_item = { + .ci_namebuf = "stream", + .ci_type = &tbstream_group_type, + }, +}; + +/* Returns reference count increased */ +static struct tbstream_group *tbstream_group_find(struct tbstream *stream) +{ + const char *name = dev_name(&stream->svc->dev); + struct config_item *item; + + guard(mutex)(&tbstream_group.cg_subsys->su_mutex); + item = config_group_find_item(&tbstream_group, name); + if (!item) + return NULL; + return to_tbstream_group(to_config_group(item)); +} + +static void tbstream_group_attach_stream(struct tbstream *stream) +{ + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return; + + guard(mutex)(&sg->lock); + if (WARN_ON(sg->stream)) { + config_group_put(&sg->group); + return; + } + sg->stream = tbstream_get(stream); + /* + * If there are existing stream devices, attach the stream to + * them now. + */ + list_for_each_entry(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + tbstream_dev_attach_stream(sdev, sg); + tbstream_dev_put(sdev); + } + + config_group_put(&sg->group); +} + +static void tbstream_group_detach_stream(struct tbstream *stream) +{ + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return; + + guard(mutex)(&sg->lock); + if (sg->stream) { + /* Detach this stream from the stream devices */ + list_for_each_entry_reverse(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + tbstream_dev_detach_stream(sdev); + tbstream_dev_put(sdev); + } + tbstream_put(sg->stream); + sg->stream = NULL; + } + + config_group_put(&sg->group); +} + +static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) +{ + struct tbstream *stream; + + stream = kzalloc_obj(*stream, GFP_KERNEL); + if (!stream) + return -ENOMEM; + + /* After this point, release stream by calling tbstream_put() */ + kref_init(&stream->kref); + stream->svc = tb_service_get(svc); + INIT_LIST_HEAD(&stream->list); + + scoped_guard(mutex, &tbstream_lock) + list_add_tail(&stream->list, &tbstream_list); + + tbstream_group_attach_stream(stream); + tb_service_set_drvdata(svc, stream); + return 0; +} + +static void tbstream_remove(struct tb_service *svc) +{ + struct tbstream *stream = tb_service_get_drvdata(svc); + + tbstream_group_detach_stream(stream); + scoped_guard(mutex, &tbstream_lock) + list_del(&stream->list); + tbstream_put(stream); +} + +static int __maybe_unused tbstream_suspend(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + struct tbstream *stream = tb_service_get_drvdata(svc); + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return 0; + + list_for_each_entry_reverse(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + /* Stop the stream (if it was open) */ + if (sdev->users) + tbstream_dev_stop(sdev); + tbstream_dev_put(sdev); + } + + config_group_put(&sg->group); + return 0; +} + +static int __maybe_unused tbstream_resume(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + struct tbstream *stream = tb_service_get_drvdata(svc); + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return 0; + + list_for_each_entry(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + if (sdev->users) { + int ret; + + ret = tbstream_dev_start(sdev); + if (ret) { + tbstream_dev_put(sdev); + config_group_put(&sg->group); + return ret; + } + } + tbstream_dev_put(sdev); + } + + config_group_put(&sg->group); + return 0; +} + +static const struct dev_pm_ops tbstream_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tbstream_suspend, tbstream_resume) +}; + +static const struct tb_service_id tbstream_ids[] = { + { TB_SERVICE("stream", 1) }, + { }, +}; +MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); + +static struct tb_service_driver tbstream_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "thunderbolt_stream", + .pm = &tbstream_pm_ops, + }, + .probe = tbstream_probe, + .remove = tbstream_remove, + .id_table = tbstream_ids, +}; + +static int __init tbstream_init(void) +{ + int ret; + + tbstream_dir = tb_property_create_dir(&tbstream_dir_uuid); + if (!tbstream_dir) + return -ENOMEM; + + tb_property_add_immediate(tbstream_dir, "prtcid", 1); + tb_property_add_immediate(tbstream_dir, "prtcvers", 1); + tb_property_add_immediate(tbstream_dir, "prtcrevs", 0); + tb_property_add_immediate(tbstream_dir, "prtcstns", 0); + + ret = tb_register_property_dir("stream", tbstream_dir); + if (ret) + goto err_free_dir; + + config_group_init(&tbstream_group); + ret = tb_configfs_register_group(&tbstream_group); + if (ret) + goto err_unregister_dir; + + ret = tb_register_service_driver(&tbstream_driver); + if (ret) + goto err_unregister_group; + return 0; + +err_unregister_group: + tb_configfs_unregister_group(&tbstream_group); +err_unregister_dir: + tb_unregister_property_dir("stream", tbstream_dir); +err_free_dir: + tb_property_free_dir(tbstream_dir); + return ret; +} +module_init(tbstream_init); + +static void __exit tbstream_exit(void) +{ + tb_unregister_service_driver(&tbstream_driver); + tb_configfs_unregister_group(&tbstream_group); + tb_unregister_property_dir("stream", tbstream_dir); + tb_property_free_dir(tbstream_dir); + ida_destroy(&tbstream_indices); +} +module_exit(tbstream_exit); + +MODULE_AUTHOR("Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_DESCRIPTION("Stream data over Thunderbolt/USB4 cable"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c2ad58b19e7b..a830c82bb905 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -209,30 +209,6 @@ static int nvm_authenticate_device_dma_port(struct tb_switch *sw) return -ETIMEDOUT; } -static void nvm_authenticate_start_dma_port(struct tb_switch *sw) -{ - struct pci_dev *root_port; - - /* - * During host router NVM upgrade we should not allow root port to - * go into D3cold because some root ports cannot trigger PME - * itself. To be on the safe side keep the root port in D0 during - * the whole upgrade process. - */ - root_port = pcie_find_root_port(sw->tb->nhi->pdev); - if (root_port) - pm_runtime_get_noresume(&root_port->dev); -} - -static void nvm_authenticate_complete_dma_port(struct tb_switch *sw) -{ - struct pci_dev *root_port; - - root_port = pcie_find_root_port(sw->tb->nhi->pdev); - if (root_port) - pm_runtime_put(&root_port->dev); -} - static inline bool nvm_readable(struct tb_switch *sw) { if (tb_switch_is_usb4(sw)) { @@ -258,6 +234,7 @@ static inline bool nvm_upgradeable(struct tb_switch *sw) static int nvm_authenticate(struct tb_switch *sw, bool auth_only) { + struct tb_nhi *nhi = sw->tb->nhi; int ret; if (tb_switch_is_usb4(sw)) { @@ -274,7 +251,8 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only) sw->nvm->authenticating = true; if (!tb_route(sw)) { - nvm_authenticate_start_dma_port(sw); + if (nhi->ops->pre_nvm_auth) + nhi->ops->pre_nvm_auth(nhi); ret = nvm_authenticate_host_dma_port(sw); } else { ret = nvm_authenticate_device_dma_port(sw); @@ -1620,6 +1598,12 @@ static int tb_switch_reset_host(struct tb_switch *sw) ret = tb_port_reset(port); if (ret) return ret; + /* + * USB4 Lane 1 adapters do not have accessible + * path config space. + */ + if (tb_switch_is_usb4(sw) && !port->usb4) + continue; } else if (tb_port_is_usb3_down(port) || tb_port_is_usb3_up(port)) { tb_usb3_port_enable(port, false); @@ -1761,8 +1745,6 @@ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, /* * tb_plug_events_active() - enable/disable plug events on a switch * - * Also configures a sane plug_events_delay of 255ms. - * * Return: %0 on success, negative errno otherwise. */ static int tb_plug_events_active(struct tb_switch *sw, bool active) @@ -1773,11 +1755,6 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw)) return 0; - sw->config.plug_events_delay = 0xff; - res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1); - if (res) - return res; - res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1); if (res) return res; @@ -2639,6 +2616,8 @@ int tb_switch_configure(struct tb_switch *sw) sw->config.enabled = 1; + /* Set Notification Timeout to 255 ms for all routers */ + sw->config.plug_events_delay = 0xff; if (tb_switch_is_usb4(sw)) { /* * For USB4 devices, we need to program the CM version @@ -2650,7 +2629,6 @@ int tb_switch_configure(struct tb_switch *sw) sw->config.cmuv = ROUTER_CS_4_CMUV_V1; else sw->config.cmuv = ROUTER_CS_4_CMUV_V2; - sw->config.plug_events_delay = 0xa; /* Enumerate the switch */ ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH, @@ -2671,7 +2649,7 @@ int tb_switch_configure(struct tb_switch *sw) /* Enumerate the switch */ ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH, - ROUTER_CS_1, 3); + ROUTER_CS_1, 4); } if (ret) return ret; @@ -2743,6 +2721,7 @@ static int tb_switch_set_uuid(struct tb_switch *sw) static int tb_switch_add_dma_port(struct tb_switch *sw) { + struct tb_nhi *nhi = sw->tb->nhi; u32 status; int ret; @@ -2802,8 +2781,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) */ nvm_get_auth_status(sw, &status); if (status) { - if (!tb_route(sw)) - nvm_authenticate_complete_dma_port(sw); + if (!tb_route(sw)) { + if (nhi->ops->post_nvm_auth) + nhi->ops->post_nvm_auth(nhi); + } return 0; } @@ -2817,8 +2798,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) return ret; /* Now we can allow root port to suspend again */ - if (!tb_route(sw)) - nvm_authenticate_complete_dma_port(sw); + if (!tb_route(sw)) { + if (nhi->ops->post_nvm_auth) + nhi->ops->post_nvm_auth(nhi); + } if (status) { tb_sw_info(sw, "switch flash authentication failed\n"); @@ -2971,14 +2954,14 @@ static int tb_switch_lane_bonding_enable(struct tb_switch *sw) int ret; if (!tb_switch_lane_bonding_possible(sw)) - return 0; + return -EOPNOTSUPP; up = tb_upstream_port(sw); down = tb_switch_downstream_port(sw); if (!tb_port_width_supported(up, TB_LINK_WIDTH_DUAL) || !tb_port_width_supported(down, TB_LINK_WIDTH_DUAL)) - return 0; + return -EOPNOTSUPP; /* * Both lanes need to be in CL0. Here we assume lane 0 already be in @@ -3625,6 +3608,20 @@ int tb_switch_resume(struct tb_switch *sw, bool runtime) tb_port_warn(port, "lost during suspend, disconnecting\n"); tb_sw_set_unplugged(port->remote->sw); + } else if (port->xdomain) { + /* + * If the user replaced the XDomain with + * another router, this will succeed in + * which case we must remove the XDomain + * before adding the new router. + */ + err = tb_cfg_get_upstream_port(sw->tb->ctl, + port->xdomain->route); + if (err > 0) { + tb_port_warn(port, + "XDomain was disconnected\n"); + port->xdomain->is_unplugged = true; + } } } } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index c69c323e6952..76323255439a 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -98,7 +98,7 @@ static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) if (!ev) return; - ev->tb = tb; + ev->tb = tb_domain_get(tb); ev->route = route; ev->port = port; ev->unplug = unplug; @@ -2524,9 +2524,14 @@ put_sw: out: mutex_unlock(&tb->lock); + tb_domain_unregister_unplugged_xdomains(tb); + pm_runtime_mark_last_busy(&tb->dev); pm_runtime_put_autosuspend(&tb->dev); + /* Undo the refcount increased in tb_queue_hotplug() */ + tb_domain_put(tb); + kfree(ev); } @@ -2844,6 +2849,9 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work) /* Update other clients about the allocation change */ tb_recalc_estimated_bandwidth(tb); + + tb_dbg(tb, "checking if more DP tunnels can be established now\n"); + tb_tunnel_dp(tb); } put_sw: @@ -2949,6 +2957,7 @@ static void tb_stop(struct tb *tb) tb_tunnel_put(tunnel); } tb_switch_remove(tb->root_switch); + tb->root_switch = NULL; tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ } @@ -2991,7 +3000,8 @@ static int tb_start(struct tb *tb, bool reset) tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0); if (IS_ERR(tb->root_switch)) - return PTR_ERR(tb->root_switch); + return dev_err_probe(tb->nhi->dev, PTR_ERR(tb->root_switch), + "failed to allocate host router\n"); /* * ICM firmware upgrade needs running firmware and in native @@ -3008,14 +3018,14 @@ static int tb_start(struct tb *tb, bool reset) ret = tb_switch_configure(tb->root_switch); if (ret) { tb_switch_put(tb->root_switch); - return ret; + return dev_err_probe(tb->nhi->dev, ret, "failed to configure host router\n"); } /* Announce the switch to the world */ ret = tb_switch_add(tb->root_switch); if (ret) { tb_switch_put(tb->root_switch); - return ret; + return dev_err_probe(tb->nhi->dev, ret, "failed to add host router\n"); } /* @@ -3110,6 +3120,24 @@ static void tb_restore_children(struct tb_switch *sw) } } +static void tb_free_unplugged_xdomains(struct tb_switch *sw) +{ + struct tb_port *port; + + tb_switch_for_each_port(sw, port) { + if (tb_is_upstream_port(port)) + continue; + if (port->xdomain && port->xdomain->is_unplugged) { + tb_retimer_remove_all(port); + tb_xdomain_remove(port->xdomain); + tb_port_unconfigure_xdomain(port); + port->xdomain = NULL; + } else if (port->remote) { + tb_free_unplugged_xdomains(port->remote->sw); + } + } +} + static int tb_resume_noirq(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); @@ -3129,6 +3157,7 @@ static int tb_resume_noirq(struct tb *tb) tb_switch_resume(tb->root_switch, false); tb_free_invalid_tunnels(tb); tb_free_unplugged_children(tb->root_switch); + tb_free_unplugged_xdomains(tb->root_switch); tb_restore_children(tb->root_switch); /* @@ -3171,28 +3200,6 @@ static int tb_resume_noirq(struct tb *tb) return 0; } -static int tb_free_unplugged_xdomains(struct tb_switch *sw) -{ - struct tb_port *port; - int ret = 0; - - tb_switch_for_each_port(sw, port) { - if (tb_is_upstream_port(port)) - continue; - if (port->xdomain && port->xdomain->is_unplugged) { - tb_retimer_remove_all(port); - tb_xdomain_remove(port->xdomain); - tb_port_unconfigure_xdomain(port); - port->xdomain = NULL; - ret++; - } else if (port->remote) { - ret += tb_free_unplugged_xdomains(port->remote->sw); - } - } - - return ret; -} - static int tb_freeze_noirq(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); @@ -3212,14 +3219,14 @@ static int tb_thaw_noirq(struct tb *tb) static void tb_complete(struct tb *tb) { /* - * Release any unplugged XDomains and if there is a case where + * Unregister unplugged XDomains and if there is a case where * another domain is swapped in place of unplugged XDomain we * need to run another rescan. */ - mutex_lock(&tb->lock); - if (tb_free_unplugged_xdomains(tb->root_switch)) - tb_scan_switch(tb->root_switch); - mutex_unlock(&tb->lock); + if (tb_domain_unregister_unplugged_xdomains(tb)) { + scoped_guard(mutex, &tb->lock) + tb_scan_switch(tb->root_switch); + } } static int tb_runtime_suspend(struct tb *tb) @@ -3246,11 +3253,11 @@ static void tb_remove_work(struct work_struct *work) struct tb *tb = tcm_to_tb(tcm); mutex_lock(&tb->lock); - if (tb->root_switch) { + if (tb->root_switch) tb_free_unplugged_children(tb->root_switch); - tb_free_unplugged_xdomains(tb->root_switch); - } mutex_unlock(&tb->lock); + + tb_free_unplugged_xdomains(tb->root_switch); } static int tb_runtime_resume(struct tb *tb) @@ -3304,13 +3311,14 @@ static const struct tb_cm_ops tb_cm_ops = { */ static bool tb_apple_add_links(struct tb_nhi *nhi) { + struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev); struct pci_dev *upstream, *pdev; bool ret; if (!x86_apple_machine) return false; - switch (nhi->pdev->device) { + switch (nhi_pdev->device) { case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE: case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: @@ -3320,7 +3328,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) return false; } - upstream = pci_upstream_bridge(nhi->pdev); + upstream = pci_upstream_bridge(nhi_pdev); while (upstream) { if (!pci_is_pcie(upstream)) return false; @@ -3347,15 +3355,15 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) !pdev->is_pciehp) continue; - link = device_link_add(&pdev->dev, &nhi->pdev->dev, + link = device_link_add(&pdev->dev, nhi->dev, DL_FLAG_AUTOREMOVE_SUPPLIER | DL_FLAG_PM_RUNTIME); if (link) { - dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); ret = true; } else { - dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_warn(nhi->dev, "device link creation from %s failed\n", dev_name(&pdev->dev)); } } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 217c3114bec8..ec9192b61bc0 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -725,11 +725,11 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, length); } -#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_dbg(tb, fmt, arg...) dev_dbg(&(tb)->nhi->pdev->dev, fmt, ## arg) +#define tb_err(tb, fmt, arg...) dev_err((tb)->nhi->dev, fmt, ## arg) +#define tb_WARN(tb, fmt, arg...) dev_WARN((tb)->nhi->dev, fmt, ## arg) +#define tb_warn(tb, fmt, arg...) dev_warn((tb)->nhi->dev, fmt, ## arg) +#define tb_info(tb, fmt, arg...) dev_info((tb)->nhi->dev, fmt, ## arg) +#define tb_dbg(tb, fmt, arg...) dev_dbg((tb)->nhi->dev, fmt, ## arg) #define __TB_SW_PRINT(level, sw, fmt, arg...) \ do { \ @@ -793,6 +793,7 @@ 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 tb_domain_disconnect_all_paths(struct tb *tb); +int tb_domain_unregister_unplugged_xdomains(struct tb *tb); static inline struct tb *tb_domain_get(struct tb *tb) { @@ -1263,6 +1264,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, const uuid_t *remote_uuid); void tb_xdomain_add(struct tb_xdomain *xd); void tb_xdomain_remove(struct tb_xdomain *xd); +void tb_xdomain_unregister(struct tb_xdomain *xd); struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, u8 depth); @@ -1479,6 +1481,7 @@ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw); int usb4_dp_port_requested_bandwidth(struct tb_port *port); int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable); +int usb4_pci_port_ltssm_state(struct tb_port *port); static inline bool tb_is_usb4_port_device(const struct device *dev) { @@ -1556,4 +1559,12 @@ static inline void tb_retimer_debugfs_init(struct tb_retimer *rt) { } static inline void tb_retimer_debugfs_remove(struct tb_retimer *rt) { } #endif +#if IS_REACHABLE(CONFIG_CONFIGFS_FS) +int tb_configfs_init(void); +void tb_configfs_exit(void); +#else +static inline int tb_configfs_init(void) { return 0; } +static inline void tb_configfs_exit(void) { } +#endif + #endif diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index c0bf136236e6..92d893634d2b 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -182,8 +182,7 @@ struct tb_regs_switch_header { /* DWORD 4 */ u32 plug_events_delay:8; /* * RW, pause between plug events in - * milliseconds. Writing 0x00 is interpreted - * as 255ms. + * milliseconds. */ u32 cmuv:8; u32 __unknown4:8; @@ -216,6 +215,7 @@ struct tb_regs_switch_header { #define ROUTER_CS_6_WOPS BIT(2) #define ROUTER_CS_6_WOUS BIT(3) #define ROUTER_CS_6_HCI BIT(18) +#define ROUTER_CS_6_RR BIT(24) #define ROUTER_CS_6_CR BIT(25) #define ROUTER_CS_7 0x07 #define ROUTER_CS_9 0x09 @@ -473,10 +473,25 @@ struct tb_regs_port_header { /* PCIe adapter registers */ #define ADP_PCIE_CS_0 0x00 +#define ADP_PCIE_CS_0_LTSSM_MASK GENMASK(28, 25) #define ADP_PCIE_CS_0_PE BIT(31) #define ADP_PCIE_CS_1 0x01 #define ADP_PCIE_CS_1_EE BIT(0) +enum tb_pcie_ltssm_state { + USB4_PCIE_LTSSM_DETECT, + USB4_PCIE_LTSSM_POLLING, + USB4_PCIE_LTSSM_CONFIG, + USB4_PCIE_LTSSM_CONFIG_IDLE, + USB4_PCIE_LTSSM_RECOVERY, + USB4_PCIE_LTSSM_RECOVERY_IDLE, + USB4_PCIE_LTSSM_L0, + USB4_PCIE_LTSSM_L1, + USB4_PCIE_LTSSM_L2, + USB4_PCIE_LTSSM_DISABLED, + USB4_PCIE_LTSSM_HOT_RESET, +}; + /* USB adapter registers */ #define ADP_USB3_CS_0 0x00 #define ADP_USB3_CS_0_V BIT(30) diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 1f4318249c22..05652ee82fbf 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -1661,6 +1661,7 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel3->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel3->paths[0]->path_length, 3); + tb_tunnel_put(tunnel3); tb_tunnel_put(tunnel2); tb_tunnel_put(tunnel1); } @@ -2852,7 +2853,252 @@ static void tb_test_property_copy(struct kunit *test) tb_property_free_dir(src); } +/* + * Reproducers for three memory-safety defects in + * drivers/thunderbolt/property.c reached from a crafted XDomain + * PROPERTIES_RESPONSE payload. Without the fix these trip KASAN or + * smash the kernel stack; with the fix each returns NULL cleanly. + * + * The on-wire entry layout matches struct tb_property_entry in + * property.c (private to that translation unit): u32 key_hi, u32 + * key_lo, then a packed u32 = (type << 24) | (reserved << 16) | + * length, then u32 value. Each entry is 4 dwords. + */ + +static void tb_test_property_parse_u32_wrap(struct kunit *test) +{ + /* + * 0x102 dwords: enough for the entry's length field (0x100) to + * pass the "entry->length > block_len" gate so the wrap check + * is actually exercised. parse_dwdata's downstream OOB read + * lands ~16 GiB past the allocation regardless. + */ + u32 *block = kunit_kzalloc(test, 0x102 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + + KUNIT_ASSERT_NOT_NULL(test, block); + + block[0] = 0x55584401; /* "UXD" v1 magic */ + block[1] = 0x00000004; /* Root directory length: one entry */ + + /* + * DATA entry whose value 0xffffff00 + length 0x100 wrap to 0 + * in u32, passing the sum <= block_len guard even though the + * real offset is far past the allocation. + */ + block[2] = 0x61616161; /* key_hi */ + block[3] = 0x61616161; /* key_lo */ + block[4] = 0x64000100; /* type=DATA, reserved=0, length=0x100 */ + block[5] = 0xffffff00; /* value */ + + dir = tb_property_parse_dir(block, 0x102); + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_parse_recursion(struct kunit *test) +{ + /* + * 10 dwords: rootdir header (2) + parent DIRECTORY entry (4) + + * the child entry that lives at dir_offset(2) + UUID(4) = 6, + * occupying block[6..9]. Each recursive level re-reads the + * same block[6..9] as its first child entry, which is itself + * a DIRECTORY pointing at offset 2. + */ + u32 *block = kunit_kzalloc(test, 10 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + + KUNIT_ASSERT_NOT_NULL(test, block); + + block[0] = 0x55584401; /* "UXD" v1 magic */ + block[1] = 0x00000004; /* Root directory length: one entry */ + + /* + * DIRECTORY entry pointing at dir_offset = 2 with length = 8. + * Non-root parse derives content_offset = 6, content_len = 4, + * nentries = 1. block[6..9] is read both as the parent's UUID + * (kmemdup'd into dir->uuid) and as the single child entry -- + * which is itself a DIRECTORY pointing at offset 2, so the + * recursion never terminates and the kernel stack is exhausted. + */ + block[2] = 0x61616161; /* key_hi */ + block[3] = 0x61616161; /* key_lo */ + block[4] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */ + block[5] = 0x00000002; /* value = dir_offset */ + + block[6] = 0x62626262; /* doubles as UUID dword 0 / child key_hi */ + block[7] = 0x62626262; /* doubles as UUID dword 1 / child key_lo */ + block[8] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */ + block[9] = 0x00000002; /* value = dir_offset (back at parent) */ + + dir = tb_property_parse_dir(block, 10); + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_parse_dir_len_underflow(struct kunit *test) +{ + /* + * Allocate exactly 7 dwords (28 bytes) so the kmalloc-32 chunk + * leaves a 4-byte slab redzone tail that KASAN-Generic can flag. + * With block_len = 7, dir_offset = 4, dir_len = 3, the non-root + * UUID kmemdup reads 16 bytes from byte 16, so bytes 28..31 fall + * in the redzone and trip a KASAN slab-out-of-bounds report on + * the pre-fix kernel. Sizing the buffer at a power of two (32, + * 64, ...) puts the over-read into the slab cache tail where + * KASAN's generic shadow does not flag it, and the test reduces + * to the downstream content_len = dir_len - 4 underflow path + * which also returns NULL. + */ + u32 *block = kunit_kzalloc(test, 7 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + + KUNIT_ASSERT_NOT_NULL(test, block); + + block[0] = 0x55584401; /* "UXD" v1 magic */ + block[1] = 0x00000004; /* Root directory length: one entry */ + + /* + * DIRECTORY entry with length = 3 pointing at dir_offset = 4. + * tb_property_entry_valid() permits value(4) + length(3) <= + * block_len(7). Non-root parse begins with a kmemdup of 4 + * dwords from dir_offset for the UUID; that read runs past the + * 28-byte allocation before the dir_len < 4 reject would fire. + */ + block[2] = 0x61616161; /* key_hi */ + block[3] = 0x61616161; /* key_lo */ + block[4] = 0x44000003; /* type=DIRECTORY, reserved=0, length=3 */ + block[5] = 0x00000004; /* value = dir_offset */ + /* block[6] is the start of the four UUID dwords; block[7..] is OOB. */ + + dir = tb_property_parse_dir(block, 7); + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_parse_zero_length(struct kunit *test) +{ + u32 *block = kunit_kzalloc(test, 6 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + + KUNIT_ASSERT_NOT_NULL(test, block); + + block[0] = 0x55584401; /* rootdir magic */ + block[1] = 0x00000004; /* root length: one entry */ + + block[2] = 0x61616161; /* key_hi */ + block[3] = 0x61616161; /* key_lo */ + block[4] = 0x74000000; /* type=TEXT, reserved=0, length=0 */ + block[5] = 0x00000000; /* value */ + + dir = tb_property_parse_dir(block, 6); + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_parse_rootdir_overflow(struct kunit *test) +{ + u32 *block = kunit_kzalloc(test, 4 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + + KUNIT_ASSERT_NOT_NULL(test, block); + + block[0] = 0x55584401; /* rootdir magic */ + block[1] = 0x00000004; /* root length claims 4 dwords of content */ + block[2] = 0x61616161; + block[3] = 0x61616161; + + /* content_offset(2) + content_len(4) = 6 > block_len(4) */ + dir = tb_property_parse_dir(block, 4); + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_merge(struct kunit *test) +{ + struct tb_property_dir *dir1, *dir2, *dir3; + struct tb_property *p; + uuid_t uuid; + int ret; + + dir1 = tb_property_create_dir(&network_dir_uuid); + KUNIT_ASSERT_NOT_NULL(test, dir1); + ret = tb_property_add_immediate(dir1, "prtcid", 1); + KUNIT_EXPECT_EQ(test, ret, 0); + ret = tb_property_add_immediate(dir1, "prtcvers", 1); + KUNIT_EXPECT_EQ(test, ret, 0); + ret = tb_property_add_immediate(dir1, "prtcrevs", 0); + KUNIT_EXPECT_EQ(test, ret, 0); + ret = tb_property_add_immediate(dir1, "prtcstns", 0); + KUNIT_EXPECT_EQ(test, ret, 0); + + dir2 = tb_property_create_dir(&network_dir_uuid); + KUNIT_ASSERT_NOT_NULL(test, dir2); + ret = tb_property_add_text(dir2, "descr", "This is text"); + KUNIT_EXPECT_EQ(test, ret, 0); + /* This replaces the value in dir1 */ + ret = tb_property_add_immediate(dir2, "prtcvers", 0x1234); + KUNIT_EXPECT_EQ(test, ret, 0); + + uuid_gen(&uuid); + dir3 = tb_property_create_dir(&uuid); + KUNIT_ASSERT_NOT_NULL(test, dir3); + ret = tb_property_add_immediate(dir3, "value0", 0); + KUNIT_EXPECT_EQ(test, ret, 0); + ret = tb_property_add_text(dir3, "value1", "Text value"); + KUNIT_EXPECT_EQ(test, ret, 0); + ret = tb_property_add_dir(dir2, "my", dir3); + KUNIT_EXPECT_EQ(test, ret, 0); + + ret = tb_property_merge_dir(dir1, dir2, true); + KUNIT_EXPECT_EQ(test, ret, 0); + + p = tb_property_get_next(dir1, NULL); + KUNIT_ASSERT_NOT_NULL(test, p); + KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcid"); + KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE); + KUNIT_ASSERT_EQ(test, p->length, 1); + KUNIT_ASSERT_EQ(test, p->value.immediate, 1); + p = tb_property_get_next(dir1, p); + KUNIT_ASSERT_NOT_NULL(test, p); + KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcvers"); + KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE); + KUNIT_ASSERT_EQ(test, p->length, 1); + KUNIT_ASSERT_EQ(test, p->value.immediate, 0x1234); + p = tb_property_get_next(dir1, p); + KUNIT_ASSERT_NOT_NULL(test, p); + KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcrevs"); + KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE); + KUNIT_ASSERT_EQ(test, p->length, 1); + KUNIT_ASSERT_EQ(test, p->value.immediate, 0); + p = tb_property_get_next(dir1, p); + KUNIT_ASSERT_NOT_NULL(test, p); + KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcstns"); + KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE); + KUNIT_ASSERT_EQ(test, p->length, 1); + KUNIT_ASSERT_EQ(test, p->value.immediate, 0); + p = tb_property_get_next(dir1, p); + KUNIT_ASSERT_NOT_NULL(test, p); + KUNIT_ASSERT_STREQ(test, &p->key[0], "descr"); + KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_TEXT); + KUNIT_ASSERT_EQ(test, p->length, 4); + KUNIT_ASSERT_STREQ(test, p->value.text, "This is text"); + p = tb_property_get_next(dir1, p); + KUNIT_ASSERT_NOT_NULL(test, p); + KUNIT_ASSERT_STREQ(test, &p->key[0], "my"); + KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_DIRECTORY); + compare_dirs(test, p->value.dir, dir3); + p = tb_property_get_next(dir1, p); + KUNIT_ASSERT_NULL(test, p); + + tb_property_free_dir(dir2); + tb_property_free_dir(dir1); +} + static struct kunit_case tb_test_cases[] = { + KUNIT_CASE(tb_test_property_parse_u32_wrap), + KUNIT_CASE(tb_test_property_parse_recursion), + KUNIT_CASE(tb_test_property_parse_dir_len_underflow), KUNIT_CASE(tb_test_path_basic), KUNIT_CASE(tb_test_path_not_connected_walk), KUNIT_CASE(tb_test_path_single_hop_walk), @@ -2892,6 +3138,9 @@ static struct kunit_case tb_test_cases[] = { KUNIT_CASE(tb_test_property_parse), KUNIT_CASE(tb_test_property_format), KUNIT_CASE(tb_test_property_copy), + KUNIT_CASE(tb_test_property_parse_zero_length), + KUNIT_CASE(tb_test_property_parse_rootdir_overflow), + KUNIT_CASE(tb_test_property_merge), { } }; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index f38f7753b6e4..b7f32305f14a 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -290,6 +290,40 @@ static inline void tb_tunnel_changed(struct tb_tunnel *tunnel) tunnel->src_port, tunnel->dst_port); } +static int tb_pci_port_ltssm_state_detect(struct tb_port *port) +{ + ktime_t timeout = ktime_add_ms(ktime_get(), 500); + + do { + int ret; + + ret = usb4_pci_port_ltssm_state(port); + if (ret < 0) + return ret; + if (ret == USB4_PCIE_LTSSM_DETECT) + return 0; + + fsleep(50); + } while (ktime_before(ktime_get(), timeout)); + + return -ETIMEDOUT; +} + +static int tb_pci_pre_activate(struct tb_tunnel *tunnel) +{ + struct tb_port *down = tunnel->src_port; + struct tb_port *up = tunnel->dst_port; + int ret; + + ret = tb_switch_is_usb4(down->sw) ? + tb_pci_port_ltssm_state_detect(down) : 0; + if (ret) + return ret; + + return tb_switch_is_usb4(up->sw) ? + tb_pci_port_ltssm_state_detect(up) : 0; +} + static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable) { struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw); @@ -505,6 +539,7 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, if (!tunnel) return NULL; + tunnel->pre_activate = tb_pci_pre_activate; tunnel->activate = tb_pci_activate; tunnel->src_port = down; tunnel->dst_port = up; diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 9e810b2ae0b5..5fd4fe070f25 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -294,7 +294,12 @@ int usb4_switch_setup(struct tb_switch *sw) /* TBT3 supported by the CM */ val &= ~ROUTER_CS_5_CNS; - return tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); + if (ret) + return ret; + + return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_RR, + ROUTER_CS_6_RR, 500); } /** @@ -304,7 +309,7 @@ int usb4_switch_setup(struct tb_switch *sw) * Sets configuration valid bit for the router. Must be called before * any tunnels can be set through the router and after * usb4_switch_setup() has been called. Can be called to host and device - * routers (does nothing for the latter). + * routers (does nothing for the former). * * Return: %0 on success, negative errno otherwise. */ @@ -327,7 +332,7 @@ int usb4_switch_configuration_valid(struct tb_switch *sw) return ret; return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR, - ROUTER_CS_6_CR, 50); + ROUTER_CS_6_CR, 500); } /** @@ -3145,3 +3150,27 @@ int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable) return tb_port_write(port, &val, TB_CFG_PORT, port->cap_adap + ADP_PCIE_CS_1, 1); } + +/** + * usb4_pci_port_ltssm_state() - Read PCIe adapter LTSSM state + * @port: PCIe adapter + * + * Return: + * * LTSSM state of @port. + * * Negative errno - On failure. + */ +int usb4_pci_port_ltssm_state(struct tb_port *port) +{ + u32 val; + int ret; + + if (!tb_port_is_pcie_down(port) && !tb_port_is_pcie_up(port)) + return -EINVAL; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_PCIE_CS_0, 1); + if (ret) + return ret; + + return FIELD_GET(ADP_PCIE_CS_0_LTSSM_MASK, val); +} diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index c32d3516e780..890de530debc 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -138,7 +138,7 @@ bool usb4_usb3_port_match(struct device *usb4_port_dev, return false; /* Check if USB3 fwnode references same NHI where USB4 port resides */ - if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode)) + if (!device_match_fwnode(nhi->dev, nhi_fwnode)) return false; if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num)) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 1fd1cf4295a2..86b2f7474670 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -139,6 +139,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response, size_t size, enum tb_cfg_pkg_type type) { struct tb_cfg_request *req; + int ret; req = tb_cfg_request_alloc(); if (!req) @@ -150,7 +151,11 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response, req->request_size = size; req->request_type = type; - return tb_cfg_request(ctl, req, response_ready, req); + ret = tb_cfg_request(ctl, req, response_ready, req); + if (ret) + tb_cfg_request_put(req); + + return ret; } /** @@ -540,29 +545,19 @@ static int tb_xdp_link_state_status_request(struct tb_ctl *ctl, u64 route, } static int tb_xdp_link_state_status_response(struct tb *tb, struct tb_ctl *ctl, - struct tb_xdomain *xd, u8 sequence) + struct tb_xdomain *xd, u8 sequence, + u8 slw, u8 sls, u8 tls, u8 tlw) { struct tb_xdp_link_state_status_response res; - struct tb_port *port = tb_xdomain_downstream_port(xd); - u32 val[2]; - int ret; memset(&res, 0, sizeof(res)); tb_xdp_fill_header(&res.hdr, xd->route, sequence, LINK_STATE_STATUS_RESPONSE, sizeof(res)); - ret = tb_port_read(port, val, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_0, ARRAY_SIZE(val)); - if (ret) - return ret; - - res.slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >> - LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT; - res.sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >> - LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT; - res.tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK; - res.tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >> - LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; + res.slw = slw; + res.sls = sls; + res.tls = tls; + res.tlw = tlw; return __tb_xdomain_response(ctl, &res, sizeof(res), TB_CFG_PKG_XDOMAIN_RESP); @@ -650,6 +645,32 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler) } EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler); +static int update_service_properties(struct device *dev, void *data) +{ + struct tb_property_dir *root = data; + struct tb_service *svc; + struct tb_property *p; + + svc = tb_to_service(dev); + if (!svc) + return 0; + + guard(mutex)(&svc->lock); + + /* + * Replace the static service properties with the dynamic one. + * Typically this is the same but service drivers can add their + * own dynamic properties here too. + */ + p = tb_property_find(root, svc->key, TB_PROPERTY_TYPE_DIRECTORY); + if (!p) + return 0; + if (svc->local_properties) + return tb_property_merge_dir(p->value.dir, + svc->local_properties, false); + return 0; +} + static void update_property_block(struct tb_xdomain *xd) { mutex_lock(&xdomain_lock); @@ -674,6 +695,9 @@ static void update_property_block(struct tb_xdomain *xd) tb_property_add_text(dir, "deviceid", utsname()->nodename); tb_property_add_immediate(dir, "maxhopid", xd->local_max_hopid); + /* Add service specific dynamic properties */ + device_for_each_child(&xd->dev, dir, update_service_properties); + ret = tb_property_format_dir(dir, NULL, 0); if (ret < 0) { dev_warn(&xd->dev, "local property block creation failed\n"); @@ -751,7 +775,7 @@ static void tb_xdp_handle_request(struct work_struct *work) mutex_lock(&tb->lock); if (tb->root_switch) - uuid = tb->root_switch->uuid; + uuid = kmemdup(tb->root_switch->uuid, sizeof(*uuid), GFP_KERNEL); else uuid = NULL; mutex_unlock(&tb->lock); @@ -785,9 +809,13 @@ static void tb_xdp_handle_request(struct work_struct *work) * the xdomain related to this connection as well in * case there is a change in services it offers. */ - if (xd && device_is_registered(&xd->dev)) - queue_delayed_work(tb->wq, &xd->state_work, - msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + if (xd) { + mutex_lock(&xd->lock); + if (!xd->removing && device_is_registered(&xd->dev)) + queue_delayed_work(tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + mutex_unlock(&xd->lock); + } break; case UUID_REQUEST_OLD: @@ -800,8 +828,12 @@ static void tb_xdp_handle_request(struct work_struct *work) * received UUID request from the remote host. */ if (!ret && xd && xd->state == XDOMAIN_STATE_ERROR) { - dev_dbg(&xd->dev, "restarting handshake\n"); - start_handshake(xd); + mutex_lock(&xd->lock); + if (!xd->removing) { + dev_dbg(&xd->dev, "restarting handshake\n"); + start_handshake(xd); + } + mutex_unlock(&xd->lock); } break; @@ -810,8 +842,47 @@ static void tb_xdp_handle_request(struct work_struct *work) route); if (xd) { - ret = tb_xdp_link_state_status_response(tb, ctl, xd, - sequence); + struct tb_port *port = tb_xdomain_downstream_port(xd); + u8 slw, sls, tls, tlw; + u32 val[2]; + + /* + * Read the adapter supported and target widths + * and speeds. + */ + ret = tb_port_read(port, val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_0, + ARRAY_SIZE(val)); + if (ret) + break; + + slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >> + LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT; + sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >> + LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT; + tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK; + tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >> + LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; + + /* + * When we have higher UUID, we are supposed to + * return ERROR_NOT_READY if the tlw is not yet + * set according to the Inter-Domain spec for + * USB4 v2. + */ + if (xd->state == XDOMAIN_STATE_BONDING_UUID_HIGH && + xd->target_link_width && + xd->target_link_width != tlw) { + tb_dbg(tb, "%llx: target link width not yet set %#x != %#x\n", + route, tlw, xd->target_link_width); + tb_xdp_error_response(ctl, route, sequence, + ERROR_NOT_READY); + } else { + tb_dbg(tb, "%llx: replying with target link width set to %#x\n", + route, tlw); + ret = tb_xdp_link_state_status_response(tb, ctl, + xd, sequence, slw, sls, tls, tlw); + } } else { tb_xdp_error_response(ctl, route, sequence, ERROR_NOT_READY); @@ -829,9 +900,13 @@ static void tb_xdp_handle_request(struct work_struct *work) ret = tb_xdp_link_state_change_response(ctl, route, sequence, 0); - xd->target_link_width = lsc->tlw; - queue_delayed_work(tb->wq, &xd->state_work, - msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + mutex_lock(&xd->lock); + if (!xd->removing) { + xd->target_link_width = lsc->tlw; + queue_delayed_work(tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + } + mutex_unlock(&xd->lock); } else { tb_xdp_error_response(ctl, route, sequence, ERROR_NOT_READY); @@ -853,6 +928,7 @@ static void tb_xdp_handle_request(struct work_struct *work) } out: + kfree(uuid); kfree(xw->pkg); kfree(xw); @@ -909,6 +985,44 @@ void tb_unregister_service_driver(struct tb_service_driver *drv) } EXPORT_SYMBOL_GPL(tb_unregister_service_driver); +static int update_xdomain(struct device *dev, void *data) +{ + struct tb_xdomain *xd; + + xd = tb_to_xdomain(dev); + if (xd) { + mutex_lock(&xd->lock); + if (!xd->removing) + queue_delayed_work(xd->tb->wq, + &xd->properties_changed_work, + msecs_to_jiffies(50)); + mutex_unlock(&xd->lock); + } + + return 0; +} + +/** + * tb_service_properties_changed() - Notify the other host about changes + * @svc: Service whose properties changed + * + * Notifies the other host that service properties may have been + * changed. This should be called whenever @svc->local_properties is + * updated. + */ +void tb_service_properties_changed(struct tb_service *svc) +{ + struct tb_xdomain *xd = tb_service_parent(svc); + + if (xd->is_unplugged) + return; + + scoped_guard(mutex, &xdomain_lock) + xdomain_property_block_gen++; + update_xdomain(&xd->dev, NULL); +} +EXPORT_SYMBOL_GPL(tb_service_properties_changed); + static ssize_t key_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1008,10 +1122,11 @@ static void tb_service_release(struct device *dev) struct tb_service *svc = container_of(dev, struct tb_service, dev); struct tb_xdomain *xd = tb_service_parent(svc); - tb_service_debugfs_remove(svc); + tb_property_free_dir(svc->remote_properties); ida_free(&xd->service_ids, svc->id); kfree(svc->key); kfree(svc); + tb_xdomain_put(xd); } const struct device_type tb_service_type = { @@ -1022,6 +1137,24 @@ const struct device_type tb_service_type = { }; EXPORT_SYMBOL_GPL(tb_service_type); +static void update_service(struct tb_service *svc, struct tb_property *property) +{ + struct tb_property_dir *dir = property->value.dir; + + guard(mutex)(&svc->lock); + tb_property_free_dir(svc->remote_properties); + svc->remote_properties = tb_property_copy_dir(dir); + kobject_uevent(&svc->dev.kobj, KOBJ_CHANGE); +} + +static void __unregister_service(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + + tb_service_debugfs_remove(svc); + device_unregister(&svc->dev); +} + static int remove_missing_service(struct device *dev, void *data) { struct tb_xdomain *xd = data; @@ -1033,7 +1166,7 @@ static int remove_missing_service(struct device *dev, void *data) if (!tb_property_find(xd->remote_properties, svc->key, TB_PROPERTY_TYPE_DIRECTORY)) - device_unregister(dev); + __unregister_service(dev); return 0; } @@ -1074,6 +1207,12 @@ static int populate_service(struct tb_service *svc, if (!svc->key) return -ENOMEM; + svc->remote_properties = tb_property_copy_dir(dir); + if (!svc->remote_properties) { + kfree(svc->key); + return -ENOMEM; + } + return 0; } @@ -1098,6 +1237,7 @@ static void enumerate_services(struct tb_xdomain *xd) /* If the service exists already we are fine */ dev = device_find_child(&xd->dev, p, find_service); if (dev) { + update_service(tb_to_service(dev), p); put_device(dev); continue; } @@ -1120,12 +1260,14 @@ static void enumerate_services(struct tb_xdomain *xd) svc->id = id; svc->dev.bus = &tb_bus_type; svc->dev.type = &tb_service_type; - svc->dev.parent = &xd->dev; + svc->dev.parent = get_device(&xd->dev); + mutex_init(&svc->lock); dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id); tb_service_debugfs_init(svc); if (device_register(&svc->dev)) { + tb_service_debugfs_remove(svc); put_device(&svc->dev); break; } @@ -1939,7 +2081,13 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd) if (tb_port_get_link_generation(down) >= 4) { down->bonded = false; down->dual_link_port->bonded = false; - } else if (xd->link_width > TB_LINK_WIDTH_SINGLE) { + return; + } + + if (!xd->bonding_possible) + return; + + if (xd->link_width > TB_LINK_WIDTH_SINGLE) { /* * Just return port structures back to way they were and * update credits. No need to update userspace because @@ -1996,6 +2144,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, INIT_DELAYED_WORK(&xd->state_work, tb_xdomain_state_work); INIT_DELAYED_WORK(&xd->properties_changed_work, tb_xdomain_properties_changed); + atomic_set(&xd->ntunnels, 0); xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t), GFP_KERNEL); if (!xd->local_uuid) @@ -2058,46 +2207,64 @@ void tb_xdomain_add(struct tb_xdomain *xd) static int unregister_service(struct device *dev, void *data) { - device_unregister(dev); + __unregister_service(dev); return 0; } /** - * tb_xdomain_remove() - Remove XDomain from the bus + * tb_xdomain_remove() - Remove XDomain * @xd: XDomain to remove * - * This will stop all ongoing configuration work and remove the XDomain - * along with any services from the bus. When the last reference to @xd - * is released the object will be released as well. + * This will stop all ongoing configuration work. XDomain is not removed + * from the bus if it was added. That needs to be done separately by + * calling tb_xdomain_unregister(). + * + * Called with @tb->lock held. */ void tb_xdomain_remove(struct tb_xdomain *xd) { tb_xdomain_debugfs_remove(xd); - stop_handshake(xd); - - device_for_each_child_reverse(&xd->dev, xd, unregister_service); + mutex_lock(&xd->lock); + xd->removing = true; + mutex_unlock(&xd->lock); + stop_handshake(xd); tb_xdomain_link_exit(xd); - /* - * Undo runtime PM here explicitly because it is possible that - * the XDomain was never added to the bus and thus device_del() - * is not called for it (device_del() would handle this otherwise). - */ - pm_runtime_disable(&xd->dev); - pm_runtime_put_noidle(&xd->dev); - pm_runtime_set_suspended(&xd->dev); - if (!device_is_registered(&xd->dev)) { + /* + * Undo runtime PM here explicitly because it is + * possible that the XDomain was never added to the bus + * and thus device_del() is not called for it + * (device_del() would handle this otherwise). + */ + pm_runtime_disable(&xd->dev); + pm_runtime_put_noidle(&xd->dev); + pm_runtime_set_suspended(&xd->dev); put_device(&xd->dev); - } else { - dev_info(&xd->dev, "host disconnected\n"); - device_unregister(&xd->dev); } } /** + * tb_xdomain_unregister() - Unregister XDomain + * @xd: XDomain to unregister + * + * This will unregister the XDomain along with any services from the + * bus. When the last reference to @xd is released the object will be + * released as well. + */ +void tb_xdomain_unregister(struct tb_xdomain *xd) +{ + lockdep_assert_not_held(&xd->tb->lock); + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); + + dev_info(&xd->dev, "host disconnected\n"); + device_unregister(&xd->dev); +} + +/** * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain * @xd: XDomain connection * @@ -2273,9 +2440,15 @@ int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { - return tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path, - transmit_ring, receive_path, - receive_ring); + int ret; + + ret = tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path, + transmit_ring, receive_path, + receive_ring); + if (ret) + return ret; + atomic_inc(&xd->ntunnels); + return 0; } EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths); @@ -2298,9 +2471,15 @@ int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { - return tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path, - transmit_ring, receive_path, - receive_ring); + int ret; + + ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path, + transmit_ring, receive_path, + receive_ring); + if (ret) + return ret; + atomic_dec(&xd->ntunnels); + return 0; } EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths); @@ -2316,6 +2495,9 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, { struct tb_port *port; + if (!sw) + return NULL; + tb_switch_for_each_port(sw, port) { struct tb_xdomain *xd; @@ -2478,19 +2660,6 @@ bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type, return ret > 0; } -static int update_xdomain(struct device *dev, void *data) -{ - struct tb_xdomain *xd; - - xd = tb_to_xdomain(dev); - if (xd) { - queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, - msecs_to_jiffies(50)); - } - - return 0; -} - static void update_all_xdomains(void) { bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain); diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 6f3c86149887..eecbd631fdab 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -14,8 +14,6 @@ obj-$(CONFIG_USB_DWC2) += dwc2/ obj-$(CONFIG_USB_ISP1760) += isp1760/ obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3/ -obj-$(CONFIG_USB_CDNS3) += cdns3/ -obj-$(CONFIG_USB_CDNSP_PCI) += cdns3/ obj-$(CONFIG_USB_FOTG210) += fotg210/ diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 11e3490d62ca..d610cdcef7d0 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -51,12 +51,6 @@ "[ueagle-atm vdbg] " format, ##args); \ } while (0) -#define uea_enters(usb_dev) \ - uea_vdbg(usb_dev, "entering %s\n" , __func__) - -#define uea_leaves(usb_dev) \ - uea_vdbg(usb_dev, "leaving %s\n" , __func__) - #define uea_err(usb_dev, format, args...) \ dev_err(&(usb_dev)->dev , "[UEAGLE-ATM] " format , ##args) @@ -606,7 +600,6 @@ static void uea_upload_pre_firmware(const struct firmware *fw_entry, u32 crc = 0; int ret, size; - uea_enters(usb); if (!fw_entry) { uea_err(usb, "firmware is not available\n"); goto err; @@ -670,7 +663,6 @@ err_fw_corrupted: uea_err(usb, "firmware is corrupted\n"); err: release_firmware(fw_entry); - uea_leaves(usb); } /* @@ -681,7 +673,6 @@ static int uea_load_firmware(struct usb_device *usb, unsigned int ver) int ret; char *fw_name = EAGLE_FIRMWARE; - uea_enters(usb); uea_info(usb, "pre-firmware device, uploading firmware\n"); switch (ver) { @@ -710,7 +701,6 @@ static int uea_load_firmware(struct usb_device *usb, unsigned int ver) else uea_info(usb, "loading firmware %s\n", fw_name); - uea_leaves(usb); return ret; } @@ -1137,7 +1127,6 @@ static int uea_cmv_e1(struct uea_softc *sc, struct cmv_e1 cmv; int ret; - uea_enters(INS_TO_USBDEV(sc)); uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Address : %c%c%c%c, " "offset : 0x%04x, data : 0x%08x\n", E1_FUNCTION_TYPE(function), @@ -1164,9 +1153,8 @@ static int uea_cmv_e1(struct uea_softc *sc, sizeof(cmv), &cmv); if (ret < 0) return ret; - ret = wait_cmv_ack(sc); - uea_leaves(INS_TO_USBDEV(sc)); - return ret; + + return wait_cmv_ack(sc); } static int uea_cmv_e4(struct uea_softc *sc, @@ -1175,7 +1163,6 @@ static int uea_cmv_e4(struct uea_softc *sc, struct cmv_e4 cmv; int ret; - uea_enters(INS_TO_USBDEV(sc)); memset(&cmv, 0, sizeof(cmv)); uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Group : 0x%04x, " @@ -1199,9 +1186,8 @@ static int uea_cmv_e4(struct uea_softc *sc, sizeof(cmv), &cmv); if (ret < 0) return ret; - ret = wait_cmv_ack(sc); - uea_leaves(INS_TO_USBDEV(sc)); - return ret; + + return wait_cmv_ack(sc); } static inline int uea_read_cmv_e1(struct uea_softc *sc, @@ -1295,7 +1281,6 @@ static int uea_stat_e1(struct uea_softc *sc) u32 data; int ret; - uea_enters(INS_TO_USBDEV(sc)); data = sc->stats.phy.state; ret = uea_read_cmv_e1(sc, E1_SA_STAT, 0, &sc->stats.phy.state); @@ -1438,7 +1423,6 @@ static int uea_stat_e4(struct uea_softc *sc) u32 tmp_arr[2]; int ret; - uea_enters(INS_TO_USBDEV(sc)); data = sc->stats.phy.state; /* XXX only need to be done before operationnal... */ @@ -1803,7 +1787,6 @@ static int uea_start_reset(struct uea_softc *sc) u16 zero = 0; /* ;-) */ int ret; - uea_enters(INS_TO_USBDEV(sc)); uea_info(INS_TO_USBDEV(sc), "(re)booting started\n"); /* mask interrupt */ @@ -1871,7 +1854,6 @@ static int uea_start_reset(struct uea_softc *sc) return ret; sc->reset = 0; - uea_leaves(INS_TO_USBDEV(sc)); return ret; } @@ -1887,7 +1869,6 @@ static int uea_kthread(void *data) int ret = -EAGAIN; set_freezable(); - uea_enters(INS_TO_USBDEV(sc)); while (!kthread_should_stop()) { if (ret < 0 || sc->reset) ret = uea_start_reset(sc); @@ -1896,7 +1877,7 @@ static int uea_kthread(void *data) if (ret != -EAGAIN) uea_wait(sc, 0, msecs_to_jiffies(1000)); } - uea_leaves(INS_TO_USBDEV(sc)); + return ret; } @@ -1909,8 +1890,6 @@ static int load_XILINX_firmware(struct uea_softc *sc) u8 value; char *fw_name = FPGA930_FIRMWARE; - uea_enters(INS_TO_USBDEV(sc)); - ret = request_firmware(&fw_entry, fw_name, &sc->usb_dev->dev); if (ret) { uea_err(INS_TO_USBDEV(sc), "firmware %s is not available\n", @@ -1954,7 +1933,6 @@ static int load_XILINX_firmware(struct uea_softc *sc) err1: release_firmware(fw_entry); err0: - uea_leaves(INS_TO_USBDEV(sc)); return ret; } @@ -1964,7 +1942,6 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr) struct cmv_dsc_e1 *dsc = &sc->cmv_dsc.e1; struct cmv_e1 *cmv = &intr->u.e1.s2.cmv; - uea_enters(INS_TO_USBDEV(sc)); if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE) goto bad1; @@ -1988,7 +1965,6 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr) if (cmv->bFunction == E1_MAKEFUNCTION(E1_ADSLDIRECTIVE, E1_MODEMREADY)) { wake_up_cmv_ack(sc); - uea_leaves(INS_TO_USBDEV(sc)); return; } @@ -2002,7 +1978,6 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr) sc->data = sc->data << 16 | sc->data >> 16; wake_up_cmv_ack(sc); - uea_leaves(INS_TO_USBDEV(sc)); return; bad2: @@ -2010,14 +1985,12 @@ bad2: "Function : %d, Subfunction : %d\n", E1_FUNCTION_TYPE(cmv->bFunction), E1_FUNCTION_SUBTYPE(cmv->bFunction)); - uea_leaves(INS_TO_USBDEV(sc)); return; bad1: uea_err(INS_TO_USBDEV(sc), "invalid cmv received, " "wPreamble %d, bDirection %d\n", le16_to_cpu(cmv->wPreamble), cmv->bDirection); - uea_leaves(INS_TO_USBDEV(sc)); } /* The modem send us an ack. First with check if it right */ @@ -2026,7 +1999,6 @@ static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr) struct cmv_dsc_e4 *dsc = &sc->cmv_dsc.e4; struct cmv_e4 *cmv = &intr->u.e4.s2.cmv; - uea_enters(INS_TO_USBDEV(sc)); uea_dbg(INS_TO_USBDEV(sc), "cmv %x %x %x %x %x %x\n", be16_to_cpu(cmv->wGroup), be16_to_cpu(cmv->wFunction), be16_to_cpu(cmv->wOffset), be16_to_cpu(cmv->wAddress), @@ -2038,7 +2010,6 @@ static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr) if (be16_to_cpu(cmv->wFunction) == E4_MAKEFUNCTION(E4_ADSLDIRECTIVE, E4_MODEMREADY, 1)) { wake_up_cmv_ack(sc); - uea_leaves(INS_TO_USBDEV(sc)); return; } @@ -2051,7 +2022,6 @@ static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr) sc->data = be32_to_cpu(cmv->dwData[0]); sc->data1 = be32_to_cpu(cmv->dwData[1]); wake_up_cmv_ack(sc); - uea_leaves(INS_TO_USBDEV(sc)); return; bad2: @@ -2059,7 +2029,6 @@ bad2: "Function : %d, Subfunction : %d\n", E4_FUNCTION_TYPE(cmv->wFunction), E4_FUNCTION_SUBTYPE(cmv->wFunction)); - uea_leaves(INS_TO_USBDEV(sc)); return; } @@ -2087,8 +2056,6 @@ static void uea_intr(struct urb *urb) struct intr_pkt *intr = urb->transfer_buffer; int status = urb->status; - uea_enters(INS_TO_USBDEV(sc)); - if (unlikely(status < 0)) { uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n", status); @@ -2128,8 +2095,6 @@ static int uea_boot(struct uea_softc *sc, struct usb_interface *intf) int ret = -ENOMEM; int size; - uea_enters(INS_TO_USBDEV(sc)); - if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { size = E4_INTR_PKT_SIZE; sc->dispatch_cmv = uea_dispatch_cmv_e4; @@ -2186,7 +2151,6 @@ static int uea_boot(struct uea_softc *sc, struct usb_interface *intf) goto err2; } - uea_leaves(INS_TO_USBDEV(sc)); return 0; err2: @@ -2196,7 +2160,6 @@ err1: sc->urb_int = NULL; kfree(intr); err0: - uea_leaves(INS_TO_USBDEV(sc)); return ret; } @@ -2206,7 +2169,7 @@ err0: static void uea_stop(struct uea_softc *sc) { int ret; - uea_enters(INS_TO_USBDEV(sc)); + ret = kthread_stop(sc->kthread); uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret); @@ -2220,7 +2183,6 @@ static void uea_stop(struct uea_softc *sc) flush_work(&sc->task); release_firmware(sc->dsp_firm); - uea_leaves(INS_TO_USBDEV(sc)); } /* syfs interface */ @@ -2493,8 +2455,6 @@ static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, int ret, ifnum = intf->altsetting->desc.bInterfaceNumber; unsigned int alt; - uea_enters(usb); - /* interface 0 is for firmware/monitoring */ if (ifnum != UEA_INTR_IFACE_NO) return -ENODEV; @@ -2587,8 +2547,7 @@ static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) struct usb_device *usb = interface_to_usbdev(intf); int ret; - uea_enters(usb); - uea_info(usb, "ADSL device founded vid (%#X) pid (%#X) Rev (%#X): %s\n", + uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n", le16_to_cpu(usb->descriptor.idVendor), le16_to_cpu(usb->descriptor.idProduct), le16_to_cpu(usb->descriptor.bcdDevice), @@ -2618,7 +2577,6 @@ static void uea_disconnect(struct usb_interface *intf) { struct usb_device *usb = interface_to_usbdev(intf); int ifnum = intf->altsetting->desc.bInterfaceNumber; - uea_enters(usb); /* ADI930 has 2 interfaces and eagle 3 interfaces. * Pre-firmware device has one interface @@ -2629,8 +2587,6 @@ static void uea_disconnect(struct usb_interface *intf) mutex_unlock(&uea_mutex); uea_info(usb, "ADSL device removed\n"); } - - uea_leaves(usb); } /* diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index 0a514b591527..39ad23d1ada8 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -1,6 +1,9 @@ config USB_CDNS_SUPPORT tristate "Cadence USB Support" - depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA + depends on USB_SUPPORT && HAS_DMA + depends on USB || USB_GADGET + depends on USB if !USB_GADGET + depends on USB_GADGET if !USB select USB_XHCI_PLATFORM if USB_XHCI_HCD select USB_ROLE_SWITCH help @@ -8,44 +11,49 @@ config USB_CDNS_SUPPORT dual-role controller. It supports: dual-role switch, Host-only, and Peripheral-only. -config USB_CDNS_HOST - bool - if USB_CDNS_SUPPORT config USB_CDNS3 - tristate "Cadence USB3 Dual-Role Controller" + tristate "Cadence USB dual-role controller (USBSS and USBSSP)" depends on USB_CDNS_SUPPORT help - Say Y here if your system has a Cadence USB3 dual-role controller. - It supports: dual-role switch, Host-only, and Peripheral-only. + Say Y or M here if your system has an on-chip Cadence USB + dual-role controller. This covers both USBSS (USB 3.0) and + USBSSP (SuperSpeed Plus) IP; the driver detects the variant at + runtime. - If you choose to build this driver is a dynamically linked - as module, the module will be called cdns3.ko. -endif + The core driver (core, DRD, generic platform binding for the + "cdns,usb3" device tree compatible, optional host and gadget) + builds as one module named cdns.ko when built as a loadable + module. + + It supports: dual-role switch, Host-only, and Peripheral-only. if USB_CDNS3 -config USB_CDNS3_GADGET - bool "Cadence USB3 device controller" - depends on USB_GADGET=y || USB_GADGET=USB_CDNS3 +config USB_CDNS3_HOST + bool "Cadence USB host controller (xHCI)" + depends on USB=y || USB=USB_CDNS3 help - Say Y here to enable device controller functionality of the - Cadence USBSS-DEV driver. + Say Y here to enable host controller functionality for Cadence + USBSS and USBSSP dual-role controllers. - This controller supports FF, HS and SS mode. It doesn't support - LS and SSP mode. + The host controller is xHCI compliant and uses the standard + xHCI driver. -config USB_CDNS3_HOST - bool "Cadence USB3 host controller" - depends on USB=y || USB=USB_CDNS3 - select USB_CDNS_HOST +config USB_CDNS3_GADGET + bool "Cadence USB device controller (USBSS and USBSSP)" + depends on USB_GADGET=y || USB_GADGET=USB_CDNS3 help - Say Y here to enable host controller functionality of the - Cadence driver. + Say Y here to include Cadence USB device (gadget) support for + both USBSS (USB 3.0) and USBSSP (SuperSpeed Plus) IP in the + cdns.ko module. The implementation is selected at runtime from + the detected controller version. - Host controller is compliant with XHCI so it will use - standard XHCI driver. + USBSS gadget supports FF, HS and SS mode (not LS or SSP). + USBSSP gadget supports FF, HS, SS and SSP mode (not LS). + +comment "Platform glue driver support" config USB_CDNS3_PCI_WRAP tristate "Cadence USB3 support on PCIe-based platforms" @@ -58,6 +66,17 @@ config USB_CDNS3_PCI_WRAP If you choose to build this driver as module it will be dynamically linked and module will be called cdns3-pci.ko +config USB_CDNSP_PCI + tristate "Cadence USBSSP support on PCIe-based platforms" + depends on USB_PCI && ACPI + default USB_CDNS3 + help + If you're using the USBSSP Core IP with a PCIe, please say + 'Y' or 'M' here. + + If you choose to build this driver as module it will + be dynamically linked and module will be called cdnsp-pci.ko + config USB_CDNS3_TI tristate "Cadence USB3 support on TI platforms" depends on ARCH_K3 || COMPILE_TEST @@ -81,6 +100,7 @@ config USB_CDNS3_IMX config USB_CDNS3_STARFIVE tristate "Cadence USB3 support on StarFive SoC platforms" depends on ARCH_STARFIVE || COMPILE_TEST + default USB_CDNS3 help Say 'Y' or 'M' here if you are building for StarFive SoCs platforms that contain Cadence USB3 controller core. @@ -89,45 +109,7 @@ config USB_CDNS3_STARFIVE If you choose to build this driver as module it will be dynamically linked and module will be called cdns3-starfive.ko -endif - -if USB_CDNS_SUPPORT - -config USB_CDNSP_PCI - tristate "Cadence CDNSP Dual-Role Controller" - depends on USB_CDNS_SUPPORT && USB_PCI && ACPI - help - Say Y here if your system has a Cadence CDNSP dual-role controller. - It supports: dual-role switch Host-only, and Peripheral-only. - - If you choose to build this driver is a dynamically linked - module, the module will be called cdnsp.ko. -endif - -if USB_CDNSP_PCI - -config USB_CDNSP_GADGET - bool "Cadence CDNSP device controller" - depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI - help - Say Y here to enable device controller functionality of the - Cadence CDNSP-DEV driver. - - Cadence CDNSP Device Controller in device mode is - very similar to XHCI controller. Therefore some algorithms - used has been taken from host driver. - This controller supports FF, HS, SS and SSP mode. - It doesn't support LS. - -config USB_CDNSP_HOST - bool "Cadence CDNSP host controller" - depends on USB=y || USB=USB_CDNSP_PCI - select USB_CDNS_HOST - help - Say Y here to enable host controller functionality of the - Cadence driver. - Host controller is compliant with XHCI so it uses - standard XHCI driver. +endif # USB_CDNS3 -endif +endif # USB_CDNS_SUPPORT diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 48dfae75b5aa..b2e4ba6a49a3 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -3,42 +3,28 @@ CFLAGS_cdns3-trace.o := -I$(src) CFLAGS_cdnsp-trace.o := -I$(src) -cdns-usb-common-y := core.o drd.o -cdns3-y := cdns3-plat.o +obj-$(CONFIG_USB_CDNS3) += cdns.o -ifeq ($(CONFIG_USB),m) -obj-m += cdns-usb-common.o -obj-m += cdns3.o -else -obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o -obj-$(CONFIG_USB_CDNS3) += cdns3.o -endif +cdns-y := core.o drd.o cdns3-plat.o +cdns-$(CONFIG_USB_CDNS3_HOST) += host.o -cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o -cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o +ifneq ($(CONFIG_USB_CDNS3_GADGET),) +cdns-y += cdns3-gadget.o cdns3-ep0.o \ + cdnsp-ring.o cdnsp-gadget.o \ + cdnsp-mem.o cdnsp-ep0.o +endif +ifneq ($(CONFIG_TRACING),) ifneq ($(CONFIG_USB_CDNS3_GADGET),) -cdns3-$(CONFIG_TRACING) += cdns3-trace.o +cdns-y += cdns3-trace.o cdnsp-trace.o +endif endif +## +# Platform-specific glue layers (PCI wrappers, SoC integration) +## obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o +obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-pci.o obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o - -cdnsp-udc-pci-y := cdnsp-pci.o - -ifdef CONFIG_USB_CDNSP_PCI -ifeq ($(CONFIG_USB),m) -obj-m += cdnsp-udc-pci.o -else -obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o -endif -endif - -cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \ - cdnsp-mem.o cdnsp-ep0.o - -ifneq ($(CONFIG_USB_CDNSP_GADGET),) -cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o -endif diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 1db8db1b7cc3..42311c1bfada 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -3522,3 +3522,4 @@ int cdns3_gadget_init(struct cdns *cdns) return 0; } +EXPORT_SYMBOL_GPL(cdns3_gadget_init); diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 94e9706a1806..bb5405460035 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -21,6 +21,7 @@ #include "core.h" #include "gadget-export.h" +#include "host-export.h" #include "drd.h" static int set_phy_power_on(struct cdns *cdns) @@ -44,6 +45,19 @@ static void set_phy_power_off(struct cdns *cdns) phy_power_off(cdns->usb2_phy); } +static int cdns3_plat_gadget_init(struct cdns *cdns) +{ + if (cdns->version < CDNSP_CONTROLLER_V2) + return cdns3_gadget_init(cdns); + else + return cdnsp_gadget_init(cdns); +} + +static int cdns3_plat_host_init(struct cdns *cdns) +{ + return cdns_host_init(cdns); +} + /** * cdns3_plat_probe - probe for cdns3 core device * @pdev: Pointer to cdns3 core platform device @@ -64,6 +78,14 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->dev = dev; cdns->pdata = dev_get_platdata(dev); + if (cdns->pdata && cdns->pdata->override_apb_timeout) + cdns->override_apb_timeout = cdns->pdata->override_apb_timeout; + + if (device_is_compatible(dev, "cdns,cdnsp")) { + cdns->no_drd = true; + cdns->version = CDNSP_CONTROLLER_V2; + dev_dbg(dev, "No DRD support\n"); + } platform_set_drvdata(pdev, cdns); @@ -97,21 +119,22 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->dev_regs = regs; - cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq < 0) - return dev_err_probe(dev, cdns->otg_irq, - "Failed to get otg IRQ\n"); - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); - if (!res) { - dev_err(dev, "couldn't get otg resource\n"); - return -ENXIO; + if (!cdns->no_drd) { + cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); + if (cdns->otg_irq < 0) + return dev_err_probe(dev, cdns->otg_irq, + "Failed to get otg IRQ\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); + if (!res) { + dev_err(dev, "couldn't get otg resource\n"); + return -ENXIO; + } + cdns->otg_res = *res; } cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); - cdns->otg_res = *res; - cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); if (cdns->wakeup_irq == -EPROBE_DEFER) return cdns->wakeup_irq; @@ -143,12 +166,16 @@ static int cdns3_plat_probe(struct platform_device *pdev) if (ret) goto err_phy_power_on; - cdns->gadget_init = cdns3_gadget_init; - ret = cdns_init(cdns); if (ret) goto err_cdns_init; + cdns->gadget_init = cdns3_plat_gadget_init; + cdns->host_init = cdns3_plat_host_init; + ret = cdns_core_init_role(cdns); + if (ret) + goto err_cdns_init_role; + device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -166,6 +193,9 @@ static int cdns3_plat_probe(struct platform_device *pdev) return 0; +err_cdns_init_role: + if (cdns->role_sw) + usb_role_switch_unregister(cdns->role_sw); err_cdns_init: set_phy_power_off(cdns); err_phy_power_on: @@ -321,6 +351,7 @@ static const struct dev_pm_ops cdns3_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id of_cdns3_match[] = { { .compatible = "cdns,usb3" }, + { .compatible = "cdns,cdnsp" }, { }, }; MODULE_DEVICE_TABLE(of, of_cdns3_match); @@ -339,6 +370,3 @@ static struct platform_driver cdns3_driver = { module_platform_driver(cdns3_driver); MODULE_ALIAS("platform:cdns3"); -MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver"); diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 6b3815f8a6e5..a5275c2fb43b 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -124,20 +124,28 @@ void cdnsp_set_link_state(struct cdnsp_device *pdev, } static void cdnsp_disable_port(struct cdnsp_device *pdev, - __le32 __iomem *port_regs) + struct cdnsp_port *port) { - u32 temp = cdnsp_port_state_to_neutral(readl(port_regs)); + u32 temp; + + if (!port->exist) + return; - writel(temp | PORT_PED, port_regs); + temp = cdnsp_port_state_to_neutral(readl(&port->regs->portsc)); + writel(temp | PORT_PED, &port->regs->portsc); } static void cdnsp_clear_port_change_bit(struct cdnsp_device *pdev, - __le32 __iomem *port_regs) + struct cdnsp_port *port) { - u32 portsc = readl(port_regs); + u32 portsc; + + if (!port->exist) + return; + portsc = readl(&port->regs->portsc); writel(cdnsp_port_state_to_neutral(portsc) | - (portsc & PORT_CHANGE_BITS), port_regs); + (portsc & PORT_CHANGE_BITS), &port->regs->portsc); } static void cdnsp_set_apb_timeout_value(struct cdnsp_device *pdev) @@ -944,7 +952,7 @@ void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *pdev, struct usb_request *req, int enable) { - if (pdev->active_port != &pdev->usb2_port || !pdev->gadget.lpm_capable) + if (pdev->active_port == &pdev->usb3_port || !pdev->gadget.lpm_capable) return; trace_cdnsp_lpm(enable); @@ -1310,20 +1318,26 @@ static int cdnsp_run(struct cdnsp_device *pdev, break; } - if (speed >= USB_SPEED_SUPER) { + if (pdev->usb3_port.exist && speed >= USB_SPEED_SUPER) { writel(temp, &pdev->port3x_regs->mode_addr); cdnsp_set_link_state(pdev, &pdev->usb3_port.regs->portsc, XDEV_RXDETECT); } else { - cdnsp_disable_port(pdev, &pdev->usb3_port.regs->portsc); + cdnsp_disable_port(pdev, &pdev->usb3_port); } - cdnsp_set_link_state(pdev, &pdev->usb2_port.regs->portsc, - XDEV_RXDETECT); + if (pdev->usb2_port.exist) { + cdnsp_set_link_state(pdev, &pdev->usb2_port.regs->portsc, + XDEV_RXDETECT); + writel(PORT_REG6_L1_L0_HW_EN | fs_speed, &pdev->port20_regs->port_reg6); + } + + if (pdev->eusb_port.exist) + cdnsp_set_link_state(pdev, &pdev->eusb_port.regs->portsc, + XDEV_RXDETECT); cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); - writel(PORT_REG6_L1_L0_HW_EN | fs_speed, &pdev->port20_regs->port_reg6); ret = cdnsp_start(pdev); if (ret) { @@ -1469,8 +1483,10 @@ static void cdnsp_stop(struct cdnsp_device *pdev) cdnsp_ep_dequeue(&pdev->eps[0], req); } - cdnsp_disable_port(pdev, &pdev->usb2_port.regs->portsc); - cdnsp_disable_port(pdev, &pdev->usb3_port.regs->portsc); + cdnsp_disable_port(pdev, &pdev->usb2_port); + cdnsp_disable_port(pdev, &pdev->usb3_port); + cdnsp_disable_port(pdev, &pdev->eusb_port); + cdnsp_disable_slot(pdev); cdnsp_halt(pdev); @@ -1479,8 +1495,9 @@ static void cdnsp_stop(struct cdnsp_device *pdev) temp = readl(&pdev->ir_set->irq_pending); writel(IMAN_IE_CLEAR(temp), &pdev->ir_set->irq_pending); - cdnsp_clear_port_change_bit(pdev, &pdev->usb2_port.regs->portsc); - cdnsp_clear_port_change_bit(pdev, &pdev->usb3_port.regs->portsc); + cdnsp_clear_port_change_bit(pdev, &pdev->usb2_port); + cdnsp_clear_port_change_bit(pdev, &pdev->eusb_port); + cdnsp_clear_port_change_bit(pdev, &pdev->usb3_port); /* Clear interrupt line */ temp = readl(&pdev->ir_set->irq_pending); @@ -2075,3 +2092,4 @@ int cdnsp_gadget_init(struct cdns *cdns) return 0; } +EXPORT_SYMBOL_GPL(cdnsp_gadget_init); diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index a91cca509db0..c44bca348a41 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -1474,6 +1474,7 @@ struct cdnsp_device { unsigned int link_state; struct cdnsp_port usb2_port; + struct cdnsp_port eusb_port; struct cdnsp_port usb3_port; struct cdnsp_port *active_port; u16 test_mode; diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index a2a1b21f2ef8..5d8cdc91927d 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -1088,11 +1088,9 @@ void cdnsp_mem_cleanup(struct cdnsp_device *pdev) pdev->dcbaa, pdev->dcbaa->dma); pdev->dcbaa = NULL; - - pdev->usb2_port.exist = 0; - pdev->usb3_port.exist = 0; - pdev->usb2_port.port_num = 0; - pdev->usb3_port.port_num = 0; + memset(&pdev->usb2_port, 0, sizeof(struct cdnsp_port)); + memset(&pdev->eusb_port, 0, sizeof(struct cdnsp_port)); + memset(&pdev->usb3_port, 0, sizeof(struct cdnsp_port)); pdev->active_port = NULL; } @@ -1133,6 +1131,18 @@ static void cdnsp_add_in_port(struct cdnsp_device *pdev, port_offset = CDNSP_EXT_PORT_OFF(temp); port_count = CDNSP_EXT_PORT_COUNT(temp); + if (port == &pdev->eusb_port) { + /* + * If controller has usb2 + eusb port then eusb is as + * second port + */ + if (port_count == 2) + port_offset++; + + if (port_count == 1 && pdev->usb2_port.exist) + return; + } + trace_cdnsp_port_info(addr, port_offset, port_count, port->maj_rev); port->port_num = port_offset; @@ -1152,13 +1162,10 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) base = &pdev->cap_regs->hc_capbase; offset = cdnsp_find_next_ext_cap(base, 0, EXT_CAP_CFG_DEV_20PORT_CAP_ID); - pdev->port20_regs = base + offset; - - offset = cdnsp_find_next_ext_cap(base, 0, D_XEC_CFG_3XPORT_CAP); - pdev->port3x_regs = base + offset; + if (offset) + pdev->port20_regs = base + offset; offset = 0; - base = &pdev->cap_regs->hc_capbase; /* Driver expects max 2 extended protocol capability. */ for (i = 0; i < 2; i++) { @@ -1173,26 +1180,46 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) cdnsp_add_in_port(pdev, &pdev->usb3_port, base + offset); - if (CDNSP_EXT_PORT_MAJOR(temp) == 0x02 && - !pdev->usb2_port.port_num) - cdnsp_add_in_port(pdev, &pdev->usb2_port, - base + offset); + if (CDNSP_EXT_PORT_MAJOR(temp) == 0x02) { + if (!pdev->usb2_port.port_num && pdev->port20_regs) + cdnsp_add_in_port(pdev, &pdev->usb2_port, + base + offset); + + if (!pdev->eusb_port.port_num) + cdnsp_add_in_port(pdev, &pdev->eusb_port, + base + offset); + } } - if (!pdev->usb2_port.exist || !pdev->usb3_port.exist) { - dev_err(pdev->dev, "Error: Only one port detected\n"); + if (!pdev->usb2_port.exist && !pdev->eusb_port.exist && + !pdev->usb3_port.exist) { + dev_err(pdev->dev, "Error: No port detected\n"); return -ENODEV; } - trace_cdnsp_init("Found USB 2.0 ports and USB 3.0 ports."); + if (pdev->usb2_port.exist) { + pdev->usb2_port.regs = (struct cdnsp_port_regs __iomem *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->usb2_port.port_num - 1)); + trace_cdnsp_init("Found USB 2.0 port."); + } - pdev->usb2_port.regs = (struct cdnsp_port_regs __iomem *) - (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * - (pdev->usb2_port.port_num - 1)); + if (pdev->eusb_port.exist) { + pdev->eusb_port.regs = (struct cdnsp_port_regs __iomem *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->eusb_port.port_num - 1)); + trace_cdnsp_init("Found eUSB 2.0 port."); + } + + if (pdev->usb3_port.exist) { + offset = cdnsp_find_next_ext_cap(base, 0, D_XEC_CFG_3XPORT_CAP); + pdev->port3x_regs = base + offset; - pdev->usb3_port.regs = (struct cdnsp_port_regs __iomem *) - (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * - (pdev->usb3_port.port_num - 1)); + pdev->usb3_port.regs = (struct cdnsp_port_regs __iomem *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->usb3_port.port_num - 1)); + trace_cdnsp_init("Found USB 3.x port."); + } return 0; } diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index 566d94e49102..c38a3dc7111a 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Cadence PCI Glue driver. + * Cadence USBSSP PCI Glue driver. * * Copyright (C) 2019 Cadence. * @@ -16,26 +16,45 @@ #include <linux/pci.h> #include "core.h" -#include "gadget-export.h" +struct cdnsp_wrap { + struct platform_device *plat_dev; + struct property_entry prop[3]; + struct resource dev_res[6]; + int devfn; +}; + +#define RES_IRQ_HOST_ID 0 +#define RES_IRQ_PERIPHERAL_ID 1 +#define RES_IRQ_OTG_ID 2 +#define RES_HOST_ID 3 +#define RES_DEV_ID 4 +#define RES_DRD_ID 5 +/* DRD PCI configuration - 64-bit addressing */ +/* First PCI function */ #define PCI_BAR_HOST 0 -#define PCI_BAR_OTG 0 #define PCI_BAR_DEV 2 +/* Second PCI function */ +#define PCI_BAR_OTG 0 +/* Device only PCI configuration - 32-bit addressing */ +/* First PCI function */ +#define PCI_BAR_ONLY_DEV 1 #define PCI_DEV_FN_HOST_DEVICE 0 #define PCI_DEV_FN_OTG 1 #define PCI_DRIVER_NAME "cdns-pci-usbssp" -#define PLAT_DRIVER_NAME "cdns-usbssp" +#define PLAT_DRIVER_NAME "cdns-usb3" -#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 +#define PCI_DEVICE_ID_CDNS_UDC_USBSSP 0x0400 +#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) { /* * Gets the second function. - * Platform has two function. The fist keeps resources for - * Host/Device while the secon keeps resources for DRD/OTG. + * Platform has two function. The first keeps resources for + * Host/Device while the second keeps resources for DRD/OTG. */ if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP) return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL); @@ -48,11 +67,13 @@ static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) static int cdnsp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct device *dev = &pdev->dev; - struct pci_dev *func; + struct platform_device_info plat_info; + static struct cdns3_platform_data pdata; + struct cdnsp_wrap *wrap; struct resource *res; - struct cdns *cdnsp; - int ret; + struct pci_dev *func; + bool no_drd = false; + int ret = 0; /* * For GADGET/HOST PCI (devfn) function number is 0, @@ -62,11 +83,14 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, pdev->devfn != PCI_DEV_FN_OTG)) return -EINVAL; + if (pdev->device == PCI_DEVICE_ID_CDNS_UDC_USBSSP) + no_drd = true; + func = cdnsp_get_second_fun(pdev); - if (!func) + if (!func && !no_drd) return -EINVAL; - if (func->class == PCI_CLASS_SERIAL_USB_XHCI || + if ((func && func->class == PCI_CLASS_SERIAL_USB_XHCI) || pdev->class == PCI_CLASS_SERIAL_USB_XHCI) { ret = -EINVAL; goto put_pci; @@ -79,147 +103,125 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, } pci_set_master(pdev); - if (pci_is_enabled(func)) { - cdnsp = pci_get_drvdata(func); + + if (func && pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); } else { - cdnsp = kzalloc_obj(*cdnsp); - if (!cdnsp) { + wrap = kzalloc_obj(*wrap); + if (!wrap) { ret = -ENOMEM; goto put_pci; } } - /* For GADGET device function number is 0. */ - if (pdev->devfn == 0) { - resource_size_t rsrc_start, rsrc_len; - - /* Function 0: host(BAR_0) + device(BAR_1).*/ - dev_dbg(dev, "Initialize resources\n"); - rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); - rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); - res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); - if (!res) { - dev_dbg(dev, "controller already in use\n"); - ret = -EBUSY; - goto free_cdnsp; - } - - cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len); - if (!cdnsp->dev_regs) { - dev_dbg(dev, "error mapping memory\n"); - ret = -EFAULT; - goto free_cdnsp; - } - - cdnsp->dev_irq = pdev->irq; - dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", - &rsrc_start); - - res = &cdnsp->xhci_res[0]; - res->start = pci_resource_start(pdev, PCI_BAR_HOST); - res->end = pci_resource_end(pdev, PCI_BAR_HOST); - res->name = "xhci"; - res->flags = IORESOURCE_MEM; - dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n", - &res->start); - - /* Interrupt for XHCI, */ - res = &cdnsp->xhci_res[1]; - res->start = pdev->irq; - res->name = "host"; - res->flags = IORESOURCE_IRQ; + res = wrap->dev_res; + + if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { + int bar_dev = no_drd ? PCI_BAR_ONLY_DEV : PCI_BAR_DEV; + + /* Function 0: host(BAR_0) + device(BAR_2). */ + dev_dbg(&pdev->dev, "Initialize Device resources\n"); + res[RES_DEV_ID].start = pci_resource_start(pdev, bar_dev); + res[RES_DEV_ID].end = pci_resource_end(pdev, bar_dev); + res[RES_DEV_ID].name = "dev"; + res[RES_DEV_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n", + &res[RES_DEV_ID].start); + + res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].name = "xhci"; + res[RES_HOST_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSSP-XHCI physical base addr: %pa\n", + &res[RES_HOST_ID].start); + + /* Interrupt for XHCI */ + wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_HOST_ID].name = "host"; + wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ; + + /* Interrupt for device. It's the same as for HOST. */ + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral"; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ; } else { - res = &cdnsp->otg_res; - res->start = pci_resource_start(pdev, PCI_BAR_OTG); - res->end = pci_resource_end(pdev, PCI_BAR_OTG); - res->name = "otg"; - res->flags = IORESOURCE_MEM; - dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n", - &res->start); + res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].name = "otg"; + res[RES_DRD_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "CDNSP-DRD physical base addr: %pa\n", + &res[RES_DRD_ID].start); /* Interrupt for OTG/DRD. */ - cdnsp->otg_irq = pdev->irq; + wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_OTG_ID].name = "otg"; + wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; } - /* - * Cadence PCI based platform require some longer timeout for APB - * to fixes domain clock synchronization issue after resuming - * controller from L1 state. - */ - cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; - pci_set_drvdata(pdev, cdnsp); - - if (pci_is_enabled(func)) { - cdnsp->dev = dev; - cdnsp->gadget_init = cdnsp_gadget_init; + if (no_drd || pci_is_enabled(func)) { + u8 idx = 0; - ret = cdns_init(cdnsp); - if (ret) - goto free_cdnsp; - } - - device_wakeup_enable(&pdev->dev); - if (pci_dev_run_wake(pdev)) - pm_runtime_put_noidle(&pdev->dev); + /* set up platform device info */ + pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; - return 0; + if (no_drd) { + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("compatible", + "cdns,cdnsp"); + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "peripheral"); + } else { + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "otg"); + wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("usb-role-switch"); + } -free_cdnsp: - if (!pci_is_enabled(func)) - kfree(cdnsp); + memset(&plat_info, 0, sizeof(plat_info)); + plat_info.parent = &pdev->dev; + plat_info.fwnode = pdev->dev.fwnode; + plat_info.name = PLAT_DRIVER_NAME; + plat_info.id = pdev->devfn; + plat_info.res = wrap->dev_res; + plat_info.num_res = ARRAY_SIZE(wrap->dev_res); + plat_info.dma_mask = pdev->dma_mask; + plat_info.data = &pdata; + plat_info.size_data = sizeof(pdata); + plat_info.properties = wrap->prop; + wrap->devfn = pdev->devfn; + /* register platform device */ + wrap->plat_dev = platform_device_register_full(&plat_info); + if (IS_ERR(wrap->plat_dev)) { + ret = PTR_ERR(wrap->plat_dev); + kfree(wrap); + goto put_pci; + } + } + pci_set_drvdata(pdev, wrap); put_pci: pci_dev_put(func); - return ret; } static void cdnsp_pci_remove(struct pci_dev *pdev) { - struct cdns *cdnsp; + struct cdnsp_wrap *wrap; struct pci_dev *func; func = cdnsp_get_second_fun(pdev); - cdnsp = (struct cdns *)pci_get_drvdata(pdev); + wrap = pci_get_drvdata(pdev); - if (pci_dev_run_wake(pdev)) - pm_runtime_get_noresume(&pdev->dev); + if (wrap->devfn == pdev->devfn) + platform_device_unregister(wrap->plat_dev); - if (pci_is_enabled(func)) { - cdns_remove(cdnsp); - } else { - kfree(cdnsp); - } + if (!func || !pci_is_enabled(func)) + kfree(wrap); pci_dev_put(func); } -static int __maybe_unused cdnsp_pci_suspend(struct device *dev) -{ - struct cdns *cdns = dev_get_drvdata(dev); - - return cdns_suspend(cdns); -} - -static int __maybe_unused cdnsp_pci_resume(struct device *dev) -{ - struct cdns *cdns = dev_get_drvdata(dev); - unsigned long flags; - int ret; - - spin_lock_irqsave(&cdns->lock, flags); - ret = cdns_resume(cdns); - spin_unlock_irqrestore(&cdns->lock, flags); - cdns_set_active(cdns, 1); - - return ret; -} - -static const struct dev_pm_ops cdnsp_pci_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume) -}; - static const struct pci_device_id cdnsp_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), + .class = PCI_CLASS_SERIAL_USB_DEVICE }, + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), + .class = PCI_CLASS_SERIAL_USB_CDNS }, { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), .class = PCI_CLASS_SERIAL_USB_DEVICE }, { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), @@ -230,13 +232,10 @@ static const struct pci_device_id cdnsp_pci_ids[] = { }; static struct pci_driver cdnsp_pci_driver = { - .name = "cdnsp-pci", + .name = PCI_DRIVER_NAME, .id_table = cdnsp_pci_ids, .probe = cdnsp_pci_probe, .remove = cdnsp_pci_remove, - .driver = { - .pm = &cdnsp_pci_pm_ops, - } }; module_pci_driver(cdnsp_pci_driver); @@ -245,4 +244,4 @@ MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids); MODULE_ALIAS("pci:cdnsp"); MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence CDNSP PCI driver"); +MODULE_DESCRIPTION("Cadence CDNSP PCI wrapper"); diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c index 0758f171f73e..715658c981ff 100644 --- a/drivers/usb/cdns3/cdnsp-ring.c +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -259,7 +259,7 @@ static bool cdnsp_room_on_ring(struct cdnsp_device *pdev, */ static void cdnsp_force_l0_go(struct cdnsp_device *pdev) { - if (pdev->active_port == &pdev->usb2_port && pdev->gadget.lpm_capable) + if (pdev->active_port != &pdev->usb3_port && pdev->gadget.lpm_capable) cdnsp_set_link_state(pdev, &pdev->active_port->regs->portsc, XDEV_U0); } @@ -763,6 +763,8 @@ static int cdnsp_update_port_id(struct cdnsp_device *pdev, u32 port_id) if (port_id == pdev->usb2_port.port_num) { port = &pdev->usb2_port; + } else if (port_id == pdev->eusb_port.port_num) { + port = &pdev->eusb_port; } else if (port_id == pdev->usb3_port.port_num) { port = &pdev->usb3_port; } else { @@ -779,7 +781,8 @@ static int cdnsp_update_port_id(struct cdnsp_device *pdev, u32 port_id) cdnsp_enable_slot(pdev); } - if (port_id == pdev->usb2_port.port_num) + if ((pdev->usb2_port.exist && port_id == pdev->usb2_port.port_num) || + (pdev->eusb_port.exist && port_id == pdev->eusb_port.port_num)) cdnsp_set_usb2_hardware_lpm(pdev, NULL, 1); else writel(PORT_U1_TIMEOUT(1) | PORT_U2_TIMEOUT(1), @@ -808,7 +811,7 @@ static void cdnsp_handle_port_status(struct cdnsp_device *pdev, port_regs = pdev->active_port->regs; - if (port_id == pdev->usb2_port.port_num) + if (port_id == pdev->usb2_port.port_num || port_id == pdev->eusb_port.port_num) port2 = true; new_event: diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index f0e32227c0b7..504bdf13ea80 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -21,7 +21,6 @@ #include <linux/pm_runtime.h> #include "core.h" -#include "host-export.h" #include "drd.h" static int cdns_idle_init(struct cdns *cdns); @@ -71,7 +70,8 @@ static void cdns_role_stop(struct cdns *cdns) static void cdns_exit_roles(struct cdns *cdns) { cdns_role_stop(cdns); - cdns_drd_exit(cdns); + if (!cdns->no_drd) + cdns_drd_exit(cdns); } /** @@ -80,7 +80,7 @@ static void cdns_exit_roles(struct cdns *cdns) * * Returns 0 on success otherwise negative errno */ -static int cdns_core_init_role(struct cdns *cdns) +int cdns_core_init_role(struct cdns *cdns) { struct device *dev = cdns->dev; enum usb_dr_mode best_dr_mode; @@ -96,23 +96,13 @@ static int cdns_core_init_role(struct cdns *cdns) * can be restricted later depending on strap pin configuration. */ if (dr_mode == USB_DR_MODE_UNKNOWN) { - if (cdns->version == CDNSP_CONTROLLER_V2) { - if (IS_ENABLED(CONFIG_USB_CDNSP_HOST) && - IS_ENABLED(CONFIG_USB_CDNSP_GADGET)) - dr_mode = USB_DR_MODE_OTG; - else if (IS_ENABLED(CONFIG_USB_CDNSP_HOST)) - dr_mode = USB_DR_MODE_HOST; - else if (IS_ENABLED(CONFIG_USB_CDNSP_GADGET)) - dr_mode = USB_DR_MODE_PERIPHERAL; - } else { - if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && - IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) - dr_mode = USB_DR_MODE_OTG; - else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) - dr_mode = USB_DR_MODE_HOST; - else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) - dr_mode = USB_DR_MODE_PERIPHERAL; - } + if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && + IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_OTG; + else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) + dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_PERIPHERAL; } /* @@ -137,11 +127,8 @@ static int cdns_core_init_role(struct cdns *cdns) dr_mode = best_dr_mode; if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { - if ((cdns->version == CDNSP_CONTROLLER_V2 && - IS_ENABLED(CONFIG_USB_CDNSP_HOST)) || - (cdns->version < CDNSP_CONTROLLER_V2 && - IS_ENABLED(CONFIG_USB_CDNS3_HOST))) - ret = cdns_host_init(cdns); + if (cdns->host_init) + ret = cdns->host_init(cdns); else ret = -ENXIO; @@ -197,11 +184,14 @@ static int cdns_core_init_role(struct cdns *cdns) goto err; } + dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); + return 0; err: cdns_exit_roles(cdns); return ret; } +EXPORT_SYMBOL_GPL(cdns_core_init_role); /** * cdns_hw_role_state_machine - role switch state machine based on hw events. @@ -469,14 +459,8 @@ int cdns_init(struct cdns *cdns) if (ret) goto init_failed; - ret = cdns_core_init_role(cdns); - if (ret) - goto init_failed; - spin_lock_init(&cdns->lock); - dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); - return 0; init_failed: cdns_drd_exit(cdns); @@ -576,5 +560,5 @@ EXPORT_SYMBOL_GPL(cdns_set_active); MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>"); MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); -MODULE_DESCRIPTION("Cadence USBSS and USBSSP DRD Driver"); +MODULE_DESCRIPTION("Cadence USBSS/USBSSP DRD driver (core, DRD, platform, optional host/gadget)"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 801be9e61340..8c492fda924c 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -45,6 +45,7 @@ struct cdns3_platform_data { unsigned long quirks; #define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0) #define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1) + u32 override_apb_timeout; /* 0 = use default (e.g. for PCI) */ }; /** @@ -82,6 +83,10 @@ struct cdns3_platform_data { * @override_apb_timeout: hold value of APB timeout. For value 0 the default * value in CHICKEN_BITS_3 will be preserved. * @gadget_init: pointer to gadget initialization function + * @host_init: pointer to host initialization function + * @no_drd: DRD register block is inaccessible. The controller is hardwired to + * single role (host or device) or the logic for role switching is + * missing. */ struct cdns { struct device *dev; @@ -120,13 +125,15 @@ struct cdns { spinlock_t lock; struct xhci_plat_priv *xhci_plat_data; u32 override_apb_timeout; - int (*gadget_init)(struct cdns *cdns); + int (*host_init)(struct cdns *cdns); + bool no_drd; }; int cdns_hw_role_switch(struct cdns *cdns); int cdns_init(struct cdns *cdns); int cdns_remove(struct cdns *cdns); +int cdns_core_init_role(struct cdns *cdns); #ifdef CONFIG_PM_SLEEP int cdns_resume(struct cdns *cdns); diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 84fb38a5723a..d2bb682e4552 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -87,6 +87,9 @@ int cdns_get_id(struct cdns *cdns) { int id; + if (cdns->no_drd) + return 0; + id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE; dev_dbg(cdns->dev, "OTG ID: %d", id); @@ -107,7 +110,7 @@ void cdns_clear_vbus(struct cdns *cdns) { u32 reg; - if (cdns->version != CDNSP_CONTROLLER_V2) + if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) return; reg = readl(&cdns->otg_cdnsp_regs->override); @@ -120,7 +123,7 @@ void cdns_set_vbus(struct cdns *cdns) { u32 reg; - if (cdns->version != CDNSP_CONTROLLER_V2) + if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) return; reg = readl(&cdns->otg_cdnsp_regs->override); @@ -179,7 +182,10 @@ static void cdns_otg_enable_irq(struct cdns *cdns) int cdns_drd_host_on(struct cdns *cdns) { u32 val, ready_bit; - int ret; + int ret = 0; + + if (cdns->no_drd) + goto phy_set; /* Enable host mode. */ writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS, @@ -197,6 +203,7 @@ int cdns_drd_host_on(struct cdns *cdns) if (ret) dev_err(cdns->dev, "timeout waiting for xhci_ready\n"); +phy_set: phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_HOST); phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_HOST); return ret; @@ -210,6 +217,9 @@ void cdns_drd_host_off(struct cdns *cdns) { u32 val; + if (cdns->no_drd) + goto phy_set; + writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP | OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF, &cdns->otg_regs->cmd); @@ -218,6 +228,8 @@ void cdns_drd_host_off(struct cdns *cdns) readl_poll_timeout_atomic(&cdns->otg_regs->state, val, !(val & OTGSTATE_HOST_STATE_MASK), 1, 2000000); + +phy_set: phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID); phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); } @@ -234,6 +246,9 @@ int cdns_drd_gadget_on(struct cdns *cdns) u32 ready_bit; int ret, val; + if (cdns->no_drd) + goto phy_set; + /* switch OTG core */ writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd); @@ -251,6 +266,7 @@ int cdns_drd_gadget_on(struct cdns *cdns) return ret; } +phy_set: phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_DEVICE); phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE); return 0; @@ -265,6 +281,9 @@ void cdns_drd_gadget_off(struct cdns *cdns) { u32 val; + if (cdns->no_drd) + goto phy_set; + /* * Driver should wait at least 10us after disabling Device * before turning-off Device (DEV_BUS_DROP). @@ -277,6 +296,8 @@ void cdns_drd_gadget_off(struct cdns *cdns) readl_poll_timeout_atomic(&cdns->otg_regs->state, val, !(val & OTGSTATE_DEV_STATE_MASK), 1, 2000000); + +phy_set: phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID); phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); } @@ -392,6 +413,18 @@ int cdns_drd_init(struct cdns *cdns) u32 state, reg; int ret; + if (cdns->no_drd) { + cdns->dr_mode = usb_get_dr_mode(cdns->dev); + + if (cdns->dr_mode != USB_DR_MODE_HOST && + cdns->dr_mode != USB_DR_MODE_PERIPHERAL) { + dev_err(cdns->dev, "Incorrect dr_mode\n"); + return -EINVAL; + } + + return 0; + } + regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res); if (IS_ERR(regs)) return PTR_ERR(regs); @@ -492,6 +525,9 @@ int cdns_drd_init(struct cdns *cdns) int cdns_drd_exit(struct cdns *cdns) { + if (cdns->no_drd) + return 0; + cdns_otg_disable_irq(cdns); return 0; @@ -500,6 +536,9 @@ int cdns_drd_exit(struct cdns *cdns) /* Indicate the cdns3 core was power lost before */ bool cdns_power_is_lost(struct cdns *cdns) { + if (cdns->no_drd) + return false; + if (cdns->version == CDNS3_CONTROLLER_V0) { if (!(readl(&cdns->otg_v0_regs->simulate) & BIT(0))) return true; diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index c37b6269b001..60c3177db62c 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -10,9 +10,10 @@ #ifndef __LINUX_CDNS3_GADGET_EXPORT #define __LINUX_CDNS3_GADGET_EXPORT -#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET) +#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET) int cdnsp_gadget_init(struct cdns *cdns); +int cdns3_gadget_init(struct cdns *cdns); #else static inline int cdnsp_gadget_init(struct cdns *cdns) @@ -20,13 +21,6 @@ static inline int cdnsp_gadget_init(struct cdns *cdns) return -ENXIO; } -#endif /* CONFIG_USB_CDNSP_GADGET */ - -#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET) - -int cdns3_gadget_init(struct cdns *cdns); -#else - static inline int cdns3_gadget_init(struct cdns *cdns) { return -ENXIO; diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index cf92173ecf00..34fd1f1ad59d 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -9,7 +9,7 @@ #ifndef __LINUX_CDNS3_HOST_EXPORT #define __LINUX_CDNS3_HOST_EXPORT -#if IS_ENABLED(CONFIG_USB_CDNS_HOST) +#if IS_ENABLED(CONFIG_USB_CDNS3_HOST) int cdns_host_init(struct cdns *cdns); @@ -22,6 +22,6 @@ static inline int cdns_host_init(struct cdns *cdns) static inline void cdns_host_exit(struct cdns *cdns) { } -#endif /* USB_CDNS_HOST */ +#endif /* CONFIG_USB_CDNS3_HOST */ #endif /* __LINUX_CDNS3_HOST_EXPORT */ diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 2ab3db3c1015..07563be0013f 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -1187,19 +1187,16 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci->role = ci_get_role(ci); if (!ci_otg_is_fsm_mode(ci)) { - /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) { - /* Pull down DP for possible charger detection */ - hw_write(ci, OP_USBCMD, USBCMD_RS, 0); - ci_handle_vbus_change(ci); - } - ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", ci_role(ci)->name); goto stop; } + + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); } ret = devm_request_irq(dev, ci->irq, ci_irq_handler, IRQF_SHARED, diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index f2de86d0ce40..d52f89489893 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1835,6 +1835,20 @@ static const struct usb_ep_ops usb_ep_ops = { * GADGET block *****************************************************************************/ +static void ci_udc_enable_vbus_irq(struct ci_hdrc *ci, bool enable) +{ + u32 reg = OTGSC_BSVIS; + + if (!ci->is_otg) + return; + + if (enable) + reg |= OTGSC_BSVIE; + + /* Clear pending BSVIS and enable/disable BSVIE */ + hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, reg); +} + static int ci_udc_get_frame(struct usb_gadget *_gadget) { struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); @@ -2030,6 +2044,8 @@ static int init_eps(struct ci_hdrc *ci) { int retval = 0, i, j; + memset(ci->ci_hw_ep, 0, sizeof(ci->ci_hw_ep)); + for (i = 0; i < ci->hw_ep_max/2; i++) for (j = RX; j <= TX; j++) { int k = i + j * ci->hw_ep_max/2; @@ -2275,6 +2291,8 @@ static int udc_start(struct ci_hdrc *ci) struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps; int retval = 0; + memset(&ci->gadget, 0, sizeof(ci->gadget)); + ci->gadget.ops = &usb_gadget_ops; ci->gadget.speed = USB_SPEED_UNKNOWN; ci->gadget.max_speed = USB_SPEED_HIGH; @@ -2313,10 +2331,15 @@ static int udc_start(struct ci_hdrc *ci) ci->gadget.ep0 = &ci->ep0in->ep; + if (ci->platdata->pins_device) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_device); + retval = usb_add_gadget_udc(dev, &ci->gadget); if (retval) goto destroy_eps; + ci_udc_enable_vbus_irq(ci, true); return retval; destroy_eps: @@ -2328,48 +2351,20 @@ free_qh_pool: return retval; } -/* - * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC - * - * No interrupts active, the IRQ has been released +/** + * udc_stop: deinitialize gadget role + * @ci: chipidea controller */ -void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) +static void udc_stop(struct ci_hdrc *ci) { - if (!ci->roles[CI_ROLE_GADGET]) - return; - + ci_udc_enable_vbus_irq(ci, false); usb_del_gadget_udc(&ci->gadget); + ci->vbus_active = 0; destroy_eps(ci); dma_pool_destroy(ci->td_pool); dma_pool_destroy(ci->qh_pool); -} - -static int udc_id_switch_for_device(struct ci_hdrc *ci) -{ - if (ci->platdata->pins_device) - pinctrl_select_state(ci->platdata->pctl, - ci->platdata->pins_device); - - if (ci->is_otg) - /* Clear and enable BSV irq */ - hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, - OTGSC_BSVIS | OTGSC_BSVIE); - - return 0; -} - -static void udc_id_switch_for_host(struct ci_hdrc *ci) -{ - /* - * host doesn't care B_SESSION_VALID event - * so clear and disable BSV irq - */ - if (ci->is_otg) - hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); - - ci->vbus_active = 0; if (ci->platdata->pins_device && ci->platdata->pins_default) pinctrl_select_state(ci->platdata->pctl, @@ -2395,9 +2390,7 @@ static void udc_suspend(struct ci_hdrc *ci) static void udc_resume(struct ci_hdrc *ci, bool power_lost) { if (power_lost) { - if (ci->is_otg) - hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, - OTGSC_BSVIS | OTGSC_BSVIE); + ci_udc_enable_vbus_irq(ci, true); if (ci->vbus_active) usb_gadget_vbus_disconnect(&ci->gadget); } else if (ci->vbus_active && ci->driver && @@ -2420,7 +2413,6 @@ static void udc_resume(struct ci_hdrc *ci, bool power_lost) int ci_hdrc_gadget_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; - int ret; if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC)) return -ENXIO; @@ -2429,8 +2421,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci) if (!rdrv) return -ENOMEM; - rdrv->start = udc_id_switch_for_device; - rdrv->stop = udc_id_switch_for_host; + rdrv->start = udc_start; + rdrv->stop = udc_stop; #ifdef CONFIG_PM_SLEEP rdrv->suspend = udc_suspend; rdrv->resume = udc_resume; @@ -2438,9 +2430,22 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci) rdrv->irq = udc_irq; rdrv->name = "gadget"; - ret = udc_start(ci); - if (!ret) - ci->roles[CI_ROLE_GADGET] = rdrv; + ci->roles[CI_ROLE_GADGET] = rdrv; - return ret; + /* Pull down DP for possible charger detection */ + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + return 0; +} + +/* + * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC + * + * No interrupts active, the IRQ has been released + */ +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) +{ + struct device *dev = &ci->gadget.dev; + + if (ci->roles[CI_ROLE_GADGET] && device_is_registered(dev)) + udc_stop(ci); } diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index ddf0b5963859..49ab02f25872 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -797,6 +797,9 @@ static void acm_port_shutdown(struct tty_port *port) "ctrl polling restart failed after port close\n"); /* port_shutdown() cleared DTR/RTS; restore them */ acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS); + if (acm_submit_read_urbs(acm, GFP_KERNEL)) + dev_dbg(&acm->control->dev, + "read urb restart failed after port close\n"); } } @@ -1564,6 +1567,9 @@ skip_countries: if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) dev_warn(&intf->dev, "failed to start persistent ctrl polling\n"); + if (acm_submit_read_urbs(acm, GFP_KERNEL)) + dev_warn(&intf->dev, + "failed to start persistent bulk read polling\n"); } return 0; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index b181b43a35dc..8e8a5f59b319 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -446,7 +446,8 @@ rh_string(int id, struct usb_hcd const *hcd, u8 *data, unsigned len) /* Root hub control transfers execute synchronously */ -static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) +static int rh_call_control(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) { struct usb_ctrlrequest *cmd; u16 typeReq, wValue, wIndex, wLength; @@ -481,8 +482,8 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) * tbuf should be at least as big as the * USB hub descriptor. */ - tbuf_size = max_t(u16, sizeof(struct usb_hub_descriptor), wLength); - tbuf = kzalloc(tbuf_size, GFP_KERNEL); + tbuf_size = max_t(u16, sizeof(struct usb_hub_descriptor), wLength); + tbuf = kzalloc(tbuf_size, mem_flags); if (!tbuf) { status = -ENOMEM; goto err_alloc; @@ -807,12 +808,13 @@ static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb) return retval; } -static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) +static int rh_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, gfp_t mem_flags) { if (usb_endpoint_xfer_int(&urb->ep->desc)) return rh_queue_status (hcd, urb); if (usb_endpoint_xfer_control(&urb->ep->desc)) - return rh_call_control (hcd, urb); + return rh_call_control(hcd, urb, mem_flags); return -EINVAL; } @@ -1533,7 +1535,7 @@ int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags) */ if (is_root_hub(urb->dev)) { - status = rh_urb_enqueue(hcd, urb); + status = rh_urb_enqueue(hcd, urb, mem_flags); } else { status = map_urb_for_dma(hcd, urb, mem_flags); if (likely(status == 0)) { diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index 5c7538d498dd..f380275ac696 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -515,18 +515,20 @@ void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, u32 pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT; if (chan->ep_type != USB_ENDPOINT_XFER_CONTROL) { - if (WARN(!chan || !chan->qh, - "chan->qh must be specified for non-control eps\n")) + if (!chan->qh) { + dev_err(hsotg->dev, "chan->qh must be specified for non-control eps\n"); return; + } if (pid == TSIZ_SC_MC_PID_DATA0) chan->qh->data_toggle = DWC2_HC_PID_DATA0; else chan->qh->data_toggle = DWC2_HC_PID_DATA1; } else { - if (WARN(!qtd, - "qtd must be specified for control eps\n")) + if (!qtd) { + dev_err(hsotg->dev, "qtd must be specified for control eps\n"); return; + } if (pid == TSIZ_SC_MC_PID_DATA0) qtd->data_toggle = DWC2_HC_PID_DATA0; diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 65213896de99..517aa7f1486d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -425,8 +425,7 @@ static void dwc3_ref_clk_period(struct dwc3 *dwc) } reg = dwc3_readl(dwc, DWC3_GUCTL); - reg &= ~DWC3_GUCTL_REFCLKPER_MASK; - reg |= FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, period); + FIELD_MODIFY(DWC3_GUCTL_REFCLKPER_MASK, ®, period); dwc3_writel(dwc, DWC3_GUCTL, reg); if (DWC3_VER_IS_PRIOR(DWC3, 250A)) @@ -456,12 +455,9 @@ static void dwc3_ref_clk_period(struct dwc3 *dwc) decr = 480000000 / rate; reg = dwc3_readl(dwc, DWC3_GFLADJ); - reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK - & ~DWC3_GFLADJ_240MHZDECR - & ~DWC3_GFLADJ_240MHZDECR_PLS1; - reg |= FIELD_PREP(DWC3_GFLADJ_REFCLK_FLADJ_MASK, fladj) - | FIELD_PREP(DWC3_GFLADJ_240MHZDECR, decr >> 1) - | FIELD_PREP(DWC3_GFLADJ_240MHZDECR_PLS1, decr & 1); + FIELD_MODIFY(DWC3_GFLADJ_REFCLK_FLADJ_MASK, ®, fladj); + FIELD_MODIFY(DWC3_GFLADJ_240MHZDECR, ®, decr >> 1); + FIELD_MODIFY(DWC3_GFLADJ_240MHZDECR_PLS1, ®, decr & 1); if (dwc->gfladj_refclk_lpm_sel) reg |= DWC3_GFLADJ_REFCLK_LPM_SEL; @@ -525,7 +521,7 @@ static void dwc3_free_event_buffers(struct dwc3 *dwc) } /** - * dwc3_alloc_event_buffers - Allocates @num event buffers of size @length + * dwc3_alloc_event_buffers - Allocate one event buffer of size @length * @dwc: pointer to our controller context structure * @length: size of event buffer * diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c index 4ca567ec01d0..60ee4cc99b28 100644 --- a/drivers/usb/dwc3/dwc3-google.c +++ b/drivers/usb/dwc3/dwc3-google.c @@ -104,9 +104,8 @@ static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state) regmap_read(google->usb_cfg_regmap, google->host_cfg_offset + HOST_CFG1_OFFSET, ®); - reg &= ~HOST_CFG1_PM_POWER_STATE_REQUEST; - reg |= (FIELD_PREP(HOST_CFG1_PM_POWER_STATE_REQUEST, state) | - HOST_CFG1_PME_EN); + FIELD_MODIFY(HOST_CFG1_PM_POWER_STATE_REQUEST, ®, state); + reg |= HOST_CFG1_PME_EN; regmap_write(google->usb_cfg_regmap, google->host_cfg_offset + HOST_CFG1_OFFSET, reg); diff --git a/drivers/usb/dwc3/dwc3-octeon.c b/drivers/usb/dwc3/dwc3-octeon.c index 42bfc14ae0c4..2201f0f34abb 100644 --- a/drivers/usb/dwc3/dwc3-octeon.c +++ b/drivers/usb/dwc3/dwc3-octeon.c @@ -296,8 +296,7 @@ static int dwc3_octeon_setup(struct dwc3_octeon *octeon, return div; } val = dwc3_octeon_readq(uctl_ctl_reg); - val &= ~USBDRD_UCTL_CTL_H_CLKDIV_SEL; - val |= FIELD_PREP(USBDRD_UCTL_CTL_H_CLKDIV_SEL, div); + FIELD_MODIFY(USBDRD_UCTL_CTL_H_CLKDIV_SEL, &val, div); val |= USBDRD_UCTL_CTL_H_CLK_EN; dwc3_octeon_writeq(uctl_ctl_reg, val); val = dwc3_octeon_readq(uctl_ctl_reg); @@ -314,14 +313,11 @@ static int dwc3_octeon_setup(struct dwc3_octeon *octeon, /* Step 5a: Reference clock configuration. */ val = dwc3_octeon_readq(uctl_ctl_reg); val &= ~USBDRD_UCTL_CTL_REF_CLK_DIV2; - val &= ~USBDRD_UCTL_CTL_REF_CLK_SEL; - val |= FIELD_PREP(USBDRD_UCTL_CTL_REF_CLK_SEL, ref_clk_sel); + FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_SEL, &val, ref_clk_sel); - val &= ~USBDRD_UCTL_CTL_REF_CLK_FSEL; - val |= FIELD_PREP(USBDRD_UCTL_CTL_REF_CLK_FSEL, ref_clk_fsel); + FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_FSEL, &val, ref_clk_fsel); - val &= ~USBDRD_UCTL_CTL_MPLL_MULTIPLIER; - val |= FIELD_PREP(USBDRD_UCTL_CTL_MPLL_MULTIPLIER, mpll_mul); + FIELD_MODIFY(USBDRD_UCTL_CTL_MPLL_MULTIPLIER, &val, mpll_mul); /* Step 5b: Configure and enable spread-spectrum for SuperSpeed. */ val |= USBDRD_UCTL_CTL_SSC_EN; diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c index 7fc6696b7694..75f9c831b21a 100644 --- a/drivers/usb/gadget/udc/aspeed_udc.c +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -694,7 +694,7 @@ static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) struct ast_udc_dev *udc = ep->udc; struct ast_udc_request *req; unsigned long flags; - int rc = 0; + int rc = -EINVAL; spin_lock_irqsave(&udc->lock, flags); @@ -704,14 +704,11 @@ static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) list_del_init(&req->queue); ast_udc_done(ep, req, -ESHUTDOWN); _req->status = -ECONNRESET; + rc = 0; break; } } - /* dequeue request not found */ - if (&req->req != _req) - rc = -EINVAL; - spin_unlock_irqrestore(&udc->lock, flags); return rc; diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index e8861eaad907..60340ff9edbf 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -712,6 +712,9 @@ static int usb_gadget_connect_locked(struct usb_gadget *gadget) goto out; } + if (gadget->connected) + goto out; + if (gadget->deactivated || !gadget->udc->allow_connect || !gadget->udc->started) { /* * If the gadget isn't usable (because it is deactivated, @@ -885,8 +888,10 @@ int usb_gadget_activate(struct usb_gadget *gadget) * If gadget has been connected before deactivation, or became connected * while it was being deactivated, we call usb_gadget_connect(). */ - if (gadget->connected) + if (gadget->connected) { + gadget->connected = false; ret = usb_gadget_connect_locked(gadget); + } unlock: mutex_unlock(&gadget->udc->connect_lock); diff --git a/drivers/usb/gadget/udc/goku_udc.c b/drivers/usb/gadget/udc/goku_udc.c index db42a5e3e805..ac2a984c2f87 100644 --- a/drivers/usb/gadget/udc/goku_udc.c +++ b/drivers/usb/gadget/udc/goku_udc.c @@ -1616,7 +1616,8 @@ pm_next: if (stat & INT_USBRESET) { /* hub reset done */ ACK(INT_USBRESET); INFO(dev, "USB reset done, gadget %s\n", - dev->driver->driver.name); + dev->driver ? dev->driver->driver.name : + "<not bound>"); } // and INT_ERR on some endpoint's crc/bitstuff/... problem } diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index b3d58d7c3a77..594d67193763 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -12,7 +12,7 @@ /* #define VERBOSE_DEBUG */ #include <linux/device.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/ioport.h> @@ -261,24 +261,12 @@ static void nuke (struct pxa25x_ep *, int status); /* one GPIO should control a D+ pullup, so host sees this device (or not) */ static void pullup_off(void) { - struct pxa2xx_udc_mach_info *mach = the_controller->mach; - int off_level = mach->gpio_pullup_inverted; - - if (gpio_is_valid(mach->gpio_pullup)) - gpio_set_value(mach->gpio_pullup, off_level); - else if (mach->udc_command) - mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT); + gpiod_set_value(the_controller->pullup_gpio, 0); } static void pullup_on(void) { - struct pxa2xx_udc_mach_info *mach = the_controller->mach; - int on_level = !mach->gpio_pullup_inverted; - - if (gpio_is_valid(mach->gpio_pullup)) - gpio_set_value(mach->gpio_pullup, on_level); - else if (mach->udc_command) - mach->udc_command(PXA2XX_UDC_CMD_CONNECT); + gpiod_set_value(the_controller->pullup_gpio, 1); } #if defined(CONFIG_CPU_BIG_ENDIAN) @@ -1190,8 +1178,7 @@ static int pxa25x_udc_pullup(struct usb_gadget *_gadget, int is_active) udc = container_of(_gadget, struct pxa25x_udc, gadget); - /* not all boards support pullup control */ - if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command) + if (!udc->pullup_gpio) return -EOPNOTSUPP; udc->pullup = (is_active != 0); @@ -2343,19 +2330,17 @@ static int pxa25x_udc_probe(struct platform_device *pdev) /* other non-static parts of init */ dev->dev = &pdev->dev; - dev->mach = dev_get_platdata(&pdev->dev); dev->transceiver = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); - if (gpio_is_valid(dev->mach->gpio_pullup)) { - retval = devm_gpio_request_one(&pdev->dev, dev->mach->gpio_pullup, - GPIOF_OUT_INIT_LOW, "pca25x_udc GPIO PULLUP"); - if (retval) { - dev_dbg(&pdev->dev, - "can't get pullup gpio %d, err: %d\n", - dev->mach->gpio_pullup, retval); - goto err; - } + dev->pullup_gpio = devm_gpiod_get_index_optional(&pdev->dev, "pullup", 0, + GPIOD_OUT_HIGH); + if (IS_ERR(dev->pullup_gpio)) { + dev_dbg(&pdev->dev, + "can't get pullup gpio err: %ld\n", + PTR_ERR(dev->pullup_gpio)); + retval = PTR_ERR(dev->pullup_gpio); + goto err; } timer_setup(&dev->timer, udc_watchdog, 0); @@ -2439,7 +2424,7 @@ static int pxa25x_udc_suspend(struct platform_device *dev, pm_message_t state) struct pxa25x_udc *udc = platform_get_drvdata(dev); unsigned long flags; - if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command) + if (!udc->pullup_gpio) WARNING("USB host won't detect disconnect!\n"); udc->suspended = 1; diff --git a/drivers/usb/gadget/udc/pxa25x_udc.h b/drivers/usb/gadget/udc/pxa25x_udc.h index 6ab6047edc83..3452cf54286c 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.h +++ b/drivers/usb/gadget/udc/pxa25x_udc.h @@ -112,7 +112,7 @@ struct pxa25x_udc { struct device *dev; struct clk *clk; - struct pxa2xx_udc_mach_info *mach; + struct gpio_desc *pullup_gpio; struct usb_phy *transceiver; u64 dma_mask; struct pxa25x_ep ep [PXA_UDC_NUM_ENDPOINTS]; diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index 1abea0d48c35..640f81988c04 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -17,7 +17,6 @@ #include <linux/proc_fs.h> #include <linux/clk.h> #include <linux/irq.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/slab.h> #include <linux/string_choices.h> @@ -1423,14 +1422,7 @@ static const struct usb_ep_ops pxa_ep_ops = { */ static void dplus_pullup(struct pxa_udc *udc, int on) { - if (udc->gpiod) { - gpiod_set_value(udc->gpiod, on); - } else if (udc->udc_command) { - if (on) - udc->udc_command(PXA2XX_UDC_CMD_CONNECT); - else - udc->udc_command(PXA2XX_UDC_CMD_DISCONNECT); - } + gpiod_set_value(udc->gpiod, on); udc->pullup_on = on; } @@ -1521,7 +1513,7 @@ static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active) struct pxa_udc *udc = to_gadget_udc(_gadget); int ret; - if (!udc->gpiod && !udc->udc_command) + if (!udc->gpiod) return -EOPNOTSUPP; dplus_pullup(udc, is_active); @@ -2380,26 +2372,11 @@ MODULE_DEVICE_TABLE(of, udc_pxa_dt_ids); static int pxa_udc_probe(struct platform_device *pdev) { struct pxa_udc *udc = &memory; - int retval = 0, gpio; - struct pxa2xx_udc_mach_info *mach = dev_get_platdata(&pdev->dev); - - if (mach) { - gpio = mach->gpio_pullup; - if (gpio_is_valid(gpio)) { - retval = devm_gpio_request_one(&pdev->dev, gpio, - GPIOF_OUT_INIT_LOW, - "USB D+ pullup"); - if (retval) - return retval; - udc->gpiod = gpio_to_desc(mach->gpio_pullup); - - if (mach->gpio_pullup_inverted ^ gpiod_is_active_low(udc->gpiod)) - gpiod_toggle_active_low(udc->gpiod); - } - udc->udc_command = mach->udc_command; - } else { - udc->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_ASIS); - } + int retval = 0; + + udc->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_ASIS); + if (IS_ERR(udc->gpiod)) + return PTR_ERR(udc->gpiod); udc->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(udc->regs)) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.h b/drivers/usb/gadget/udc/pxa27x_udc.h index 31bf79ce931c..2c28b691010a 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.h +++ b/drivers/usb/gadget/udc/pxa27x_udc.h @@ -426,7 +426,6 @@ struct udc_stats { * @usb_gadget: udc gadget structure * @driver: bound gadget (zero, g_ether, g_mass_storage, ...) * @dev: device - * @udc_command: machine specific function to activate D+ pullup * @gpiod: gpio descriptor of gpio for D+ pullup (or NULL if none) * @transceiver: external transceiver to handle vbus sense and D+ pullup * @ep0state: control endpoint state machine state @@ -452,7 +451,6 @@ struct pxa_udc { struct usb_gadget gadget; struct usb_gadget_driver *driver; struct device *dev; - void (*udc_command)(int); struct gpio_desc *gpiod; struct usb_phy *transceiver; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 0a277a07cf70..b3b1ec696bf5 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -42,6 +42,12 @@ config USB_XHCI_PCI depends on USB_PCI default y +config USB_XHCI_PCI_PROM21 + tristate + depends on USB_XHCI_PCI + default USB_XHCI_PCI if SENSORS_PROM21_XHCI != n + select AUXILIARY_BUS + config USB_XHCI_PCI_RENESAS tristate "Support for additional Renesas xHCI controller with firmware" depends on USB_XHCI_PCI @@ -71,7 +77,7 @@ config USB_XHCI_HISTB config USB_XHCI_MTK tristate "xHCI support for MediaTek SoCs" select MFD_SYSCON - depends on (MIPS && SOC_MT7621) || ARCH_MEDIATEK || COMPILE_TEST + depends on (MIPS && SOC_MT7621) || ARCH_MEDIATEK || ARCH_AIROHA || COMPILE_TEST help Say 'Y' to enable the support for the xHCI host controller found in MediaTek SoCs. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index a07e7ba9cd53..174580c1281a 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o +obj-$(CONFIG_USB_XHCI_PCI_PROM21) += xhci-pci-prom21.o obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 0e17c988d36a..73e76d0e6973 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1685,6 +1685,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, case ClearHubFeature: break; case ClearPortFeature: + if (index != 1) + goto error; switch (value) { case USB_PORT_FEAT_SUSPEND: break; @@ -1694,6 +1696,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, !pdata->vbus_active_level); fallthrough; default: + if (value >= 32) + goto error; max3421_hcd->port_status &= ~(1 << value); } break; @@ -1726,6 +1730,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, break; case SetPortFeature: + if (index != 1) + goto error; switch (value) { case USB_PORT_FEAT_LINK_STATE: case USB_PORT_FEAT_U1_TIMEOUT: @@ -1747,6 +1753,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, max3421_reset_port(hcd); fallthrough; default: + if (value >= 32) + goto error; if ((max3421_hcd->port_status & USB_PORT_STAT_POWER) != 0) max3421_hcd->port_status |= (1 << value); diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c index 7e6f7d72f03e..b1cabf5582fa 100644 --- a/drivers/usb/host/xhci-dbgcap.c +++ b/drivers/usb/host/xhci-dbgcap.c @@ -275,7 +275,6 @@ xhci_dbc_queue_trb(struct xhci_ring *ring, u32 field1, trace_xhci_dbc_gadget_ep_queue(ring, &trb->generic, xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue)); - ring->num_trbs_free--; next = ++(ring->enqueue); if (TRB_TYPE_LINK_LE32(next->link.control)) { next->link.control ^= cpu_to_le32(TRB_CYCLE); @@ -296,7 +295,7 @@ static int xhci_dbc_queue_bulk_tx(struct dbc_ep *dep, num_trbs = count_trbs(req->dma, req->length); WARN_ON(num_trbs != 1); - if (ring->num_trbs_free < num_trbs) + if (xhci_num_trbs_free(ring) <= num_trbs) return -EBUSY; addr = req->dma; @@ -629,18 +628,36 @@ static void xhci_dbc_mem_cleanup(struct xhci_dbc *dbc) dbc->ring_evt = NULL; } +static int xhci_dbc_enable_dce(struct xhci_dbc *dbc, bool enable) +{ + u32 done_state = 0; + u32 ctrl = 0; + + if (enable) { + ctrl = readl(&dbc->regs->control); + ctrl |= DBC_CTRL_DBC_ENABLE | DBC_CTRL_PORT_ENABLE; + done_state = DBC_CTRL_DBC_ENABLE; + } + + writel(ctrl, &dbc->regs->control); + return xhci_handshake(&dbc->regs->control, DBC_CTRL_DBC_ENABLE, + done_state, 1000); +} + +static void xhci_dbc_set_state(struct xhci_dbc *dbc, enum dbc_state new_state) +{ + dbc->state_timestamp = jiffies; + dbc->state = new_state; +} + static int xhci_do_dbc_start(struct xhci_dbc *dbc) { int ret; - u32 ctrl; if (dbc->state != DS_DISABLED) return -EINVAL; - writel(0, &dbc->regs->control); - ret = xhci_handshake(&dbc->regs->control, - DBC_CTRL_DBC_ENABLE, - 0, 1000); + ret = xhci_dbc_enable_dce(dbc, false); if (ret) return ret; @@ -648,27 +665,11 @@ static int xhci_do_dbc_start(struct xhci_dbc *dbc) if (ret) return ret; - ctrl = readl(&dbc->regs->control); - writel(ctrl | DBC_CTRL_DBC_ENABLE | DBC_CTRL_PORT_ENABLE, - &dbc->regs->control); - ret = xhci_handshake(&dbc->regs->control, - DBC_CTRL_DBC_ENABLE, - DBC_CTRL_DBC_ENABLE, 1000); + ret = xhci_dbc_enable_dce(dbc, true); if (ret) return ret; - dbc->state = DS_ENABLED; - - return 0; -} - -static int xhci_do_dbc_stop(struct xhci_dbc *dbc) -{ - if (dbc->state == DS_DISABLED) - return -EINVAL; - - writel(0, &dbc->regs->control); - dbc->state = DS_DISABLED; + xhci_dbc_set_state(dbc, DS_ENABLED); return 0; } @@ -684,29 +685,37 @@ static int xhci_dbc_start(struct xhci_dbc *dbc) spin_lock_irqsave(&dbc->lock, flags); ret = xhci_do_dbc_start(dbc); + if (ret) + goto err_unlock; + spin_unlock_irqrestore(&dbc->lock, flags); - if (ret) { - pm_runtime_put(dbc->dev); /* note this was self.controller */ - return ret; - } + mod_delayed_work(system_percpu_wq, &dbc->event_work, + msecs_to_jiffies(dbc->poll_interval)); + + return 0; - return mod_delayed_work(system_percpu_wq, &dbc->event_work, - msecs_to_jiffies(dbc->poll_interval)); +err_unlock: + + spin_unlock_irqrestore(&dbc->lock, flags); + pm_runtime_put(dbc->dev); /* note this was self.controller */ + + return ret; } static void xhci_dbc_stop(struct xhci_dbc *dbc) { - int ret; unsigned long flags; WARN_ON(!dbc); + spin_lock(&dbc->lock); + switch (dbc->state) { case DS_DISABLED: + spin_unlock(&dbc->lock); return; case DS_CONFIGURED: - spin_lock(&dbc->lock); xhci_dbc_flush_requests(dbc); spin_unlock(&dbc->lock); @@ -714,19 +723,20 @@ static void xhci_dbc_stop(struct xhci_dbc *dbc) dbc->driver->disconnect(dbc); break; default: + spin_unlock(&dbc->lock); break; } cancel_delayed_work_sync(&dbc->event_work); spin_lock_irqsave(&dbc->lock, flags); - ret = xhci_do_dbc_stop(dbc); + writel(0, &dbc->regs->control); + xhci_dbc_set_state(dbc, DS_DISABLED); spin_unlock_irqrestore(&dbc->lock, flags); - if (ret) - return; xhci_dbc_mem_cleanup(dbc); - pm_runtime_put_sync(dbc->dev); /* note, was self.controller */ + + pm_runtime_put(dbc->dev); /* note, was self.controller */ } static void @@ -796,7 +806,6 @@ static void dbc_handle_xfer_event(struct xhci_dbc *dbc, union xhci_trb *event) } if (r->status == -COMP_STALL_ERROR) { dev_warn(dbc->dev, "Give back stale stalled req\n"); - ring->num_trbs_free++; xhci_dbc_giveback(r, 0); } } @@ -861,7 +870,6 @@ static void dbc_handle_xfer_event(struct xhci_dbc *dbc, union xhci_trb *event) break; } - ring->num_trbs_free++; req->actual = req->length - remain_length; xhci_dbc_giveback(req, status); } @@ -893,23 +901,49 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc) return EVT_ERR; case DS_ENABLED: portsc = readl(&dbc->regs->portsc); + ctrl = readl(&dbc->regs->control); + if (portsc & DBC_PORTSC_CONN_STATUS) { - dbc->state = DS_CONNECTED; + xhci_dbc_set_state(dbc, DS_CONNECTED); dev_info(dbc->dev, "DbC connected\n"); + } else if (!(ctrl & DBC_CTRL_DBC_ENABLE)) { + dev_err(dbc->dev, "unexpected DbC disable, xHC reset?\n"); } return EVT_DONE; case DS_CONNECTED: ctrl = readl(&dbc->regs->control); + portsc = readl(&dbc->regs->portsc); if (ctrl & DBC_CTRL_DBC_RUN) { - dbc->state = DS_CONFIGURED; + xhci_dbc_set_state(dbc, DS_CONFIGURED); dev_info(dbc->dev, "DbC configured\n"); - portsc = readl(&dbc->regs->portsc); writel(portsc, &dbc->regs->portsc); ret = EVT_GSER; break; } + /* Connection lost */ + if (!(portsc & DBC_PORTSC_CONN_STATUS)) { + /* covers DCE == 0 as it also sets CONN_STATUS to 0 */ + dev_warn(dbc->dev, "DbC connection lost mid enumeration\n"); + xhci_dbc_set_state(dbc, DS_ENABLED); + + return EVT_DONE; + } + + /* Enumeration timeout */ + if (time_is_before_jiffies(dbc->state_timestamp + + msecs_to_jiffies(DBC_ENUMERATION_TIMEOUT))) { + dev_err(dbc->dev, "DbC enumeration timeout, re-enabling DbC\n"); + dev_dbg(dbc->dev, "dcctrl %x, dcportsc %x\n", ctrl, portsc); + + /* Toggle DCE to retry enumeration */ + ret = xhci_dbc_enable_dce(dbc, false); + udelay(100); + ret = xhci_dbc_enable_dce(dbc, true); + xhci_dbc_set_state(dbc, DS_ENABLED); + } + return EVT_DONE; case DS_CONFIGURED: /* Handle cable unplug event: */ @@ -917,7 +951,7 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc) if (!(portsc & DBC_PORTSC_PORT_ENABLED) && !(portsc & DBC_PORTSC_CONN_STATUS)) { dev_info(dbc->dev, "DbC cable unplugged\n"); - dbc->state = DS_ENABLED; + xhci_dbc_set_state(dbc, DS_ENABLED); xhci_dbc_flush_requests(dbc); xhci_dbc_reinit_ep_rings(dbc); return EVT_DISC; @@ -927,7 +961,7 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc) if (portsc & DBC_PORTSC_RESET_CHANGE) { dev_info(dbc->dev, "DbC port reset\n"); writel(portsc, &dbc->regs->portsc); - dbc->state = DS_ENABLED; + xhci_dbc_set_state(dbc, DS_ENABLED); xhci_dbc_flush_requests(dbc); xhci_dbc_reinit_ep_rings(dbc); return EVT_DISC; @@ -1075,12 +1109,17 @@ static ssize_t dbc_store(struct device *dev, xhci = hcd_to_xhci(dev_get_drvdata(dev)); dbc = xhci->dbc; - if (sysfs_streq(buf, "enable")) + if (sysfs_streq(buf, "enable")) { + mutex_lock(&dbc->enable_mutex); xhci_dbc_start(dbc); - else if (sysfs_streq(buf, "disable")) + mutex_unlock(&dbc->enable_mutex); + } else if (sysfs_streq(buf, "disable")) { + mutex_lock(&dbc->enable_mutex); xhci_dbc_stop(dbc); - else + mutex_unlock(&dbc->enable_mutex); + } else { return -EINVAL; + } return count; } @@ -1446,6 +1485,7 @@ xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver * INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events); spin_lock_init(&dbc->lock); + mutex_init(&dbc->enable_mutex); ret = sysfs_create_groups(&dev->kobj, dbc_dev_groups); if (ret) @@ -1463,8 +1503,9 @@ void xhci_dbc_remove(struct xhci_dbc *dbc) if (!dbc) return; /* stop hw, stop wq and call dbc->ops->stop() */ + mutex_lock(&dbc->enable_mutex); xhci_dbc_stop(dbc); - + mutex_unlock(&dbc->enable_mutex); /* remove sysfs files */ sysfs_remove_groups(&dbc->dev->kobj, dbc_dev_groups); @@ -1517,6 +1558,8 @@ int xhci_dbc_suspend(struct xhci_hcd *xhci) if (!dbc) return 0; + mutex_lock(&dbc->enable_mutex); + switch (dbc->state) { case DS_ENABLED: case DS_CONNECTED: @@ -1528,6 +1571,7 @@ int xhci_dbc_suspend(struct xhci_hcd *xhci) } xhci_dbc_stop(dbc); + mutex_unlock(&dbc->enable_mutex); return 0; } @@ -1540,11 +1584,15 @@ int xhci_dbc_resume(struct xhci_hcd *xhci) if (!dbc) return 0; + mutex_lock(&dbc->enable_mutex); + if (dbc->resume_required) { dbc->resume_required = 0; xhci_dbc_start(dbc); } + mutex_unlock(&dbc->enable_mutex); + return ret; } #endif /* CONFIG_PM */ diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h index 20ae4e7617f2..df7aca8bfe99 100644 --- a/drivers/usb/host/xhci-dbgcap.h +++ b/drivers/usb/host/xhci-dbgcap.h @@ -113,6 +113,7 @@ struct dbc_ep { #define DBC_POLL_INTERVAL_DEFAULT 64 /* milliseconds */ #define DBC_POLL_INTERVAL_MAX 5000 /* milliseconds */ #define DBC_XFER_INACTIVITY_TIMEOUT 10 /* milliseconds */ +#define DBC_ENUMERATION_TIMEOUT 2000 /* milliseconds */ /* * Private structure for DbC hardware state: */ @@ -140,6 +141,7 @@ struct dbc_driver { struct xhci_dbc { spinlock_t lock; /* device access */ + struct mutex enable_mutex; struct device *dev; struct xhci_hcd *xhci; struct dbc_regs __iomem *regs; @@ -162,6 +164,7 @@ struct xhci_dbc { struct delayed_work event_work; unsigned int poll_interval; /* ms */ unsigned long xfer_timestamp; + unsigned long state_timestamp; unsigned resume_required:1; struct dbc_ep eps[2]; diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index bacd0ddd0d09..b0264bd8577a 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -226,9 +226,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, USB_SSP_SUBLINK_SPEED_ST_SYM_RX); ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); - attr &= ~USB_SSP_SUBLINK_SPEED_ST; - attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, - USB_SSP_SUBLINK_SPEED_ST_SYM_TX); + FIELD_MODIFY(USB_SSP_SUBLINK_SPEED_ST, &attr, + USB_SSP_SUBLINK_SPEED_ST_SYM_TX); ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); break; case PLT_ASYM_RX: @@ -639,7 +638,7 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd) /* * xhci_set_port_power() must be called with xhci->lock held. - * It will release and re-aquire the lock while calling ACPI + * It will release and re-acquire the lock while calling ACPI * method. */ static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port, diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 997fe90f54e5..a5e7f363922f 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -324,12 +324,6 @@ void xhci_initialize_ring_info(struct xhci_ring *ring) * handling ring expansion, set the cycle state equal to the old ring. */ ring->cycle_state = 1; - - /* - * Each segment has a link TRB, and leave an extra TRB for SW - * accounting purpose - */ - ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1; } EXPORT_SYMBOL_GPL(xhci_initialize_ring_info); @@ -883,8 +877,8 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev, /* If device ctx array still points to _this_ device, clear it */ if (dev->out_ctx && - xhci->dcbaa->dev_context_ptrs[slot_id] == cpu_to_le64(dev->out_ctx->dma)) - xhci->dcbaa->dev_context_ptrs[slot_id] = 0; + xhci->dcbaa.ctx_array[slot_id] == cpu_to_le64(dev->out_ctx->dma)) + xhci->dcbaa.ctx_array[slot_id] = 0; trace_xhci_free_virt_device(dev); @@ -1022,11 +1016,11 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, dev->udev = udev; /* Point to output device context in dcbaa. */ - xhci->dcbaa->dev_context_ptrs[slot_id] = cpu_to_le64(dev->out_ctx->dma); + xhci->dcbaa.ctx_array[slot_id] = cpu_to_le64(dev->out_ctx->dma); xhci_dbg(xhci, "Set slot id %d dcbaa entry %p to 0x%llx\n", slot_id, - &xhci->dcbaa->dev_context_ptrs[slot_id], - le64_to_cpu(xhci->dcbaa->dev_context_ptrs[slot_id])); + &xhci->dcbaa.ctx_array[slot_id], + le64_to_cpu(xhci->dcbaa.ctx_array[slot_id])); trace_xhci_alloc_virt_device(dev); @@ -1677,7 +1671,7 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) if (!xhci->scratchpad->sp_buffers) goto fail_sp3; - xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma); + xhci->dcbaa.ctx_array[0] = cpu_to_le64(xhci->scratchpad->sp_dma); for (i = 0; i < num_sp; i++) { dma_addr_t dma; void *buf = dma_alloc_coherent(dev, xhci->page_size, &dma, @@ -1933,6 +1927,7 @@ void xhci_rh_bw_cleanup(struct xhci_hcd *xhci) void xhci_mem_cleanup(struct xhci_hcd *xhci) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_device_context_array *dcbaa; int i; cancel_delayed_work_sync(&xhci->cmd_timer); @@ -1952,8 +1947,11 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed command ring"); xhci_cleanup_command_queue(xhci); - for (i = xhci->max_slots; i > 0; i--) - xhci_free_virt_devices_depth_first(xhci, i); + if (xhci->devs) { + for (i = xhci->max_slots; i > 0; i--) + xhci_free_virt_devices_depth_first(xhci, i); + kfree(xhci->devs); + } dma_pool_destroy(xhci->segment_pool); xhci->segment_pool = NULL; @@ -1978,10 +1976,12 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed medium stream array pool"); - if (xhci->dcbaa) - dma_free_coherent(dev, sizeof(*xhci->dcbaa), - xhci->dcbaa, xhci->dcbaa->dma); - xhci->dcbaa = NULL; + dcbaa = &xhci->dcbaa; + if (dcbaa->ctx_array) { + dma_free_coherent(dev, array_size(sizeof(*dcbaa->ctx_array), xhci->max_slots + 1), + dcbaa->ctx_array, dcbaa->dma); + dcbaa->ctx_array = NULL; + } scratchpad_free(xhci); @@ -2008,6 +2008,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci->rh_bw = NULL; xhci->port_caps = NULL; xhci->interrupters = NULL; + xhci->devs = NULL; xhci->usb2_rhub.bus_state.bus_suspended = 0; xhci->usb3_rhub.bus_state.bus_suspended = 0; @@ -2409,23 +2410,26 @@ EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter); int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { - struct device *dev = xhci_to_hcd(xhci)->self.sysdev; - dma_addr_t dma; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_device_context_array *dcbaa = &xhci->dcbaa; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); - /* - * xHCI section 5.4.6 - Device Context array must be - * "physically contiguous and 64-byte (cache line) aligned". - */ - xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, flags); - if (!xhci->dcbaa) + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating internal virtual device array"); + xhci->devs = kcalloc_node(xhci->max_slots + 1, sizeof(*xhci->devs), flags, + dev_to_node(dev)); + if (!xhci->devs) + goto fail; + + xhci->dcbaa.ctx_array = + dma_alloc_coherent(dev, array_size(sizeof(*dcbaa->ctx_array), xhci->max_slots + 1), + &dcbaa->dma, flags); + if (!dcbaa->ctx_array) goto fail; - xhci->dcbaa->dma = dma; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Device context base array address = %pad (DMA), %p (virt)", - &xhci->dcbaa->dma, xhci->dcbaa); + &dcbaa->dma, dcbaa->ctx_array); /* * Initialize the ring segment pool. The ring must be a contiguous diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 06043c7c3100..d9b865546a67 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -185,9 +185,9 @@ static void xhci_mtk_rxfifo_depth_set(struct xhci_hcd_mtk *mtk) return; value = readl(hcd->regs + HSCH_CFG1); - value &= ~SCH3_RXFIFO_DEPTH_MASK; - value |= FIELD_PREP(SCH3_RXFIFO_DEPTH_MASK, - SCH_FIFO_TO_KB(mtk->rxfifo_depth) - 1); + FIELD_MODIFY(SCH3_RXFIFO_DEPTH_MASK, &value, + SCH_FIFO_TO_KB(mtk->rxfifo_depth) - 1); + writel(value, hcd->regs + HSCH_CFG1); } diff --git a/drivers/usb/host/xhci-pci-prom21.c b/drivers/usb/host/xhci-pci-prom21.c new file mode 100644 index 000000000000..6486f4a09345 --- /dev/null +++ b/drivers/usb/host/xhci-pci-prom21.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Promontory 21 xHCI host controller PCI Bus Glue. + * + * This does not add any PROM21-specific USB or xHCI operation. It exists only + * to publish an auxiliary device for integrated temperature sensor support. + * + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com> + */ + +#include <linux/auxiliary_bus.h> +#include <linux/device/devres.h> +#include <linux/errno.h> +#include <linux/idr.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_data/usb-xhci-prom21.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "xhci-pci.h" + +struct prom21_xhci_auxdev { + struct auxiliary_device *auxdev; + struct prom21_xhci_pdata pdata; + int id; +}; + +static DEFINE_IDA(prom21_xhci_auxdev_ida); + +static void prom21_xhci_auxdev_release(struct device *dev, void *res) +{ + struct prom21_xhci_auxdev *prom21_auxdev = res; + + auxiliary_device_destroy(prom21_auxdev->auxdev); + ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id); +} + +static int prom21_xhci_create_auxdev(struct pci_dev *pdev) +{ + struct prom21_xhci_auxdev *prom21_auxdev; + struct usb_hcd *hcd = pci_get_drvdata(pdev); + int ret; + + prom21_auxdev = devres_alloc(prom21_xhci_auxdev_release, + sizeof(*prom21_auxdev), GFP_KERNEL); + if (!prom21_auxdev) + return -ENOMEM; + + prom21_auxdev->pdata.pdev = pdev; + prom21_auxdev->pdata.regs = hcd->regs; + prom21_auxdev->pdata.rsrc_len = hcd->rsrc_len; + + prom21_auxdev->id = ida_alloc(&prom21_xhci_auxdev_ida, GFP_KERNEL); + if (prom21_auxdev->id < 0) { + ret = prom21_auxdev->id; + goto err_free_devres; + } + + prom21_auxdev->auxdev = auxiliary_device_create(&pdev->dev, + KBUILD_MODNAME, "hwmon", + &prom21_auxdev->pdata, + prom21_auxdev->id); + if (!prom21_auxdev->auxdev) { + ret = -ENOMEM; + goto err_free_ida; + } + + devres_add(&pdev->dev, prom21_auxdev); + return 0; + +err_free_ida: + ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id); +err_free_devres: + devres_free(prom21_auxdev); + return ret; +} + +static void prom21_xhci_destroy_auxdev(struct pci_dev *pdev) +{ + devres_release(&pdev->dev, prom21_xhci_auxdev_release, NULL, NULL); +} + +static int prom21_xhci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int retval; + + retval = xhci_pci_common_probe(dev, id); + if (retval) + return retval; + + retval = prom21_xhci_create_auxdev(dev); + if (retval) { + /* + * The auxiliary device only provides optional temperature sensor + * support. Keep the xHCI controller usable if it fails. + */ + dev_err(&dev->dev, + "failed to create PROM21 hwmon auxiliary device: %d\n", + retval); + } + + return 0; +} + +static void prom21_xhci_remove(struct pci_dev *dev) +{ + prom21_xhci_destroy_auxdev(dev); + xhci_pci_remove(dev); +} + +static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver prom21_xhci_driver = { + .name = "xhci-pci-prom21", + .id_table = pci_ids, + + .probe = prom21_xhci_probe, + .remove = prom21_xhci_remove, + + .shutdown = usb_hcd_pci_shutdown, + .driver = { + .pm = pm_ptr(&usb_hcd_pci_pm_ops), + }, +}; +module_pci_driver(prom21_xhci_driver); + +MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>"); +MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver"); +MODULE_IMPORT_NS("xhci"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 585b2f3117b0..039c26b241d0 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -696,12 +696,23 @@ static const struct pci_device_id pci_ids_renesas[] = { { /* end: all zeroes */ } }; +/* handled by xhci-pci-prom21 if enabled */ +static const struct pci_device_id pci_ids_prom21[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) }, + { /* end: all zeroes */ } +}; + static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { if (IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) && pci_match_id(pci_ids_renesas, dev)) return -ENODEV; + if (IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21) && + pci_match_id(pci_ids_prom21, dev)) + return -ENODEV; + return xhci_pci_common_probe(dev, id); } diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h index e87c7d9d76b8..11f435f94322 100644 --- a/drivers/usb/host/xhci-pci.h +++ b/drivers/usb/host/xhci-pci.h @@ -4,6 +4,9 @@ #ifndef XHCI_PCI_H #define XHCI_PCI_H +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC 0x43fc +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD 0x43fd + int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id); void xhci_pci_remove(struct pci_dev *dev); diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 8a993ee21c87..d747c0905827 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -32,29 +32,6 @@ MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3); -static void xhci_rcar_start_gen2(struct usb_hcd *hcd) -{ - /* LCLK Select */ - writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); - /* USB3.0 Configuration */ - writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); - writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); - writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); - /* USB3.0 Polarity */ - writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); - writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); -} - -static int xhci_rcar_is_gen2(struct device *dev) -{ - struct device_node *node = dev->of_node; - - return of_device_is_compatible(node, "renesas,xhci-r8a7790") || - of_device_is_compatible(node, "renesas,xhci-r8a7791") || - of_device_is_compatible(node, "renesas,xhci-r8a7793") || - of_device_is_compatible(node, "renesas,rcar-gen2-xhci"); -} - static void xhci_rcar_start(struct usb_hcd *hcd) { u32 temp; @@ -64,8 +41,23 @@ static void xhci_rcar_start(struct usb_hcd *hcd) temp = readl(hcd->regs + RCAR_USB3_INT_ENA); temp |= RCAR_USB3_INT_ENA_VAL; writel(temp, hcd->regs + RCAR_USB3_INT_ENA); - if (xhci_rcar_is_gen2(hcd->self.controller)) - xhci_rcar_start_gen2(hcd); + } +} + +static void xhci_rcar_gen2_start(struct usb_hcd *hcd) +{ + if (hcd->regs != NULL) { + xhci_rcar_start(hcd); + + /* LCLK Select */ + writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); + /* USB3.0 Configuration */ + writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); + writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); + writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); + /* USB3.0 Polarity */ + writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); + writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); } } @@ -192,13 +184,16 @@ static int xhci_rcar_init_quirk(struct usb_hcd *hcd) static int xhci_rcar_resume_quirk(struct usb_hcd *hcd) { + struct xhci_plat_priv *priv; int ret; ret = xhci_rcar_download_firmware(hcd); - if (!ret) - xhci_rcar_start(hcd); + if (ret) + return ret; - return ret; + priv = hcd_to_xhci_priv(hcd); + priv->plat_start(hcd); + return 0; } /* @@ -213,19 +208,20 @@ static int xhci_rcar_resume_quirk(struct usb_hcd *hcd) * long delay for the handshake of STS_HALT is neeed in xhci_suspend() * by using the XHCI_SLOW_SUSPEND quirk. */ -#define SET_XHCI_PLAT_PRIV_FOR_RCAR(firmware) \ - .firmware_name = firmware, \ - .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, \ - .init_quirk = xhci_rcar_init_quirk, \ - .plat_start = xhci_rcar_start, \ - .resume_quirk = xhci_rcar_resume_quirk, - static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { - SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V1) + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1, + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rcar_init_quirk, + .plat_start = xhci_rcar_gen2_start, + .resume_quirk = xhci_rcar_resume_quirk, }; static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { - SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3) + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V3, + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rcar_init_quirk, + .plat_start = xhci_rcar_start, + .resume_quirk = xhci_rcar_resume_quirk, }; static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index e47e644b296e..4f98d8269625 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -340,7 +340,7 @@ static bool trb_in_td(struct xhci_td *td, dma_addr_t suspect_dma) * Only for transfer and command rings where driver is the producer, not for * event rings. */ -static unsigned int xhci_num_trbs_free(struct xhci_ring *ring) +unsigned int xhci_num_trbs_free(struct xhci_ring *ring) { struct xhci_segment *enq_seg = ring->enq_seg; union xhci_trb *enq = ring->enqueue; @@ -609,7 +609,7 @@ static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index) { - if (slot_id == 0 || slot_id >= MAX_HC_SLOTS) { + if (slot_id == 0 || slot_id > xhci->max_slots) { xhci_warn(xhci, "Invalid slot_id %u\n", slot_id); return NULL; } @@ -1613,7 +1613,7 @@ static void xhci_handle_cmd_disable_slot(struct xhci_hcd *xhci, int slot_id, /* Delete default control endpoint resources */ xhci_free_device_endpoint_resources(xhci, virt_dev, true); if (cmd_comp_code == COMP_SUCCESS) { - xhci->dcbaa->dev_context_ptrs[slot_id] = 0; + xhci->dcbaa.ctx_array[slot_id] = 0; xhci->devs[slot_id] = NULL; } } @@ -1804,7 +1804,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_command *cmd; u32 cmd_type; - if (slot_id >= MAX_HC_SLOTS) { + if (slot_id > xhci->max_slots) { xhci_warn(xhci, "Invalid slot_id %u\n", slot_id); return; } @@ -2395,7 +2395,6 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, u32 trb_comp_code; bool sum_trbs_for_length = false; u32 remaining, requested, ep_trb_len; - int short_framestatus; trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); urb_priv = td->urb->hcpriv; @@ -2404,8 +2403,6 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, requested = frame->length; remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); - short_framestatus = td->urb->transfer_flags & URB_SHORT_NOT_OK ? - -EREMOTEIO : 0; /* handle completion code */ switch (trb_comp_code) { @@ -2413,15 +2410,12 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, /* Don't overwrite status if TD had an error, see xHCI 4.9.1 */ if (td->error_mid_td) break; - if (remaining) { - frame->status = short_framestatus; + if (remaining) sum_trbs_for_length = true; - break; - } frame->status = 0; break; case COMP_SHORT_PACKET: - frame->status = short_framestatus; + frame->status = 0; sum_trbs_for_length = true; break; case COMP_BANDWIDTH_OVERRUN_ERROR: @@ -2456,7 +2450,7 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, break; case COMP_STOPPED_SHORT_PACKET: /* field normally containing residue now contains transferred */ - frame->status = short_framestatus; + frame->status = 0; requested = remaining; break; case COMP_STOPPED_LENGTH_INVALID: @@ -2492,26 +2486,6 @@ finish_td: finish_td(xhci, ep, ep_ring, td, trb_comp_code); } -static void skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, - struct xhci_virt_ep *ep, int status) -{ - struct urb_priv *urb_priv; - struct usb_iso_packet_descriptor *frame; - int idx; - - urb_priv = td->urb->hcpriv; - idx = urb_priv->num_tds_done; - frame = &td->urb->iso_frame_desc[idx]; - - /* The transfer is partly done. */ - frame->status = -EXDEV; - - /* calc actual length */ - frame->actual_length = 0; - - xhci_dequeue_td(xhci, td, ep->ring, status); -} - /* * Process bulk and interrupt tds, update urb status and actual_length. */ @@ -2542,6 +2516,7 @@ static void process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, td->status = 0; break; case COMP_SHORT_PACKET: + ep->err_count = 0; td->status = 0; break; case COMP_STOPPED_SHORT_PACKET: @@ -2854,7 +2829,11 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID) return 0; - skip_isoc_td(xhci, td, ep, status); + /* + * TD was missed, skip it. Core already initialized frame->status + * to -EXDEV and frame->actual_length to 0, nothing more to do. + */ + xhci_dequeue_td(xhci, td, ep_ring, 0); if (!list_empty(&ep_ring->td_list)) { if (ring_xrun_event) { @@ -4353,6 +4332,7 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd, u32 field3, u32 field4, bool command_must_succeed) { int reserved_trbs = xhci->cmd_ring_reserved_trbs; + struct usb_hcd *hcd = xhci_to_hcd(xhci); int ret; if ((xhci->xhc_state & XHCI_STATE_DYING) || @@ -4362,6 +4342,11 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd, return -ESHUTDOWN; } + if (!HCD_HW_ACCESSIBLE(hcd)) { + xhci_warn(xhci, "Can't queue command, xHC not accessible\n"); + return -ESHUTDOWN; + } + if (!command_must_succeed) reserved_trbs++; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index a54f5b57f205..6922cc5496c1 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -102,17 +102,10 @@ int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us) */ void xhci_quiesce(struct xhci_hcd *xhci) { - u32 halted; u32 cmd; - u32 mask; - - mask = ~(XHCI_IRQS); - halted = readl(&xhci->op_regs->status) & STS_HALT; - if (!halted) - mask &= ~CMD_RUN; cmd = readl(&xhci->op_regs->command); - cmd &= mask; + cmd &= ~(CMD_RUN | XHCI_IRQS); writel(cmd, &xhci->op_regs->command); } @@ -558,7 +551,7 @@ static void xhci_init(struct usb_hcd *hcd) xhci_set_cmd_ring_deq(xhci); /* Set Device Context Base Address Array pointer */ - xhci_write_64(xhci, xhci->dcbaa->dma, &xhci->op_regs->dcbaa_ptr); + xhci_write_64(xhci, xhci->dcbaa.dma, &xhci->op_regs->dcbaa_ptr); /* Set Doorbell array pointer */ xhci_set_doorbell_ptr(xhci); @@ -999,6 +992,10 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) /* step 1: stop endpoint */ /* skipped assuming that port suspend has done */ + /* Check if command ring is empty */ + if (!list_empty(&xhci->cmd_list)) + xhci_warn(xhci, "Suspending and stopping xHC with pending command!\n"); + /* step 2: clear Run/Stop bit */ command = readl(&xhci->op_regs->command); command &= ~CMD_RUN; @@ -4460,9 +4457,9 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, xhci_dbg_trace(xhci, trace_xhci_dbg_address, "Slot ID %d dcbaa entry @%p = %#016llx", udev->slot_id, - &xhci->dcbaa->dev_context_ptrs[udev->slot_id], + &xhci->dcbaa.ctx_array[udev->slot_id], (unsigned long long) - le64_to_cpu(xhci->dcbaa->dev_context_ptrs[udev->slot_id])); + le64_to_cpu(xhci->dcbaa.ctx_array[udev->slot_id])); xhci_dbg_trace(xhci, trace_xhci_dbg_address, "Output Context DMA address = %#08llx", (unsigned long long)virt_dev->out_ctx->dma); @@ -5460,7 +5457,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); - xhci->max_slots = HCS_MAX_SLOTS(hcs_params1); + xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); /* xhci-plat or xhci-pci might have set max_interrupters already */ if (!xhci->max_interrupters) @@ -5533,8 +5530,6 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) init_completion(&xhci->cmd_ring_stop_completion); xhci_hcd_page_size(xhci); - memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs)); - /* Allocate xHCI data structures */ retval = xhci_mem_init(xhci, GFP_KERNEL); if (retval) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index aeecd301f207..d02046a573e4 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -33,8 +33,11 @@ /* xHCI PCI Configuration Registers */ #define XHCI_SBRN_OFFSET (0x60) -/* Max number of USB devices for any host controller - limit in section 6.1 */ -#define MAX_HC_SLOTS 256 +/* + * Max number of Devices Slots. xHCI specification section 5.3.3 + * Valid values are in the range of 1 to 255. + */ +#define MAX_HC_SLOTS 255 /* * Max Number of Ports. xHCI specification section 5.3.3 * Valid values are in the range of 1 to 255. @@ -792,18 +795,18 @@ struct xhci_tt_bw_info { /** * struct xhci_device_context_array - * @dev_context_ptr array of 64-bit DMA addresses for device contexts + * @ctx_array: Pointer to an array of addresses. The array size depends on Max + * Slots read from HCSPARAMS1. + * @dma: DMA address to @ctx_array + * + * Device Context Base Address Array (DCBAA) - Section 6.1. + * ctx_array[0]: Scratchpad Buffer Array Base Address + * ctx_array[1-MaxSlots]: Device Context Base Address */ struct xhci_device_context_array { - /* 64-bit device addresses; we only write 32-bit addresses */ - __le64 dev_context_ptrs[MAX_HC_SLOTS]; - /* private xHCD pointers */ + __le64 *ctx_array; dma_addr_t dma; }; -/* - * TODO: change this to be dynamically sized at HC mem init time since the HC - * might not be able to handle the maximum number of devices possible. - */ struct xhci_transfer_event { @@ -1376,7 +1379,6 @@ struct xhci_ring { u32 cycle_state; unsigned int stream_id; unsigned int num_segs; - unsigned int num_trbs_free; /* used only by xhci DbC */ unsigned int bounce_buf_len; enum xhci_ring_type type; u32 old_trb_comp_code; @@ -1532,7 +1534,7 @@ struct xhci_hcd { /* optional reset controller */ struct reset_control *reset; /* data structures */ - struct xhci_device_context_array *dcbaa; + struct xhci_device_context_array dcbaa; struct xhci_interrupter **interrupters; struct xhci_ring *cmd_ring; unsigned int cmd_ring_state; @@ -1552,7 +1554,7 @@ struct xhci_hcd { /* these are not thread safe so use mutex */ struct mutex mutex; /* Internal mirror of the HW's dcbaa */ - struct xhci_virt_device *devs[MAX_HC_SLOTS]; + struct xhci_virt_device **devs; /* For keeping track of bandwidth domains per roothub. */ struct xhci_root_port_bw_info *rh_bw; @@ -1955,6 +1957,7 @@ void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, void xhci_cleanup_command_queue(struct xhci_hcd *xhci); void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring); 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); diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 0b56b773dbdf..91b62acb537b 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -145,7 +145,7 @@ config USB_APPLEDISPLAY Displays over USB. This driver provides a sysfs interface. config USB_QCOM_EUD - tristate "QCOM Embedded USB Debugger(EUD) Driver" + tristate "Qualcomm Embedded USB Debugger(EUD) Driver" depends on ARCH_QCOM || COMPILE_TEST select QCOM_SCM select USB_ROLE_SWITCH diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 7cdbdfe07a76..11508ed4df25 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -665,6 +665,7 @@ static const struct usb_device_id onboard_dev_id_table[] = { { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 HUB */ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 HUB */ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0625) }, /* Genesys Logic GL3590 USB 3.2 HUB */ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 HUB */ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 HUB */ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 HUB */ diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index ac1aa3e122ad..3523f8f8a149 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -101,6 +101,13 @@ static const struct onboard_dev_pdata genesys_gl852g_data = { .is_hub = true, }; +static const struct onboard_dev_pdata genesys_gl3590_data = { + .reset_us = 50, + .num_supplies = 2, + .supply_names = { "vdd", "vdd12" }, + .is_hub = true, +}; + static const struct onboard_dev_pdata usb_a_conn_data = { .num_supplies = 1, .supply_names = { "vbus" }, @@ -146,6 +153,7 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,625", .data = &genesys_gl3590_data, }, { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, }, { .compatible = "usbbda,179", .data = &realtek_rtl8188etv_data, }, { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index 7c0778631bea..fb0693742f01 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -746,15 +746,15 @@ static int usb251xb_i2c_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume); static const struct i2c_device_id usb251xb_id[] = { - { "usb2422" }, - { "usb2512b" }, - { "usb2512bi" }, - { "usb2513b" }, - { "usb2513bi" }, - { "usb2514b" }, - { "usb2514bi" }, - { "usb2517" }, - { "usb2517i" }, + { .name = "usb2422" }, + { .name = "usb2512b" }, + { .name = "usb2512bi" }, + { .name = "usb2513b" }, + { .name = "usb2513bi" }, + { .name = "usb2514b" }, + { .name = "usb2514bi" }, + { .name = "usb2517" }, + { .name = "usb2517i" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, usb251xb_id); diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index 322e59381b78..759770a13260 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -390,7 +390,7 @@ static SIMPLE_DEV_PM_OPS(usb3503_platform_pm_ops, usb3503_platform_suspend, usb3503_platform_resume); static const struct i2c_device_id usb3503_id[] = { - { USB3503_I2C_NAME }, + { .name = USB3503_I2C_NAME }, { } }; MODULE_DEVICE_TABLE(i2c, usb3503_id); diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c index c9a2fb3518ae..2ae9656715e0 100644 --- a/drivers/usb/misc/usb4604.c +++ b/drivers/usb/misc/usb4604.c @@ -135,7 +135,7 @@ static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend, usb4604_i2c_resume); static const struct i2c_device_id usb4604_id[] = { - { "usb4604" }, + { .name = "usb4604" }, { } }; MODULE_DEVICE_TABLE(i2c, usb4604_id); diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c index 2940f0c84e1b..73cc70e958de 100644 --- a/drivers/usb/phy/phy-isp1301.c +++ b/drivers/usb/phy/phy-isp1301.c @@ -25,7 +25,7 @@ struct isp1301 { #define phy_to_isp(p) (container_of((p), struct isp1301, phy)) static const struct i2c_device_id isp1301_id[] = { - { "isp1301" }, + { .name = "isp1301" }, { } }; MODULE_DEVICE_TABLE(i2c, isp1301_id); diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c index d974da43fba3..3a574b6ee0c4 100644 --- a/drivers/usb/serial/ark3116.c +++ b/drivers/usb/serial/ark3116.c @@ -28,7 +28,6 @@ #include <linux/usb/serial.h> #include <linux/serial.h> #include <linux/serial_reg.h> -#include <linux/uaccess.h> #include <linux/mutex.h> #include <linux/spinlock.h> diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c index 7bbd9523d4e9..cfff292631aa 100644 --- a/drivers/usb/serial/belkin_sa.c +++ b/drivers/usb/serial/belkin_sa.c @@ -23,11 +23,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include "belkin_sa.h" diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c index 4e8ceb23c27d..57e20eb02a46 100644 --- a/drivers/usb/serial/cyberjack.c +++ b/drivers/usb/serial/cyberjack.c @@ -28,11 +28,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index bcf302e88ca4..78fb1389b34c 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -25,7 +25,6 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/moduleparam.h> @@ -35,7 +34,6 @@ #include <linux/serial.h> #include <linux/kfifo.h> #include <linux/delay.h> -#include <linux/uaccess.h> #include <linux/unaligned.h> #include "cypress_m8.h" diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index c481208255eb..6899aebfd6ae 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -15,11 +15,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/wait.h> #include <linux/sched/signal.h> diff --git a/drivers/usb/serial/empeg.c b/drivers/usb/serial/empeg.c index aedcf7ebd269..4937575caff1 100644 --- a/drivers/usb/serial/empeg.c +++ b/drivers/usb/serial/empeg.c @@ -16,11 +16,8 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> -#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c index 9262a2ac97f5..dcceb95d7d99 100644 --- a/drivers/usb/serial/f81232.c +++ b/drivers/usb/serial/f81232.c @@ -11,13 +11,10 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/mutex.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/serial_reg.h> diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c index 685930ac8383..0037767e4bbf 100644 --- a/drivers/usb/serial/f81534.c +++ b/drivers/usb/serial/f81534.c @@ -28,7 +28,6 @@ #include <linux/usb/serial.h> #include <linux/serial_reg.h> #include <linux/module.h> -#include <linux/uaccess.h> /* Serial Port register Address */ #define F81534_UART_BASE_ADDRESS 0x1200 diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index af14548fa03d..88dd32da82c2 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -31,7 +31,6 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index 7205483a0115..8020149f5658 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -15,12 +15,9 @@ #include <linux/slab.h> #include <linux/timer.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> -#include <linux/atomic.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 757f0a586ddb..6eaf74930aa3 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -17,7 +17,6 @@ #include <linux/moduleparam.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> #include <linux/kfifo.h> #include <linux/serial.h> diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 3f5889145e51..34ccf7820537 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -25,12 +25,12 @@ * */ +#include <linux/atomic.h> #include <linux/kernel.h> #include <linux/jiffies.h> #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index d48819d785ef..07c0eff3bef4 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -18,7 +18,6 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> @@ -28,7 +27,6 @@ #include <linux/kfifo.h> #include <linux/ioctl.h> #include <linux/firmware.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c index 3c6a9b9b9c2b..c01bdbcb3f55 100644 --- a/drivers/usb/serial/ipaq.c +++ b/drivers/usb/serial/ipaq.c @@ -10,11 +10,8 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> -#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/ipw.c b/drivers/usb/serial/ipw.c index 83709d678b3a..5be153f2c68d 100644 --- a/drivers/usb/serial/ipw.c +++ b/drivers/usb/serial/ipw.c @@ -36,12 +36,10 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> #include "usb-wwan.h" #define DRIVER_AUTHOR "Roelf Diedericks" diff --git a/drivers/usb/serial/ir-usb.c b/drivers/usb/serial/ir-usb.c index 12e928d25ba1..aac84539730d 100644 --- a/drivers/usb/serial/ir-usb.c +++ b/drivers/usb/serial/ir-usb.c @@ -25,11 +25,9 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/usb/irda.h> diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index 0ca111b111c7..3b1f7546fb5c 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -14,13 +14,11 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include "iuu_phoenix.h" diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index 28b80607cebd..4d3746c7a94e 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -29,11 +29,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/usb/ezusb.h> diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 5036600dd334..3b99f9676c35 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -15,12 +15,10 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> #include <linux/workqueue.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/usb/ezusb.h> diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c index e72a0b45a707..b74c88fec03a 100644 --- a/drivers/usb/serial/kl5kusb105.c +++ b/drivers/usb/serial/kl5kusb105.c @@ -35,10 +35,8 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> -#include <linux/uaccess.h> #include <linux/unaligned.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index 6126afd67a7b..4a622cf8e6ae 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -23,11 +23,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/ioctl.h> diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c index 163161881d2d..a6de18917ec9 100644 --- a/drivers/usb/serial/mct_u232.c +++ b/drivers/usb/serial/mct_u232.c @@ -21,11 +21,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/unaligned.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index bc834cc48550..35473544f1c8 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -13,11 +13,8 @@ #include <linux/usb.h> #include <linux/errno.h> #include <linux/slab.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> -#include <linux/moduleparam.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb/serial.h> #define DRIVER_DESC "Metrologic Instruments Inc. - USB-POS driver" diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 94459408e7fb..104f43a8f4d8 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -17,11 +17,12 @@ * Copyright (C) 2000 Inside Out Networks, All rights reserved. * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com> */ + +#include <linux/atomic.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index c0d0c24b074b..ef803e74ab4b 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -13,7 +13,6 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/serial.h> diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index c9b9928c473a..e3c5a1b97542 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -20,9 +20,7 @@ #include <linux/serial_reg.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/unaligned.h> @@ -146,11 +144,6 @@ #define MX_WAIT_FOR_LOW_WATER 0x0040 #define MX_WAIT_FOR_SEND_NEXT 0x0080 -#define MX_UPORT_2_PORT BIT(0) -#define MX_UPORT_4_PORT BIT(1) -#define MX_UPORT_8_PORT BIT(2) -#define MX_UPORT_16_PORT BIT(3) - /* This structure holds all of the local port information */ struct mxuport_port { u8 mcr_state; /* Last MCR state */ @@ -159,26 +152,31 @@ struct mxuport_port { spinlock_t spinlock; /* Protects msr_state */ }; +/* Encode number of ports (2..16 or undefined) */ +#define MX_PORTS_MASK GENMASK(3, 0) +#define MX_PORTS_OFFSET 1 +#define MX_PORTS(n) (((n) - MX_PORTS_OFFSET) & MX_PORTS_MASK) + /* Table of devices that work with this driver */ static const struct usb_device_id mxuport_idtable[] = { { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1250_PID), - .driver_info = MX_UPORT_2_PORT }, + .driver_info = MX_PORTS(2) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1251_PID), - .driver_info = MX_UPORT_2_PORT }, + .driver_info = MX_PORTS(2) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1410_PID), - .driver_info = MX_UPORT_4_PORT }, + .driver_info = MX_PORTS(4) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1450_PID), - .driver_info = MX_UPORT_4_PORT }, + .driver_info = MX_PORTS(4) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1451_PID), - .driver_info = MX_UPORT_4_PORT }, + .driver_info = MX_PORTS(4) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1618_PID), - .driver_info = MX_UPORT_8_PORT }, + .driver_info = MX_PORTS(8) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1658_PID), - .driver_info = MX_UPORT_8_PORT }, + .driver_info = MX_PORTS(8) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1613_PID), - .driver_info = MX_UPORT_16_PORT }, + .driver_info = MX_PORTS(16) }, { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1653_PID), - .driver_info = MX_UPORT_16_PORT }, + .driver_info = MX_PORTS(16) }, {} /* Terminating entry */ }; @@ -942,14 +940,8 @@ static int mxuport_calc_num_ports(struct usb_serial *serial, int num_ports; int i; - if (features & MX_UPORT_2_PORT) { - num_ports = 2; - } else if (features & MX_UPORT_4_PORT) { - num_ports = 4; - } else if (features & MX_UPORT_8_PORT) { - num_ports = 8; - } else if (features & MX_UPORT_16_PORT) { - num_ports = 16; + if (features & MX_PORTS_MASK) { + num_ports = (features & MX_PORTS_MASK) + MX_PORTS_OFFSET; } else { dev_warn(&serial->interface->dev, "unknown device, assuming two ports\n"); diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c index b59982ed8b25..363b8432cc61 100644 --- a/drivers/usb/serial/omninet.c +++ b/drivers/usb/serial/omninet.c @@ -14,10 +14,8 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index e2bed477ad57..152dfc901cd8 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -10,14 +10,12 @@ #include <linux/kernel.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/slab.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> #define CONTROL_RTS 0x02 #define RESEND_CTS_STATE 0x03 diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index a34e79cfd5b6..4c4009b8a46d 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -32,7 +32,6 @@ #include <linux/jiffies.h> #include <linux/errno.h> #include <linux/tty.h> -#include <linux/tty_flip.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/bitops.h> diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index 3ef5b5d8ce1a..c6364e8ff71f 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -38,15 +38,12 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/spinlock.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> #include <linux/kfifo.h> #include "oti6858.h" diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 50dc838e8115..0bcbdcea52af 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -15,13 +15,10 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/serial.h> #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/unaligned.h> diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index 1a930dc668e4..105c0a0eae86 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -8,7 +8,6 @@ */ #include <linux/tty.h> -#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index b05c655b3cc4..dc2b39810a26 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -13,14 +13,12 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/serial.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/serial_reg.h> -#include <linux/uaccess.h> /* default urb timeout for usb operations */ #define QT2_USB_TIMEOUT USB_CTRL_SET_TIMEOUT diff --git a/drivers/usb/serial/safe_serial.c b/drivers/usb/serial/safe_serial.c index d267a31dcccf..55e59e341732 100644 --- a/drivers/usb/serial/safe_serial.c +++ b/drivers/usb/serial/safe_serial.c @@ -64,11 +64,9 @@ #include <linux/errno.h> #include <linux/gfp.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index 2fbb48f19efd..c11d64bf08fb 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -14,8 +14,6 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> -#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> #include <linux/usb.h> diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c index da6316410b77..b0d51558b73c 100644 --- a/drivers/usb/serial/ssu100.c +++ b/drivers/usb/serial/ssu100.c @@ -9,14 +9,12 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/serial.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/serial_reg.h> -#include <linux/uaccess.h> #define QT_OPEN_CLOSE_CHANNEL 0xca #define QT_SET_GET_DEVICE 0xc2 diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c index 7a5aa39172a8..8823671a9c7a 100644 --- a/drivers/usb/serial/symbolserial.c +++ b/drivers/usb/serial/symbolserial.c @@ -10,12 +10,10 @@ #include <linux/kernel.h> #include <linux/tty.h> #include <linux/slab.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> static const struct usb_device_id id_table[] = { { USB_DEVICE(0x05e0, 0x0600) }, diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index b3591d6d7645..b3f00e315815 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -18,7 +18,6 @@ #include <linux/firmware.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> @@ -26,7 +25,6 @@ #include <linux/serial.h> #include <linux/kfifo.h> #include <linux/mutex.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 0e072fd87c3d..17edc057a311 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -22,14 +22,11 @@ #include <linux/slab.h> #include <linux/tty.h> #include <linux/tty_driver.h> -#include <linux/tty_flip.h> #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/seq_file.h> #include <linux/spinlock.h> #include <linux/mutex.h> #include <linux/list.h> -#include <linux/uaccess.h> #include <linux/serial.h> #include <linux/usb.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index e752ffa4dc62..a183cc4515ed 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -27,7 +27,6 @@ #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/bitops.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/cdc.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c index 062a38fe0c1c..5706c46c49c2 100644 --- a/drivers/usb/serial/visor.c +++ b/drivers/usb/serial/visor.c @@ -15,12 +15,8 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> -#include <linux/tty_flip.h> #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/spinlock.h> -#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/usb/cdc.h> diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index 6e2b52b2b5f8..a2d83f69e00e 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -16,13 +16,9 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/tty.h> -#include <linux/tty_driver.h> -#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/spinlock.h> #include <linux/mutex.h> -#include <linux/uaccess.h> -#include <asm/termbits.h> #include <linux/usb.h> #include <linux/serial_reg.h> #include <linux/serial.h> diff --git a/drivers/usb/serial/wishbone-serial.c b/drivers/usb/serial/wishbone-serial.c index 670d573f6b63..4a1fed4cb57b 100644 --- a/drivers/usb/serial/wishbone-serial.c +++ b/drivers/usb/serial/wishbone-serial.c @@ -11,7 +11,6 @@ #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> #define GSI_VENDOR_OPENCLOSE 0xB0 diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 9fc4082de770..352c765d8803 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -16,6 +16,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/tty.h> +#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/cdc.h> #include <linux/usb/serial.h> diff --git a/drivers/usb/serial/xsens_mt.c b/drivers/usb/serial/xsens_mt.c index 382b3698c1d5..151a6e893ba2 100644 --- a/drivers/usb/serial/xsens_mt.c +++ b/drivers/usb/serial/xsens_mt.c @@ -10,7 +10,6 @@ #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/serial.h> -#include <linux/uaccess.h> #define XSENS_VID 0x2639 diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 2e8ae1d2faf9..604868ebf422 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1577,8 +1577,8 @@ static void anx7411_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id anx7411_id[] = { - { "anx7411" }, - {} + { .name = "anx7411" }, + { } }; MODULE_DEVICE_TABLE(i2c, anx7411_id); diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c index c54e42c7e6a1..bea0c1deec94 100644 --- a/drivers/usb/typec/mux/fsa4480.c +++ b/drivers/usb/typec/mux/fsa4480.c @@ -336,7 +336,7 @@ static void fsa4480_remove(struct i2c_client *client) } static const struct i2c_device_id fsa4480_table[] = { - { "fsa4480" }, + { .name = "fsa4480" }, { } }; MODULE_DEVICE_TABLE(i2c, fsa4480_table); diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 1698428654ab..219a32da1348 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -151,13 +151,14 @@ struct pmc_usb { u8 num_ports; struct device *dev; struct intel_scu_ipc_dev *ipc; - struct pmc_usb_port *port; struct acpi_device *iom_adev; void __iomem *iom_base; u32 iom_port_status_offset; u8 iom_port_status_size; struct dentry *dentry; + + struct pmc_usb_port port[] __counted_by(num_ports); }; static struct dentry *pmc_mux_debugfs_root; @@ -731,27 +732,25 @@ static int pmc_usb_probe(struct platform_device *pdev) { struct fwnode_handle *fwnode = NULL; struct pmc_usb *pmc; + u8 num_ports = 0; int i = 0; int ret; - pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL); - if (!pmc) - return -ENOMEM; - device_for_each_child_node(&pdev->dev, fwnode) - pmc->num_ports++; + num_ports++; /* The IOM microcontroller has a limitation of max 4 ports. */ - if (pmc->num_ports > 4) { + if (num_ports > 4) { dev_err(&pdev->dev, "driver limited to 4 ports\n"); return -ERANGE; } - pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports, - sizeof(struct pmc_usb_port), GFP_KERNEL); - if (!pmc->port) + pmc = devm_kzalloc(&pdev->dev, struct_size(pmc, port, num_ports), GFP_KERNEL); + if (!pmc) return -ENOMEM; + pmc->num_ports = num_ports; + pmc->ipc = devm_intel_scu_ipc_dev_get(&pdev->dev); if (!pmc->ipc) return -EPROBE_DEFER; diff --git a/drivers/usb/typec/mux/it5205.c b/drivers/usb/typec/mux/it5205.c index 4357cc67a867..5e1a120b2e3b 100644 --- a/drivers/usb/typec/mux/it5205.c +++ b/drivers/usb/typec/mux/it5205.c @@ -266,7 +266,7 @@ static void it5205_remove(struct i2c_client *client) } static const struct i2c_device_id it5205_table[] = { - { "it5205" }, + { .name = "it5205" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, it5205_table); diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c index b57b6c9c40fe..d1fa26ff442c 100644 --- a/drivers/usb/typec/mux/nb7vpq904m.c +++ b/drivers/usb/typec/mux/nb7vpq904m.c @@ -499,7 +499,7 @@ static void nb7vpq904m_remove(struct i2c_client *client) } static const struct i2c_device_id nb7vpq904m_table[] = { - { "nb7vpq904m" }, + { .name = "nb7vpq904m" }, { } }; MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table); diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c index 8eeec135dcdb..985683fe49e9 100644 --- a/drivers/usb/typec/mux/pi3usb30532.c +++ b/drivers/usb/typec/mux/pi3usb30532.c @@ -169,7 +169,7 @@ static void pi3usb30532_remove(struct i2c_client *client) } static const struct i2c_device_id pi3usb30532_table[] = { - { "pi3usb30532" }, + { .name = "pi3usb30532" }, { } }; MODULE_DEVICE_TABLE(i2c, pi3usb30532_table); diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c index 1256252eceed..f52443638ee2 100644 --- a/drivers/usb/typec/mux/ps883x.c +++ b/drivers/usb/typec/mux/ps883x.c @@ -61,19 +61,110 @@ struct ps883x_retimer { struct mutex lock; /* protect non-concurrent retimer & switch */ enum typec_orientation orientation; - u8 cfg0; - u8 cfg1; - u8 cfg2; + bool in_reset; }; +static int ps883x_enable_vregs(struct ps883x_retimer *retimer) +{ + struct device *dev = &retimer->client->dev; + int ret; + + ret = regulator_enable(retimer->vdd33_supply); + if (ret) { + dev_err(dev, "cannot enable VDD 3.3V regulator: %d\n", ret); + return ret; + } + + ret = regulator_enable(retimer->vdd33_cap_supply); + if (ret) { + dev_err(dev, "cannot enable VDD 3.3V CAP regulator: %d\n", ret); + goto err_vdd33_disable; + } + + usleep_range(4000, 10000); + + ret = regulator_enable(retimer->vdd_supply); + if (ret) { + dev_err(dev, "cannot enable VDD regulator: %d\n", ret); + goto err_vdd33_cap_disable; + } + + ret = regulator_enable(retimer->vddar_supply); + if (ret) { + dev_err(dev, "cannot enable VDD AR regulator: %d\n", ret); + goto err_vdd_disable; + } + + ret = regulator_enable(retimer->vddat_supply); + if (ret) { + dev_err(dev, "cannot enable VDD AT regulator: %d\n", ret); + goto err_vddar_disable; + } + + ret = regulator_enable(retimer->vddio_supply); + if (ret) { + dev_err(dev, "cannot enable VDD IO regulator: %d\n", ret); + goto err_vddat_disable; + } + + return 0; + +err_vddat_disable: + regulator_disable(retimer->vddat_supply); +err_vddar_disable: + regulator_disable(retimer->vddar_supply); +err_vdd_disable: + regulator_disable(retimer->vdd_supply); +err_vdd33_cap_disable: + regulator_disable(retimer->vdd33_cap_supply); +err_vdd33_disable: + regulator_disable(retimer->vdd33_supply); + + return ret; +} + +static void ps883x_disable_vregs(struct ps883x_retimer *retimer) +{ + regulator_disable(retimer->vddio_supply); + regulator_disable(retimer->vddat_supply); + regulator_disable(retimer->vddar_supply); + regulator_disable(retimer->vdd_supply); + regulator_disable(retimer->vdd33_cap_supply); + regulator_disable(retimer->vdd33_supply); +} + +static void ps883x_reset(struct ps883x_retimer *retimer) +{ + if (retimer->in_reset) + return; + + gpiod_set_value(retimer->reset_gpio, 1); + ps883x_disable_vregs(retimer); + retimer->in_reset = true; +} + static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0, - int cfg1, int cfg2) + int cfg1, int cfg2, bool reset) { struct device *dev = &retimer->client->dev; int ret; - if (retimer->cfg0 == cfg0 && retimer->cfg1 == cfg1 && retimer->cfg2 == cfg2) + if (reset) { + ps883x_reset(retimer); + return 0; + } else if (retimer->in_reset) { + ret = ps883x_enable_vregs(retimer); + if (ret) + return ret; + + gpiod_set_value(retimer->reset_gpio, 0); + + /* firmware initialization delay */ + msleep(60); + + retimer->in_reset = false; + } ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0); if (ret) { @@ -93,10 +184,6 @@ static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0, return ret; } - retimer->cfg0 = cfg0; - retimer->cfg1 = cfg1; - retimer->cfg2 = cfg2; - return 0; } @@ -107,6 +194,7 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state int cfg0 = CONN_STATUS_0_CONNECTION_PRESENT; int cfg1 = 0x00; int cfg2 = 0x00; + bool reset = false; if (retimer->orientation == TYPEC_ORIENTATION_REVERSE) cfg0 |= CONN_STATUS_0_ORIENTATION_REVERSED; @@ -148,9 +236,13 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state } } else { switch (state->mode) { + /* SAFE can be transient or point to an actual disconnect */ case TYPEC_STATE_SAFE: + reset = retimer->orientation == TYPEC_ORIENTATION_NONE; + break; /* USB2 pins don't even go through this chip */ case TYPEC_MODE_USB2: + reset = true; break; case TYPEC_STATE_USB: case TYPEC_MODE_USB3: @@ -171,7 +263,7 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state } } - return ps883x_configure(retimer, cfg0, cfg1, cfg2); + return ps883x_configure(retimer, cfg0, cfg1, cfg2, reset); } static int ps883x_sw_set(struct typec_switch_dev *sw, @@ -184,11 +276,19 @@ static int ps883x_sw_set(struct typec_switch_dev *sw, if (ret) return ret; - mutex_lock(&retimer->lock); + guard(mutex)(&retimer->lock); if (retimer->orientation != orientation) { retimer->orientation = orientation; + /* + * Orientation notifications usually come prior to mode switch + * events. If the retimer is already in reset, we still want to + * cache the new orientation value for the subsequent ps883x_set(). + */ + if (retimer->in_reset) + return 0; + ret = regmap_assign_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, CONN_STATUS_0_ORIENTATION_REVERSED, orientation == TYPEC_ORIENTATION_REVERSE); @@ -196,8 +296,6 @@ static int ps883x_sw_set(struct typec_switch_dev *sw, dev_err(&retimer->client->dev, "failed to set orientation: %d\n", ret); } - mutex_unlock(&retimer->lock); - return ret; } @@ -222,75 +320,6 @@ static int ps883x_retimer_set(struct typec_retimer *rtmr, return typec_mux_set(retimer->typec_mux, &mux_state); } -static int ps883x_enable_vregs(struct ps883x_retimer *retimer) -{ - struct device *dev = &retimer->client->dev; - int ret; - - ret = regulator_enable(retimer->vdd33_supply); - if (ret) { - dev_err(dev, "cannot enable VDD 3.3V regulator: %d\n", ret); - return ret; - } - - ret = regulator_enable(retimer->vdd33_cap_supply); - if (ret) { - dev_err(dev, "cannot enable VDD 3.3V CAP regulator: %d\n", ret); - goto err_vdd33_disable; - } - - usleep_range(4000, 10000); - - ret = regulator_enable(retimer->vdd_supply); - if (ret) { - dev_err(dev, "cannot enable VDD regulator: %d\n", ret); - goto err_vdd33_cap_disable; - } - - ret = regulator_enable(retimer->vddar_supply); - if (ret) { - dev_err(dev, "cannot enable VDD AR regulator: %d\n", ret); - goto err_vdd_disable; - } - - ret = regulator_enable(retimer->vddat_supply); - if (ret) { - dev_err(dev, "cannot enable VDD AT regulator: %d\n", ret); - goto err_vddar_disable; - } - - ret = regulator_enable(retimer->vddio_supply); - if (ret) { - dev_err(dev, "cannot enable VDD IO regulator: %d\n", ret); - goto err_vddat_disable; - } - - return 0; - -err_vddat_disable: - regulator_disable(retimer->vddat_supply); -err_vddar_disable: - regulator_disable(retimer->vddar_supply); -err_vdd_disable: - regulator_disable(retimer->vdd_supply); -err_vdd33_cap_disable: - regulator_disable(retimer->vdd33_cap_supply); -err_vdd33_disable: - regulator_disable(retimer->vdd33_supply); - - return ret; -} - -static void ps883x_disable_vregs(struct ps883x_retimer *retimer) -{ - regulator_disable(retimer->vddio_supply); - regulator_disable(retimer->vddat_supply); - regulator_disable(retimer->vddar_supply); - regulator_disable(retimer->vdd_supply); - regulator_disable(retimer->vdd33_cap_supply); - regulator_disable(retimer->vdd33_supply); -} - static int ps883x_get_vregs(struct ps883x_retimer *retimer) { struct device *dev = &retimer->client->dev; @@ -422,6 +451,9 @@ static int ps883x_retimer_probe(struct i2c_client *client) } } + /* Keep the retimer in reset until a Type-C notification comes */ + ps883x_reset(retimer); + sw_desc.drvdata = retimer; sw_desc.fwnode = dev_fwnode(dev); sw_desc.set = ps883x_sw_set; diff --git a/drivers/usb/typec/mux/ptn36502.c b/drivers/usb/typec/mux/ptn36502.c index 129d9d24b932..afd16775dbaf 100644 --- a/drivers/usb/typec/mux/ptn36502.c +++ b/drivers/usb/typec/mux/ptn36502.c @@ -404,7 +404,7 @@ static void ptn36502_remove(struct i2c_client *client) } static const struct i2c_device_id ptn36502_table[] = { - { "ptn36502" }, + { .name = "ptn36502" }, { } }; MODULE_DEVICE_TABLE(i2c, ptn36502_table); diff --git a/drivers/usb/typec/mux/wcd939x-usbss.c b/drivers/usb/typec/mux/wcd939x-usbss.c index d46c353dfaf2..73db3aa3cec4 100644 --- a/drivers/usb/typec/mux/wcd939x-usbss.c +++ b/drivers/usb/typec/mux/wcd939x-usbss.c @@ -753,7 +753,7 @@ static void wcd939x_usbss_remove(struct i2c_client *client) } static const struct i2c_device_id wcd939x_usbss_table[] = { - { "wcd9390-usbss" }, + { .name = "wcd9390-usbss" }, { } }; MODULE_DEVICE_TABLE(i2c, wcd939x_usbss_table); diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 9ab1277b7ed1..3319f6a2b0c9 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1841,8 +1841,8 @@ static const struct of_device_id fusb302_dt_match[] __maybe_unused = { MODULE_DEVICE_TABLE(of, fusb302_dt_match); static const struct i2c_device_id fusb302_i2c_device_id[] = { - { "typec_fusb302" }, - {} + { .name = "typec_fusb302" }, + { } }; MODULE_DEVICE_TABLE(i2c, fusb302_i2c_device_id); diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c index 3766790c1548..35320f89dad2 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c @@ -4,19 +4,14 @@ */ #include <linux/err.h> -#include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/regmap.h> -#include <linux/regulator/consumer.h> #include <linux/slab.h> -#include <linux/usb/role.h> #include <linux/usb/tcpm.h> -#include <linux/usb/typec_mux.h> #include <drm/bridge/aux-bridge.h> diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index 8051eaa46991..429bd42a0e62 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -9,6 +9,7 @@ #include <linux/kernel.h> #include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> @@ -704,6 +705,7 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, struct device *dev = &pdev->dev; struct pmic_typec_port_irq_data *irq_data; struct pmic_typec_port *pmic_typec_port; + struct fwnode_handle *connector; int i, ret, irq; pmic_typec_port = devm_kzalloc(dev, sizeof(*pmic_typec_port), GFP_KERNEL); @@ -720,7 +722,15 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, mutex_init(&pmic_typec_port->vbus_lock); - pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus"); + connector = device_get_named_child_node(dev, "connector"); + if (!connector) + return -EINVAL; + + pmic_typec_port->vdd_vbus = devm_of_regulator_get_optional(dev, + to_of_node(connector), + "vbus"); + if (pmic_typec_port->vdd_vbus == ERR_PTR(-ENODEV)) + pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus"); if (IS_ERR(pmic_typec_port->vdd_vbus)) return PTR_ERR(pmic_typec_port->vdd_vbus); diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 0148b8f50412..7ac7000b2d13 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -141,13 +141,10 @@ static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) } if (vconn_pres) { - if (polarity == TYPEC_POLARITY_CC2) { - reg &= ~TCPC_ROLE_CTRL_CC1; - reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_OPEN); - } else { - reg &= ~TCPC_ROLE_CTRL_CC2; - reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_OPEN); - } + if (polarity == TYPEC_POLARITY_CC2) + FIELD_MODIFY(TCPC_ROLE_CTRL_CC1, ®, TCPC_ROLE_CTRL_CC_OPEN); + else + FIELD_MODIFY(TCPC_ROLE_CTRL_CC2, ®, TCPC_ROLE_CTRL_CC_OPEN); } ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); @@ -1020,7 +1017,7 @@ static int tcpci_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume); static const struct i2c_device_id tcpci_id[] = { - { "tcpci" }, + { .name = "tcpci" }, { } }; MODULE_DEVICE_TABLE(i2c, tcpci_id); diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 7324139d51c8..998693a61839 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -579,7 +579,7 @@ static int max_tcpci_suspend(struct device *dev) static SIMPLE_DEV_PM_OPS(max_tcpci_pm_ops, max_tcpci_suspend, max_tcpci_resume); static const struct i2c_device_id max_tcpci_id[] = { - { "maxtcpc" }, + { .name = "maxtcpc" }, { } }; MODULE_DEVICE_TABLE(i2c, max_tcpci_id); diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index 4b3e4e22a82e..a8726da6fc71 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -373,10 +373,10 @@ static const struct rt1711h_chip_info rt1715 = { }; static const struct i2c_device_id rt1711h_id[] = { - { "et7304", (kernel_ulong_t)&rt1715 }, - { "rt1711h", (kernel_ulong_t)&rt1711h }, - { "rt1715", (kernel_ulong_t)&rt1715 }, - {} + { .name = "et7304", .driver_data = (kernel_ulong_t)&rt1715 }, + { .name = "rt1711h", .driver_data = (kernel_ulong_t)&rt1711h }, + { .name = "rt1715", .driver_data = (kernel_ulong_t)&rt1715 }, + { } }; MODULE_DEVICE_TABLE(i2c, rt1711h_id); diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d0b769333bd9..d5ee0af9058b 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -2028,7 +2028,7 @@ static const struct of_device_id tps6598x_of_match[] = { MODULE_DEVICE_TABLE(of, tps6598x_of_match); static const struct i2c_device_id tps6598x_id[] = { - { "tps6598x", (kernel_ulong_t)&tps6598x_data }, + { .name = "tps6598x", .driver_data = (kernel_ulong_t)&tps6598x_data }, { } }; MODULE_DEVICE_TABLE(i2c, tps6598x_id); diff --git a/drivers/usb/typec/ucsi/cros_ec_ucsi.c b/drivers/usb/typec/ucsi/cros_ec_ucsi.c index 251aa7251ce6..c192d42d449e 100644 --- a/drivers/usb/typec/ucsi/cros_ec_ucsi.c +++ b/drivers/usb/typec/ucsi/cros_ec_ucsi.c @@ -114,12 +114,14 @@ static int cros_ucsi_async_control(struct ucsi *ucsi, u64 cmd) } static int cros_ucsi_sync_control(struct ucsi *ucsi, u64 cmd, u32 *cci, - void *data, size_t size) + void *data, size_t size, void *msg_out, + size_t msg_out_size) { struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); int ret; - ret = ucsi_sync_control_common(ucsi, cmd, cci, data, size); + ret = ucsi_sync_control_common(ucsi, cmd, cci, data, size, msg_out, + msg_out_size); switch (ret) { case -EBUSY: /* EC may return -EBUSY if CCI.busy is set. diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index a4b9a6b51649..ff33a5e7c6b0 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -40,6 +40,11 @@ static int ucsi_cmd(void *data, u64 val) case UCSI_READ_POWER_LEVEL: ret = ucsi_send_command(ucsi, val, NULL, 0); break; + case UCSI_SET_PDOS: + ret = ucsi_write_message_out_command(ucsi, val, NULL, 0, + ucsi->debugfs->message_out, + UCSI_COMMAND_DATA_LEN(val)); + break; case UCSI_GET_CAPABILITY: case UCSI_GET_CONNECTOR_CAPABILITY: case UCSI_GET_ALTERNATE_MODES: @@ -110,6 +115,30 @@ static int ucsi_vbus_volt_show(struct seq_file *m, void *v) } DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt); +static ssize_t ucsi_message_out_write(struct file *file, + const char __user *data, size_t count, loff_t *ppos) +{ + struct ucsi *ucsi = file->private_data; + int ret; + + char *buf __free(kfree) = memdup_user_nul(data, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = hex2bin(ucsi->debugfs->message_out, buf, + min(count / 2, sizeof(ucsi->debugfs->message_out))); + if (ret) + return ret; + + return count; +} + +static const struct file_operations ucsi_message_out_fops = { + .open = simple_open, + .write = ucsi_message_out_write, + .llseek = generic_file_llseek, +}; + void ucsi_debugfs_register(struct ucsi *ucsi) { ucsi->debugfs = kzalloc_obj(*ucsi->debugfs); @@ -122,6 +151,8 @@ void ucsi_debugfs_register(struct ucsi *ucsi) debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops); debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops); debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops); + debugfs_create_file("message_out", 0200, ucsi->debugfs->dentry, ucsi, + &ucsi_message_out_fops); } void ucsi_debugfs_unregister(struct ucsi *ucsi) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 61cb24ed820f..92166a3725b1 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -63,7 +63,8 @@ void ucsi_notify_common(struct ucsi *ucsi, u32 cci) EXPORT_SYMBOL_GPL(ucsi_notify_common); int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size) + void *data, size_t size, void *msg_out, + size_t msg_out_size) { bool ack = UCSI_COMMAND(command) == UCSI_ACK_CC_CI; int ret; @@ -75,6 +76,17 @@ int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, reinit_completion(&ucsi->complete); + if (msg_out && msg_out_size) { + if (!ucsi->ops->write_message_out) { + ret = -EOPNOTSUPP; + goto out_clear_bit; + } + + ret = ucsi->ops->write_message_out(ucsi, msg_out, msg_out_size); + if (ret) + goto out_clear_bit; + } + ret = ucsi->ops->async_control(ucsi, command); if (ret) goto out_clear_bit; @@ -110,11 +122,12 @@ static int ucsi_acknowledge(struct ucsi *ucsi, bool conn_ack) ctrl |= UCSI_ACK_CONNECTOR_CHANGE; } - return ucsi->ops->sync_control(ucsi, ctrl, NULL, NULL, 0); + return ucsi->ops->sync_control(ucsi, ctrl, NULL, NULL, 0, NULL, 0); } static int ucsi_run_command(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size, bool conn_ack) + void *data, size_t size, void *msg_out, + size_t msg_out_size, bool conn_ack) { int ret, err; @@ -123,10 +136,12 @@ static int ucsi_run_command(struct ucsi *ucsi, u64 command, u32 *cci, if (size > UCSI_MAX_DATA_LENGTH(ucsi)) return -EINVAL; - ret = ucsi->ops->sync_control(ucsi, command, cci, data, size); + ret = ucsi->ops->sync_control(ucsi, command, cci, data, size, + msg_out, msg_out_size); if (*cci & UCSI_CCI_BUSY) - return ucsi_run_command(ucsi, UCSI_CANCEL, cci, NULL, 0, false) ?: -EBUSY; + return ucsi_run_command(ucsi, UCSI_CANCEL, cci, + NULL, 0, NULL, 0, false) ?: -EBUSY; if (ret) return ret; @@ -158,7 +173,8 @@ static int ucsi_read_error(struct ucsi *ucsi, u8 connector_num) int ret; command = UCSI_GET_ERROR_STATUS | UCSI_CONNECTOR_NUMBER(connector_num); - ret = ucsi_run_command(ucsi, command, &cci, &error, sizeof(error), false); + ret = ucsi_run_command(ucsi, command, &cci, &error, + sizeof(error), NULL, 0, false); if (ret < 0) return ret; @@ -208,7 +224,8 @@ static int ucsi_read_error(struct ucsi *ucsi, u8 connector_num) } static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd, - void *data, size_t size, bool conn_ack) + void *data, size_t size, void *msg_out, + size_t msg_out_size, bool conn_ack) { u8 connector_num; u32 cci; @@ -236,7 +253,8 @@ static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd, mutex_lock(&ucsi->ppm_lock); - ret = ucsi_run_command(ucsi, cmd, &cci, data, size, conn_ack); + ret = ucsi_run_command(ucsi, cmd, &cci, data, size, + msg_out, msg_out_size, conn_ack); if (cci & UCSI_CCI_ERROR) ret = ucsi_read_error(ucsi, connector_num); @@ -250,10 +268,23 @@ static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd, int ucsi_send_command(struct ucsi *ucsi, u64 command, void *data, size_t size) { - return ucsi_send_command_common(ucsi, command, data, size, false); + return ucsi_send_command_common(ucsi, command, data, + size, NULL, 0, false); } EXPORT_SYMBOL_GPL(ucsi_send_command); +int ucsi_write_message_out_command(struct ucsi *ucsi, u64 command, + void *data, size_t size, void *msg_out, + size_t msg_out_size) +{ + if (msg_out_size > UCSI_MAX_MSG_OUT_DATA_LEN(ucsi)) + return -EINVAL; + + return ucsi_send_command_common(ucsi, command, data, + size, msg_out, msg_out_size, false); +} +EXPORT_SYMBOL_GPL(ucsi_write_message_out_command); + /* -------------------------------------------------------------------------- */ struct ucsi_work { @@ -681,7 +712,8 @@ static int ucsi_get_connector_status(struct ucsi_connector *con, bool conn_ack) UCSI_MAX_DATA_LENGTH(con->ucsi)); int ret; - ret = ucsi_send_command_common(con->ucsi, command, &con->status, size, conn_ack); + ret = ucsi_send_command_common(con->ucsi, command, &con->status, size, + NULL, 0, conn_ack); return ret < 0 ? ret : 0; } @@ -1658,6 +1690,7 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con) INIT_WORK(&con->work, ucsi_handle_connector_change); init_completion(&con->complete); mutex_init(&con->lock); + lockdep_set_class(&con->lock, &con->lock_key); INIT_LIST_HEAD(&con->partner_tasks); con->ucsi = ucsi; @@ -1903,6 +1936,9 @@ static int ucsi_init(struct ucsi *ucsi) goto err_reset; } + for (i = 0; i < ucsi->cap.num_connectors; i++) + lockdep_register_key(&connector[i].lock_key); + /* Register all connectors */ for (i = 0; i < ucsi->cap.num_connectors; i++) { connector[i].num = i + 1; @@ -1932,6 +1968,9 @@ static int ucsi_init(struct ucsi *ucsi) return 0; err_unregister: + for (i = 0; i < ucsi->cap.num_connectors; i++) + lockdep_unregister_key(&connector[i].lock_key); + for (con = connector; con->port; con++) { if (con->wq) destroy_workqueue(con->wq); @@ -2182,6 +2221,7 @@ void ucsi_unregister(struct ucsi *ucsi) usb_power_delivery_unregister(ucsi->connector[i].pd); ucsi->connector[i].pd = NULL; typec_unregister_port(ucsi->connector[i].port); + lockdep_unregister_key(&ucsi->connector[i].lock_key); } kfree(ucsi->connector); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index cff9ddc2ae21..325ed1e5ca80 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -65,6 +65,7 @@ struct dentry; * @read_cci: Read CCI register * @poll_cci: Read CCI register while polling with notifications disabled * @read_message_in: Read message data from UCSI + * @write_message_out: Write message data to UCSI * @sync_control: Blocking control operation * @async_control: Non-blocking control operation * @update_altmodes: Squashes duplicate DP altmodes @@ -82,8 +83,9 @@ struct ucsi_operations { int (*read_cci)(struct ucsi *ucsi, u32 *cci); int (*poll_cci)(struct ucsi *ucsi, u32 *cci); int (*read_message_in)(struct ucsi *ucsi, void *val, size_t val_len); + int (*write_message_out)(struct ucsi *ucsi, void *data, size_t data_len); int (*sync_control)(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size); + void *data, size_t size, void *msg_out, size_t msg_out_size); int (*async_control)(struct ucsi *ucsi, u64 command); bool (*update_altmodes)(struct ucsi *ucsi, u8 recipient, struct ucsi_altmode *orig, @@ -136,6 +138,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_PD_MESSAGE 0x15 #define UCSI_GET_CAM_CS 0x18 #define UCSI_SET_SINK_PATH 0x1c +#define UCSI_SET_PDOS 0x1d #define UCSI_READ_POWER_LEVEL 0x1e #define UCSI_SET_USB 0x21 #define UCSI_GET_LPM_PPM_INFO 0x22 @@ -213,6 +216,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_PD_MESSAGE_TYPE_IDENTITY 4 #define UCSI_GET_PD_MESSAGE_TYPE_REVISION 5 +/* Data length bits */ +#define UCSI_COMMAND_DATA_LEN(_cmd_) (((_cmd_) >> 8) & GENMASK(7, 0)) + /* -------------------------------------------------------------------------- */ /* Error information returned by PPM in response to GET_ERROR_STATUS command. */ @@ -453,6 +459,8 @@ struct ucsi_bitfield { /* -------------------------------------------------------------------------- */ +#define MESSAGE_OUT_MAX_LEN 256 + struct ucsi_debugfs_entry { u64 command; struct ucsi_data { @@ -460,6 +468,7 @@ struct ucsi_debugfs_entry { u64 high; } response; int status; + u8 message_out[MESSAGE_OUT_MAX_LEN]; struct dentry *dentry; }; @@ -503,6 +512,9 @@ struct ucsi { }; #define UCSI_MAX_DATA_LENGTH(u) (((u)->version < UCSI_VERSION_2_0) ? 0x10 : 0xff) +#define UCSI_MAX_MSG_OUT_DATA_LEN(u) \ + (((u)->version >= UCSI_VERSION_3_0) ? 255 : \ + (((u)->version >= UCSI_VERSION_2_0) ? 256 : 16)) #define UCSI_MAX_SVID 5 #define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6) @@ -517,6 +529,7 @@ struct ucsi_connector { struct ucsi *ucsi; struct mutex lock; /* port lock */ + struct lock_class_key lock_key; struct work_struct work; struct completion complete; struct workqueue_struct *wq; @@ -564,13 +577,17 @@ struct ucsi_connector { int ucsi_send_command(struct ucsi *ucsi, u64 command, void *retval, size_t size); +int ucsi_write_message_out_command(struct ucsi *ucsi, u64 command, + void *retval, size_t size, + void *msg_out, size_t msg_out_size); void ucsi_altmode_update_active(struct ucsi_connector *con); int ucsi_resume(struct ucsi *ucsi); void ucsi_notify_common(struct ucsi *ucsi, u32 cci); int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size); + void *data, size_t size, void *msg_out, + size_t msg_out_size); #if IS_ENABLED(CONFIG_POWER_SUPPLY) int ucsi_register_port_psy(struct ucsi_connector *con); diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 6b92f296e985..60b12961e1a4 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -86,6 +86,21 @@ static int ucsi_acpi_read_message_in(struct ucsi *ucsi, void *val, size_t val_le return 0; } +static int ucsi_acpi_write_message_out(struct ucsi *ucsi, void *data, size_t data_len) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + + if (!data || !data_len) + return -EINVAL; + + if (ucsi->version <= UCSI_VERSION_1_2) + memcpy(ua->base + UCSI_MESSAGE_OUT, data, data_len); + else + memcpy(ua->base + UCSIv2_MESSAGE_OUT, data, data_len); + + return 0; +} + static int ucsi_acpi_async_control(struct ucsi *ucsi, u64 command) { struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); @@ -101,19 +116,22 @@ static const struct ucsi_operations ucsi_acpi_ops = { .read_cci = ucsi_acpi_read_cci, .poll_cci = ucsi_acpi_poll_cci, .read_message_in = ucsi_acpi_read_message_in, + .write_message_out = ucsi_acpi_write_message_out, .sync_control = ucsi_sync_control_common, .async_control = ucsi_acpi_async_control }; static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, - void *val, size_t len) + void *val, size_t len, void *msg_out, + size_t msg_out_size) { u16 bogus_change = UCSI_CONSTAT_POWER_LEVEL_CHANGE | UCSI_CONSTAT_PDOS_CHANGE; struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); int ret; - ret = ucsi_sync_control_common(ucsi, command, cci, val, len); + ret = ucsi_sync_control_common(ucsi, command, cci, val, len, + msg_out, msg_out_size); if (ret < 0) return ret; diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 4463c1ae96bd..d46ca942026e 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -608,7 +608,8 @@ static int ucsi_ccg_async_control(struct ucsi *ucsi, u64 command) } static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size) + void *data, size_t size, void *msg_out, + size_t msg_out_size) { struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); struct ucsi_connector *con; @@ -630,7 +631,8 @@ static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, ucsi_ccg_update_set_new_cam_cmd(uc, con, &command); } - ret = ucsi_sync_control_common(ucsi, command, cci, data, size); + ret = ucsi_sync_control_common(ucsi, command, cci, data, size, + msg_out, msg_out_size); switch (UCSI_COMMAND(command)) { case UCSI_GET_CURRENT_CAM: @@ -1530,8 +1532,8 @@ static const struct of_device_id ucsi_ccg_of_match_table[] = { MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table); static const struct i2c_device_id ucsi_ccg_device_id[] = { - { "ccgx-ucsi" }, - {} + { .name = "ccgx-ucsi" }, + { } }; MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id); diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index ca749fde49bd..ad669d2f8b9c 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -18,6 +18,7 @@ #include <linux/usb/pd_vdo.h> #include <linux/usb/typec_altmode.h> #include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> #include <linux/workqueue_types.h> #include "ucsi.h" @@ -82,6 +83,8 @@ struct gaokun_ucsi_port { struct gaokun_ucsi *ucsi; struct auxiliary_device *bridge; + struct typec_mux *typec_mux; + int idx; enum gaokun_ucsi_ccx ccx; enum gaokun_ucsi_mux mux; @@ -226,19 +229,18 @@ static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port, port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi); port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi); - /* Mode and SVID are unused; keeping them to make things clearer */ switch (port->mode) { case USBC_DPAM_PAN_C: case USBC_DPAM_PAN_C_REVERSE: - port->mode = DP_PIN_ASSIGN_C; /* correct it for usb later */ + port->mode = TYPEC_DP_STATE_C; /* correct it for usb later */ break; case USBC_DPAM_PAN_D: case USBC_DPAM_PAN_D_REVERSE: - port->mode = DP_PIN_ASSIGN_D; + port->mode = TYPEC_DP_STATE_D; break; case USBC_DPAM_PAN_E: case USBC_DPAM_PAN_E_REVERSE: - port->mode = DP_PIN_ASSIGN_E; + port->mode = TYPEC_DP_STATE_E; break; case USBC_DPAM_PAN_NONE: port->mode = TYPEC_STATE_SAFE; @@ -287,18 +289,32 @@ static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec) return idx; } -static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port) +static void gaokun_ucsi_handle_usb_mode(struct gaokun_ucsi_port *port) { struct gaokun_ucsi *uec = port->ucsi; - int idx = port->idx; - - if (idx >= uec->ucsi->cap.num_connectors) { + struct typec_mux_state state = {}; + struct typec_altmode dp_alt = {}; + int idx = port->idx, ret; + + /* + * For every typec port on this platform, the only mode-switch is + * controlled by its qmp combo phy which consumes svid and mode only. + */ + dp_alt.svid = port->svid; + state.mode = port->mode; + state.alt = &dp_alt; + + if (idx >= uec->num_ports) { dev_warn(uec->dev, "altmode port out of range: %d\n", idx); return; } + ret = typec_mux_set(port->typec_mux, &state); + if (ret) + dev_err(uec->dev, "failed to set mux %d\n", ret); + /* UCSI callback .connector_status() have set orientation */ - if (port->bridge) + if (port->bridge && port->svid == USB_TYPEC_DP_SID) drm_aux_hpd_bridge_notify(&port->bridge->dev, port->hpd_state ? connector_status_connected : @@ -307,7 +323,7 @@ static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port) gaokun_ec_ucsi_pan_ack(uec->ec, port->idx); } -static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec) +static void gaokun_ucsi_usb_notify_ind(struct gaokun_ucsi *uec) { int idx; @@ -320,7 +336,7 @@ static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec) if (idx == GAOKUN_UCSI_NO_PORT_UPDATE) gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */ else - gaokun_ucsi_handle_altmode(&uec->ports[idx]); + gaokun_ucsi_handle_usb_mode(&uec->ports[idx]); } /* @@ -352,7 +368,7 @@ static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx) port = &uec->ports[idx]; if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) { dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT"); - gaokun_ucsi_altmode_notify_ind(uec); + gaokun_ucsi_usb_notify_ind(uec); } } @@ -366,7 +382,7 @@ static int gaokun_ucsi_notify(struct notifier_block *nb, switch (action) { case EC_EVENT_USB: gaokun_ucsi_complete_usb_ack(uec); - gaokun_ucsi_altmode_notify_ind(uec); + gaokun_ucsi_usb_notify_ind(uec); return NOTIFY_OK; case EC_EVENT_UCSI: @@ -429,8 +445,15 @@ static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec) fwnode_handle_put(fwnode); return PTR_ERR(ucsi_port->bridge); } - } + ucsi_port->typec_mux = fwnode_typec_mux_get(fwnode); + if (IS_ERR(ucsi_port->typec_mux)) { + fwnode_handle_put(fwnode); + return dev_err_probe(dev, PTR_ERR(ucsi_port->typec_mux), + "failed to acquire mode-switch for port: %d\n", + port); + } + } for (i = 0; i < num_ports; i++) { if (!uec->ports[i].bridge) continue; @@ -502,10 +525,14 @@ static int gaokun_ucsi_probe(struct auxiliary_device *adev, static void gaokun_ucsi_remove(struct auxiliary_device *adev) { struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev); + int i; disable_delayed_work_sync(&uec->work); 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); + ucsi_destroy(uec->ucsi); } diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index 838ac0185082..848ed459a6de 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -737,8 +737,8 @@ static const struct of_device_id __maybe_unused ucsi_stm32g0_typec_of_match[] = MODULE_DEVICE_TABLE(of, ucsi_stm32g0_typec_of_match); static const struct i2c_device_id ucsi_stm32g0_typec_i2c_devid[] = { - { "stm32g0-typec" }, - {} + { .name = "stm32g0-typec" }, + { } }; MODULE_DEVICE_TABLE(i2c, ucsi_stm32g0_typec_i2c_devid); diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c index 0187c1c4b21a..1be18d101842 100644 --- a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c +++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c @@ -89,7 +89,8 @@ static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command) static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, - void *data, size_t size) + void *data, size_t size, + void *msg_out, size_t msg_out_size) { int ret; @@ -126,7 +127,8 @@ static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi, return 0; } - ret = ucsi_sync_control_common(ucsi, command, cci, data, size); + ret = ucsi_sync_control_common(ucsi, command, cci, + data, size, msg_out, msg_out_size); if (ret < 0) return ret; diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig index b9f94e2e278d..50945b6fae1a 100644 --- a/drivers/usb/usbip/Kconfig +++ b/drivers/usb/usbip/Kconfig @@ -40,7 +40,7 @@ config USBIP_VHCI_HC_PORTS config USBIP_VHCI_NR_HCS int "Number of USB/IP virtual host controllers" - range 1 128 + range 1 32 default 1 depends on USBIP_VHCI_HCD help diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index b98d14c43d13..a7ede6fb3da9 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -59,6 +59,29 @@ static void port_show_vhci(char **out, int hub, int port, struct vhci_device *vd *out += sprintf(*out, "\n"); } +static ssize_t status_show_not_ready(int pdev_nr, char *out) +{ + char *s = out; + int i = 0; + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "hs %04u %03u ", + (pdev_nr * VHCI_PORTS) + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "ss %04u %03u ", + (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + return out - s; +} + /* Sysfs entry to show port status */ static ssize_t status_show_vhci(int pdev_nr, char *out) { @@ -76,6 +99,12 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) } hcd = platform_get_drvdata(pdev); + + if (!hcd) { + usbip_dbg_vhci_sysfs("show status error (hcd is NULL)\n"); + return status_show_not_ready(pdev_nr, out); + } + vhci_hcd = hcd_to_vhci_hcd(hcd); vhci = vhci_hcd->vhci; @@ -104,29 +133,6 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) return out - s; } -static ssize_t status_show_not_ready(int pdev_nr, char *out) -{ - char *s = out; - int i = 0; - - for (i = 0; i < VHCI_HC_PORTS; i++) { - out += sprintf(out, "hs %04u %03u ", - (pdev_nr * VHCI_PORTS) + i, - VDEV_ST_NOTASSIGNED); - out += sprintf(out, "000 00000000 0000000000000000 0-0"); - out += sprintf(out, "\n"); - } - - for (i = 0; i < VHCI_HC_PORTS; i++) { - out += sprintf(out, "ss %04u %03u ", - (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i, - VDEV_ST_NOTASSIGNED); - out += sprintf(out, "000 00000000 0000000000000000 0-0"); - out += sprintf(out, "\n"); - } - return out - s; -} - static int status_name_to_id(const char *name) { char *c; diff --git a/include/linux/platform_data/pxa2xx_udc.h b/include/linux/platform_data/pxa2xx_udc.h index bc99cc6a3c5f..c1e4d03bae2c 100644 --- a/include/linux/platform_data/pxa2xx_udc.h +++ b/include/linux/platform_data/pxa2xx_udc.h @@ -10,21 +10,6 @@ #ifndef PXA2XX_UDC_H #define PXA2XX_UDC_H -struct pxa2xx_udc_mach_info { - int (*udc_is_connected)(void); /* do we see host? */ - void (*udc_command)(int cmd); -#define PXA2XX_UDC_CMD_CONNECT 0 /* let host see us */ -#define PXA2XX_UDC_CMD_DISCONNECT 1 /* so host won't see us */ - - /* Boards following the design guidelines in the developer's manual, - * with on-chip GPIOs not Lubbock's weird hardware, can have a sane - * VBUS IRQ and omit the methods above. Store the GPIO number - * here. Note that sometimes the signals go through inverters... - */ - bool gpio_pullup_inverted; - int gpio_pullup; /* high == pullup activated */ -}; - #ifdef CONFIG_PXA27x extern void pxa27x_clear_otgph(void); #else diff --git a/include/linux/platform_data/usb-xhci-prom21.h b/include/linux/platform_data/usb-xhci-prom21.h new file mode 100644 index 000000000000..ee672ad452a8 --- /dev/null +++ b/include/linux/platform_data/usb-xhci-prom21.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * AMD Promontory 21 xHCI auxiliary device platform data. + * + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com> + */ + +#ifndef _LINUX_PLATFORM_DATA_USB_XHCI_PROM21_H +#define _LINUX_PLATFORM_DATA_USB_XHCI_PROM21_H + +#include <linux/compiler_types.h> +#include <linux/types.h> + +struct pci_dev; + +struct prom21_xhci_pdata { + struct pci_dev *pdev; + void __iomem *regs; + resource_size_t rsrc_len; +}; + +#endif diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 0ba112175bb3..feb1af175cfd 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -13,6 +13,7 @@ #include <linux/types.h> +struct config_group; struct fwnode_handle; struct device; @@ -153,6 +154,9 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block, ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, size_t block_len); struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir); +int tb_property_merge_dir(struct tb_property_dir *parent, + const struct tb_property_dir *dir, + bool replace); struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid); void tb_property_free_dir(struct tb_property_dir *dir); int tb_property_add_immediate(struct tb_property_dir *parent, const char *key, @@ -209,6 +213,8 @@ enum tb_link_width { * @link_width: Width of the downstream facing link * @link_usb4: Downstream link is USB4 * @is_unplugged: The XDomain is unplugged + * @removing: Set by tb_xdomain_remove() under @lock to prevent + * concurrent delayed work queueing * @needs_uuid: If the XDomain does not have @remote_uuid it will be * queried first * @service_ids: Used to generate IDs for the services @@ -228,6 +234,7 @@ enum tb_link_width { * changed notification * @bonding_possible: True if lane bonding is possible on local side * @target_link_width: Target link width from the remote host + * @ntunnels: Keeps track of how many tunnels go through this XDomain * @link: Root switch link the remote domain is connected (ICM only) * @depth: Depth in the chain the remote domain is connected (ICM only) * @@ -257,6 +264,7 @@ struct tb_xdomain { enum tb_link_width link_width; bool link_usb4; bool is_unplugged; + bool removing; bool needs_uuid; struct ida service_ids; struct ida in_hopids; @@ -273,6 +281,7 @@ struct tb_xdomain { int properties_changed_retries; bool bonding_possible; u8 target_link_width; + atomic_t ntunnels; u8 link; u8 depth; }; @@ -392,6 +401,10 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler); * @prtcvers: Protocol version from the properties directory * @prtcrevs: Protocol software revision from the properties directory * @prtcstns: Protocol settings mask from the properties directory + * @lock: Protects this structure + * @local_properties: Properties owned by the service driver + * @remote_properties: Properties read from the remote service. These + * are read-only. * @debugfs_dir: Pointer to the service debugfs directory. Always created * when debugfs is enabled. Can be used by service drivers to * add their own entries under the service. @@ -399,6 +412,9 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler); * Each domain exposes set of services it supports as collection of * properties. For each service there will be one corresponding * &struct tb_service. Service drivers are bound to these. + * + * Service drivers can add their own dynamic properties to + * @local_properties but whenever they do so @lock must be held. */ struct tb_service { struct device dev; @@ -408,6 +424,9 @@ struct tb_service { u32 prtcvers; u32 prtcrevs; u32 prtcstns; + struct mutex lock; + struct tb_property_dir *local_properties; + struct tb_property_dir *remote_properties; struct dentry *debugfs_dir; }; @@ -476,16 +495,17 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) return tb_to_xdomain(svc->dev.parent); } +void tb_service_properties_changed(struct tb_service *svc); + /** * struct tb_nhi - thunderbolt native host interface * @lock: Must be held during ring creation/destruction. Is acquired by * interrupt_work when dispatching interrupts to individual rings. - * @pdev: Pointer to the PCI device + * @dev: Device associated with this NHI instance * @ops: NHI specific optional ops * @iobase: MMIO space of the NHI * @tx_rings: All Tx rings available on this host controller * @rx_rings: All Rx rings available on this host controller - * @msix_ida: Used to allocate MSI-X vectors for rings * @going_away: The host controller device is about to disappear so when * this flag is set, avoid touching the hardware anymore. * @iommu_dma_protection: An IOMMU will isolate external-facing ports. @@ -493,20 +513,21 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * MSI-X is used. * @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 */ struct tb_nhi { spinlock_t lock; - struct pci_dev *pdev; + struct device *dev; const struct tb_nhi_ops *ops; void __iomem *iobase; struct tb_ring **tx_rings; struct tb_ring **rx_rings; - struct ida msix_ida; bool going_away; bool iommu_dma_protection; struct work_struct interrupt_work; u32 hop_count; unsigned long quirks; + struct completion domain_released; }; /** @@ -535,6 +556,9 @@ struct tb_nhi { * @start_poll: Called when ring interrupt is triggered to start * polling. Passing %NULL keeps the ring in interrupt mode. * @poll_data: Data passed to @start_poll + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) + * @wait: Used to signal that the ring may be empty now */ struct tb_ring { spinlock_t lock; @@ -558,6 +582,8 @@ struct tb_ring { u16 eof_mask; void (*start_poll)(void *data); void *poll_data; + unsigned int interval_nsec; + wait_queue_head_t wait; }; /* Leave ring interrupt enabled on suspend */ @@ -609,7 +635,20 @@ struct ring_frame { }; /* Minimum size for ring_rx */ -#define TB_FRAME_SIZE 0x100 +#define TB_FRAME_SIZE 256 +#define TB_MAX_FRAME_SIZE 4096 + +static inline size_t tb_ring_frame_size(const struct ring_frame *frame) +{ + if (frame->size) + return frame->size; + return TB_MAX_FRAME_SIZE; +} + +static inline size_t tb_ring_size(const struct tb_ring *ring) +{ + return ring->size; +} struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size, unsigned int flags); @@ -618,6 +657,7 @@ struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size, u16 sof_mask, u16 eof_mask, void (*start_poll)(void *), void *poll_data); void tb_ring_start(struct tb_ring *ring); +bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec); void tb_ring_stop(struct tb_ring *ring); void tb_ring_free(struct tb_ring *ring); @@ -670,6 +710,8 @@ static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame) struct ring_frame *tb_ring_poll(struct tb_ring *ring); void tb_ring_poll_complete(struct tb_ring *ring); +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec); + /** * tb_ring_dma_device() - Return device used for DMA mapping * @ring: Ring whose DMA device is retrieved @@ -681,12 +723,17 @@ void tb_ring_poll_complete(struct tb_ring *ring); */ static inline struct device *tb_ring_dma_device(struct tb_ring *ring) { - return &ring->nhi->pdev->dev; + return ring->nhi->dev; } bool usb4_usb3_port_match(struct device *usb4_port_dev, const struct fwnode_handle *usb3_port_fwnode); +#if IS_REACHABLE(CONFIG_CONFIGFS_FS) +int tb_configfs_register_group(struct config_group *group); +void tb_configfs_unregister_group(struct config_group *group); +#endif + #else /* CONFIG_USB4 */ static inline bool usb4_usb3_port_match(struct device *usb4_port_dev, const struct fwnode_handle *usb3_port_fwnode) |
