summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-25 10:44:46 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-25 10:44:46 -0700
commitf4d50813c0958b7d6bc79a7e0a77193372cbfd03 (patch)
treeb911078680e9c1f0f680ebbff232382dcc8fb32b
parent29b0977977d7b77b192e434d1a94628cf006f795 (diff)
parente1e6e541c5c9cf548e9fdc35fc26808c82074440 (diff)
Merge tag 'usb-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB / Thunderbolt updates from Greg KH: "Here is the big set of USB and Thunderbolt driver updates for 7.3-rc1. Lots of driver work for new devices and systems, and many other minor fixes and updates. Included in here are: - Thunderbolt subsystem driver updates and additions - typec driver updates and additions - usb gadget fixes all over the place, seems like people are finally paying attention to these drivers for some reason - xhci driver updates and fixes based on lots of reports - usb-serial driver updates and additions - new device ids - other minor USB driver updates and fixes All of these have been in linux-next for a while with no reported issues" * tag 'usb-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (163 commits) usb: gadget: uvc: fix dangling pointers in uvc_function_bind() and uvc_function_unbind() usb: typec: hd3ss3220: fix VBUS regulator error message usb: usbfs: fix use-after-free of usb_device in usbdev_release() usb: gadget: u_audio: Fix use-after-free on sound card disconnect usb: dwc3: gadget: Fix use-after-free in dwc3_gadget_free_endpoints due to race condition usb: gadget: f_tcm: keep port count until LUN teardown completes usb: usbtest: disable dynamic ID support usb: typec: tcpci: pass correct rx_type to tcpm_pd_receive() USB: c67x00: fix use-after-free in c67x00_add_iso_urb() usb: typec: ucsi: use UCSI_TIMEOUT_MS for sync command completion usb: gadget: snps_udc_plat: clean up PHY on probe deferral usb: gadget: f_tcm: fix deadlock in usbg_make_tpg() usb: dwc2: gadget: Exit partial power down state when changing USB pull-up usb: gadget: f_fs: Fix Use-After-Free in AIO error path usb: gadget: f_fs: Prevent deadlock during ep0 read loop usb: gadget: at91_udc: drain polled-VBUS timer/work before udc is freed usb: gadget: midi2: remove default configfs groups on teardown usb: gadget: uvc: Fix null pointer dereference in uvcg_video_init() usb: typec: thunderbolt: Disable work before freeing tbt on remove usb: xhci: Handle bogus TRB pointers in Missed Service Error events ...
-rw-r--r--Documentation/ABI/obsolete/sysfs-bus-usb2
-rw-r--r--Documentation/ABI/testing/configfs-thunderbolt_stream15
-rw-r--r--Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy3
-rw-r--r--Documentation/ABI/testing/sysfs-bus-usb2
-rw-r--r--Documentation/admin-guide/thunderbolt.rst4
-rw-r--r--Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml93
-rw-r--r--Documentation/devicetree/bindings/usb/cdns,usb3.yaml3
-rw-r--r--Documentation/devicetree/bindings/usb/generic-ehci.yaml7
-rw-r--r--Documentation/devicetree/bindings/usb/generic-ohci.yaml5
-rw-r--r--Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml6
-rw-r--r--Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml9
-rw-r--r--Documentation/devicetree/bindings/usb/renesas,usbhs.yaml1
-rw-r--r--Documentation/devicetree/bindings/usb/ti,tps6598x.yaml6
-rw-r--r--Documentation/translations/zh_CN/usb/CREDITS147
-rw-r--r--Documentation/translations/zh_CN/usb/acm.rst62
-rw-r--r--Documentation/translations/zh_CN/usb/authorization.rst79
-rw-r--r--Documentation/translations/zh_CN/usb/chipidea.rst63
-rw-r--r--Documentation/translations/zh_CN/usb/dwc3.rst37
-rw-r--r--Documentation/translations/zh_CN/usb/ehci.rst247
-rw-r--r--Documentation/translations/zh_CN/usb/index.rst18
-rw-r--r--Documentation/translations/zh_CN/usb/usbmon.rst362
-rw-r--r--Documentation/usb/functionfs.rst80
-rw-r--r--Documentation/usb/gadget-testing.rst3
-rw-r--r--drivers/media/usb/as102/as102_usb_drv.c73
-rw-r--r--drivers/net/thunderbolt/main.c4
-rw-r--r--drivers/net/usb/pegasus.c54
-rw-r--r--drivers/net/usb/pegasus.h3
-rw-r--r--drivers/net/wireless/ath/ath9k/hif_usb.c12
-rw-r--r--drivers/net/wireless/ath/ath9k/hif_usb.h2
-rw-r--r--drivers/target/target_core_fabric_configfs.c8
-rw-r--r--drivers/thunderbolt/debugfs.c28
-rw-r--r--drivers/thunderbolt/dma_test.c4
-rw-r--r--drivers/thunderbolt/domain.c30
-rw-r--r--drivers/thunderbolt/nhi.c55
-rw-r--r--drivers/thunderbolt/nhi.h21
-rw-r--r--drivers/thunderbolt/nhi_regs.h4
-rw-r--r--drivers/thunderbolt/pci.c54
-rw-r--r--drivers/thunderbolt/stream.c260
-rw-r--r--drivers/thunderbolt/switch.c11
-rw-r--r--drivers/thunderbolt/tb.c21
-rw-r--r--drivers/thunderbolt/tb.h1
-rw-r--r--drivers/thunderbolt/tunnel.c13
-rw-r--r--drivers/thunderbolt/xdomain.c4
-rw-r--r--drivers/usb/atm/xusbatm.c6
-rw-r--r--drivers/usb/c67x00/c67x00-sched.c4
-rw-r--r--drivers/usb/cdns3/cdns3-plat.c4
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c2
-rw-r--r--drivers/usb/class/usbtmc.c2
-rw-r--r--drivers/usb/core/config.c2
-rw-r--r--drivers/usb/core/devices.c7
-rw-r--r--drivers/usb/core/devio.c15
-rw-r--r--drivers/usb/core/driver.c12
-rw-r--r--drivers/usb/core/hub.c17
-rw-r--r--drivers/usb/dwc2/core_intr.c3
-rw-r--r--drivers/usb/dwc2/gadget.c11
-rw-r--r--drivers/usb/dwc3/core.c99
-rw-r--r--drivers/usb/dwc3/core.h7
-rw-r--r--drivers/usb/dwc3/dwc3-am62.c13
-rw-r--r--drivers/usb/dwc3/dwc3-qcom.c2
-rw-r--r--drivers/usb/dwc3/dwc3-xilinx.c18
-rw-r--r--drivers/usb/dwc3/gadget.c16
-rw-r--r--drivers/usb/dwc3/host.c5
-rw-r--r--drivers/usb/gadget/configfs.c8
-rw-r--r--drivers/usb/gadget/function/f_fs.c163
-rw-r--r--drivers/usb/gadget/function/f_mass_storage.c3
-rw-r--r--drivers/usb/gadget/function/f_midi2.c2
-rw-r--r--drivers/usb/gadget/function/f_tcm.c25
-rw-r--r--drivers/usb/gadget/function/f_uac1.c13
-rw-r--r--drivers/usb/gadget/function/f_uac1_legacy.c56
-rw-r--r--drivers/usb/gadget/function/f_uac2.c13
-rw-r--r--drivers/usb/gadget/function/f_uvc.c7
-rw-r--r--drivers/usb/gadget/function/u_audio.c36
-rw-r--r--drivers/usb/gadget/function/u_fs.h8
-rw-r--r--drivers/usb/gadget/function/u_uac1_legacy.h3
-rw-r--r--drivers/usb/gadget/function/uvc_video.c2
-rw-r--r--drivers/usb/gadget/legacy/inode.c17
-rw-r--r--drivers/usb/gadget/legacy/nokia.c2
-rw-r--r--drivers/usb/gadget/udc/Kconfig2
-rw-r--r--drivers/usb/gadget/udc/aspeed_udc.c50
-rw-r--r--drivers/usb/gadget/udc/at91_udc.c20
-rw-r--r--drivers/usb/gadget/udc/fsl_qe_udc.c8
-rw-r--r--drivers/usb/gadget/udc/pch_udc.c1
-rw-r--r--drivers/usb/gadget/udc/r8a66597-udc.c1
-rw-r--r--drivers/usb/gadget/udc/snps_udc_plat.c5
-rw-r--r--drivers/usb/host/fsl-mph-dr-of.c7
-rw-r--r--drivers/usb/host/ohci-dbg.c9
-rw-r--r--drivers/usb/host/xhci-caps.h41
-rw-r--r--drivers/usb/host/xhci-dbgtty.c25
-rw-r--r--drivers/usb/host/xhci-debugfs.c3
-rw-r--r--drivers/usb/host/xhci-histb.c2
-rw-r--r--drivers/usb/host/xhci-hub.c6
-rw-r--r--drivers/usb/host/xhci-mem.c7
-rw-r--r--drivers/usb/host/xhci-mtk.c4
-rw-r--r--drivers/usb/host/xhci-pci.c4
-rw-r--r--drivers/usb/host/xhci-plat.c2
-rw-r--r--drivers/usb/host/xhci-ring.c492
-rw-r--r--drivers/usb/host/xhci-tegra.c5
-rw-r--r--drivers/usb/host/xhci.c43
-rw-r--r--drivers/usb/host/xhci.h21
-rw-r--r--drivers/usb/misc/cypress_cy7c63.c8
-rw-r--r--drivers/usb/misc/usb-ljca.c3
-rw-r--r--drivers/usb/misc/usbtest.c1
-rw-r--r--drivers/usb/misc/uss720.c1
-rw-r--r--drivers/usb/mtu3/mtu3_core.c11
-rw-r--r--drivers/usb/mtu3/mtu3_host.c26
-rw-r--r--drivers/usb/mtu3/mtu3_plat.c3
-rw-r--r--drivers/usb/musb/Kconfig1
-rw-r--r--drivers/usb/musb/musb_host.c14
-rw-r--r--drivers/usb/phy/phy-fsl-usb.c52
-rw-r--r--drivers/usb/phy/phy-fsl-usb.h6
-rw-r--r--drivers/usb/renesas_usbhs/common.c6
-rw-r--r--drivers/usb/serial/digi_acceleport.c334
-rw-r--r--drivers/usb/serial/keyspan_pda.c1
-rw-r--r--drivers/usb/serial/metro-usb.c31
-rw-r--r--drivers/usb/serial/option.c20
-rw-r--r--drivers/usb/serial/pl2303.c108
-rw-r--r--drivers/usb/serial/pl2303.h2
-rw-r--r--drivers/usb/serial/spcp8x5.c19
-rw-r--r--drivers/usb/typec/altmodes/Kconfig1
-rw-r--r--drivers/usb/typec/altmodes/displayport.c18
-rw-r--r--drivers/usb/typec/altmodes/thunderbolt.c6
-rw-r--r--drivers/usb/typec/class.c71
-rw-r--r--drivers/usb/typec/hd3ss3220.c2
-rw-r--r--drivers/usb/typec/mux/tusb1046.c1
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c12
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c634
-rw-r--r--drivers/usb/typec/tipd/core.c85
-rw-r--r--drivers/usb/typec/tipd/tps6598x.h10
-rw-r--r--drivers/usb/typec/ucsi/debugfs.c5
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c21
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h1
-rw-r--r--drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c15
-rw-r--r--include/linux/thunderbolt.h10
-rw-r--r--include/linux/usb.h3
-rw-r--r--include/linux/usb/pd.h53
-rw-r--r--include/linux/usb/tcpci.h1
-rw-r--r--include/linux/usb/typec.h1
-rw-r--r--include/target/target_core_fabric.h2
-rw-r--r--include/uapi/linux/usb/functionfs.h24
-rw-r--r--rust/kernel/usb.rs35
140 files changed, 3149 insertions, 1786 deletions
diff --git a/Documentation/ABI/obsolete/sysfs-bus-usb b/Documentation/ABI/obsolete/sysfs-bus-usb
index bd096d33fbc7..067016e62e11 100644
--- a/Documentation/ABI/obsolete/sysfs-bus-usb
+++ b/Documentation/ABI/obsolete/sysfs-bus-usb
@@ -26,6 +26,6 @@ Description:
initializes all non-hub devices in the "on" level. Some
drivers may change this setting when they are bound.
- This file is deprecated and will be removed after 2010.
+ This file is deprecated.
Use the power/control file instead; it does exactly the
same thing.
diff --git a/Documentation/ABI/testing/configfs-thunderbolt_stream b/Documentation/ABI/testing/configfs-thunderbolt_stream
index 7abc6b73a1e4..cbecb3d8db50 100644
--- a/Documentation/ABI/testing/configfs-thunderbolt_stream
+++ b/Documentation/ABI/testing/configfs-thunderbolt_stream
@@ -27,6 +27,21 @@ Description:
default values. If there is an advertised remote stream
with the same name, uses its values as the default.
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/busy_poll
+Date: Nov 2026
+KernelVersion: v7.3
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ Instead of using interrupts for completing the frames in
+ the TX/RX rings, busy poll them directly from the
+ read(2) and write(2) calls. This burns more CPU cycles
+ but provides lower latency for applications that need it.
+
+ This also makes poll(2) return EPOLLERR because
+ interrupts do not provide wakeup anymore. Likewise a
+ blocking read(2) without available data busy-spins until
+ data arrives or a signal is received.
+
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/index
Date: Sep 2026
KernelVersion: v7.2
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy
index b2eaefd9bc49..6a681d219f43 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy
@@ -5,8 +5,5 @@ Description:
The attributes:
audio_buf_size - audio buffer size
- fn_cap - capture pcm device file name
- fn_cntl - control device file name
- fn_play - playback pcm device file name
req_buf_size - ISO OUT endpoint request buffer size
req_count - ISO OUT endpoint request count
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb
index af9b653422f1..baf4bf6d32ee 100644
--- a/Documentation/ABI/testing/sysfs-bus-usb
+++ b/Documentation/ABI/testing/sysfs-bus-usb
@@ -293,7 +293,7 @@ Description:
Supported values are 0 - 15.
More information on how besl values map to microseconds can be found in
- USB 2.0 ECN Errata for Link Power Management, section 4.10)
+ USB 2.0 ECN Errata for Link Power Management, section 4.10
What: /sys/bus/usb/devices/.../rx_lanes
Date: March 2018
diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst
index 91a6cb109988..ff25fe853706 100644
--- a/Documentation/admin-guide/thunderbolt.rst
+++ b/Documentation/admin-guide/thunderbolt.rst
@@ -294,8 +294,8 @@ for the retimers::
This enumerates and adds the on-board retimers. Now retimer NVM can be
upgraded in the same way than with cable connected (see previous
-section). However, the retimer is not disconnected as we are offline
-mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for
+section). However, the retimer is not disconnected as we are in offline
+mode, so after writing ``1`` to ``nvm_authenticate`` one should wait for
5 or more seconds before running rescan again::
# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan
diff --git a/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml b/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml
new file mode 100644
index 000000000000..fff5a200f8c7
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/aspeed,dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Aspeed SuperSpeed DWC3 USB SoC controller
+
+maintainers:
+ - Ryan Chen <ryan_chen@aspeedtech.com>
+
+description:
+ The common content of the node is defined in snps,dwc3.yaml.
+
+select:
+ properties:
+ compatible:
+ contains:
+ const: aspeed,ast2700-xhci
+ required:
+ - compatible
+
+properties:
+ compatible:
+ items:
+ - const: aspeed,ast2700-xhci
+ - const: snps,dwc3
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: Controller bus early clock
+ - description: PHY reference clock
+ - description: Controller suspend clock
+
+ clock-names:
+ items:
+ - const: bus_early
+ - const: ref
+ - const: suspend
+
+ resets:
+ maxItems: 1
+
+ phys:
+ maxItems: 1
+
+ phy-names:
+ const: usb3-phy
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - resets
+ - phys
+ - phy-names
+
+allOf:
+ - $ref: snps,dwc3.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/aspeed,ast2700-scu.h>
+ #include <dt-bindings/reset/aspeed,ast2700-scu.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ usb@12030000 {
+ compatible = "aspeed,ast2700-xhci", "snps,dwc3";
+ reg = <0x0 0x12030000 0x0 0x10000>;
+ interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>,
+ <&syscon0 SCU0_CLK_U2PHY_REFCLK>,
+ <&syscon0 SCU0_CLK_U2PHY_CLK12M>;
+ clock-names = "bus_early", "ref", "suspend";
+ resets = <&syscon0 SCU0_RESET_PORTA_XHCI>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb3axh_default &pinctrl_usb2axh_default>;
+ phys = <&uphy3a>;
+ phy-names = "usb3-phy";
+ dr_mode = "host";
+ };
+ };
diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml
index e8082c5c05a2..c14a88aad31e 100644
--- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml
+++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml
@@ -50,6 +50,9 @@ properties:
- const: otg
- const: wakeup
+ iommus:
+ maxItems: 1
+
port:
$ref: /schemas/graph.yaml#/properties/port
description:
diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml
index 55a5aa7d7a54..ae9fb70d0212 100644
--- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml
+++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml
@@ -98,6 +98,10 @@ properties:
- if a USB DRD channel: first clock should be host and second
one should be peripheral
+ clock-names:
+ minItems: 1
+ maxItems: 4
+
power-domains:
maxItems: 1
@@ -186,6 +190,9 @@ allOf:
required:
- clocks
- clock-names
+ else:
+ properties:
+ clock-names: false
unevaluatedProperties: false
diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml
index d42f448fa204..322808aaa283 100644
--- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml
+++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml
@@ -83,6 +83,10 @@ properties:
- if a USB DRD channel: first clock should be host and second
one should be peripheral
+ clock-names:
+ minItems: 1
+ maxItems: 4
+
power-domains:
maxItems: 1
@@ -182,6 +186,7 @@ allOf:
else:
properties:
+ clock-names: false
atmel,vbus-gpio: false
atmel,oc-gpio: false
diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml
index 21fc6bbe954f..d148e938d647 100644
--- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml
+++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml
@@ -28,6 +28,7 @@ properties:
- mediatek,mt8188-mtu3
- mediatek,mt8192-mtu3
- mediatek,mt8195-mtu3
+ - mediatek,mt8196-mtu3
- mediatek,mt8365-mtu3
- const: mediatek,mtu3
@@ -200,7 +201,10 @@ properties:
103 - used by mt8195, IP0, specific 1.03;
105 - used by mt8195, IP2, specific 1.05;
106 - used by mt8195, IP3, specific 1.06;
- enum: [1, 2, 101, 102, 103, 105, 106]
+ 107 - used by mt8196, IP0, specific 1.07;
+ 108 - used by mt8196, IP1, specific 1.08;
+ 109 - used by mt8196, IP2, specific 1.09;
+ enum: [1, 2, 101, 102, 103, 105, 106, 107, 108, 109]
mediatek,u3p-dis-msk:
$ref: /schemas/types.yaml#/definitions/uint32
diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml
index d99af9f413d0..ea60f7220afe 100644
--- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml
+++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml
@@ -29,6 +29,7 @@ properties:
- qcom,glymur-dwc3-mp
- qcom,ipq4019-dwc3
- qcom,ipq5018-dwc3
+ - qcom,ipq5210-dwc3
- qcom,ipq5332-dwc3
- qcom,ipq5424-dwc3
- qcom,ipq6018-dwc3
@@ -36,6 +37,7 @@ properties:
- qcom,ipq8074-dwc3
- qcom,ipq9574-dwc3
- qcom,kaanapali-dwc3
+ - qcom,maili-dwc3
- qcom,milos-dwc3
- qcom,msm8953-dwc3
- qcom,msm8994-dwc3
@@ -60,6 +62,7 @@ properties:
- qcom,sdx55-dwc3
- qcom,sdx65-dwc3
- qcom,sdx75-dwc3
+ - qcom,shikra-dwc3
- qcom,sm4250-dwc3
- qcom,sm6115-dwc3
- qcom,sm6125-dwc3
@@ -203,9 +206,11 @@ allOf:
compatible:
contains:
enum:
+ - qcom,ipq5210-dwc3
- qcom,ipq5424-dwc3
- qcom,ipq9574-dwc3
- qcom,kaanapali-dwc3
+ - qcom,maili-dwc3
- qcom,msm8953-dwc3
- qcom,msm8996-dwc3
- qcom,msm8998-dwc3
@@ -218,6 +223,7 @@ allOf:
- qcom,sdx55-dwc3
- qcom,sdx65-dwc3
- qcom,sdx75-dwc3
+ - qcom,shikra-dwc3
- qcom,sm6350-dwc3
- qcom,sm8750-dwc3
then:
@@ -497,6 +503,7 @@ allOf:
compatible:
contains:
enum:
+ - qcom,ipq5210-dwc3
- qcom,ipq5424-dwc3
- qcom,ipq9574-dwc3
then:
@@ -543,6 +550,7 @@ allOf:
- qcom,ipq4019-dwc3
- qcom,ipq8064-dwc3
- qcom,kaanapali-dwc3
+ - qcom,maili-dwc3
- qcom,qcs615-dwc3
- qcom,qcs8300-dwc3
- qcom,qdu1000-dwc3
@@ -556,6 +564,7 @@ allOf:
- qcom,sdx55-dwc3
- qcom,sdx65-dwc3
- qcom,sdx75-dwc3
+ - qcom,shikra-dwc3
- qcom,sm6350-dwc3
- qcom,sm6375-dwc3
- qcom,sm8150-dwc3
diff --git a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml
index dc74e70f1b92..13715b7c94c8 100644
--- a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml
+++ b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml
@@ -27,6 +27,7 @@ properties:
- renesas,usbhs-r9a07g044 # RZ/G2{L,LC}
- renesas,usbhs-r9a07g054 # RZ/V2L
- renesas,usbhs-r9a08g045 # RZ/G3S
+ - renesas,usbhs-r9a08g046 # RZ/G3L
- renesas,usbhs-r9a09g047 # RZ/G3E
- renesas,usbhs-r9a09g056 # RZ/V2N
- renesas,usbhs-r9a09g057 # RZ/V2H(P)
diff --git a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml
index 1745e28b3110..2c589e9e712e 100644
--- a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml
+++ b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml
@@ -4,13 +4,14 @@
$id: http://devicetree.org/schemas/usb/ti,tps6598x.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Texas Instruments 6598x Type-C Port Switch and Power Delivery controller
+title: Texas Instruments Type-C port switch and USB Power Delivery controllers
maintainers:
- Bryan O'Donoghue <bryan.odonoghue@linaro.org>
description: |
- Texas Instruments 6598x Type-C Port Switch and Power Delivery controller
+ Texas Instruments 6598x and 66993 Type-C Port Switch and Power Delivery
+ controller.
A variant of this controller known as Apple CD321x or Apple ACE is also
present on hardware with Apple SoCs such as the M1.
@@ -19,6 +20,7 @@ properties:
compatible:
enum:
- ti,tps6598x
+ - ti,tps66993
- apple,cd321x
- ti,tps25750
diff --git a/Documentation/translations/zh_CN/usb/CREDITS b/Documentation/translations/zh_CN/usb/CREDITS
index c133b1a5daff..ccfeced03ea6 100644
--- a/Documentation/translations/zh_CN/usb/CREDITS
+++ b/Documentation/translations/zh_CN/usb/CREDITS
@@ -10,12 +10,10 @@
:校译:
-简易 Linux USB 驱动的致谢名单:
+Simple Linux USB 驱动项目致谢名单:
-以下人员都为 Linux USB 驱动代码作出了贡献
-(按姓氏字母顺序排列)。我相信这份名单本应
-更长一些,但确实不容易维护。
-如需将自己加入名单,请提交补丁。
+以下人员都为 Linux USB 驱动代码作出过贡献(按姓氏字母顺序排列)。这份名
+单本该更长,只是确实不易维护;如果你也应列名其中,欢迎提交补丁把自己加上。
Georg Acher <acher@informatik.tu-muenchen.de>
David Brownell <dbrownell@users.sourceforge.net>
@@ -41,123 +39,124 @@
特别感谢:
Inaky Perez Gonzalez <inaky@peloncho.fis.ucm.es>
- 感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd
- 驱动中的大部分代码。我们从那项工作中学到了很多。
+ 感谢他牵头开发 Linux USB 驱动,并编写了 uusbd 驱动的大部分代码,我们
+ 从中学到了很多。
NetBSD 和 FreeBSD 的 USB 开发者们
感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。
-附加感谢:
- 还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助
- (摘自 Inaky 驱动原始的 THANKS 文件):
+另外还要感谢:
- 以下公司曾帮助我们开发 Linux USB / UUSBD:
+ 以下公司和个人在硬件、支持、时间和开发工作上给予了帮助(摘自 Inaky
+ 驱动原始的 THANKS 文件):
- - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面为我
- 提供支持。没想到能得到这么大的帮助。
+ 以下公司曾为 Linux USB / UUSBD 的开发提供帮助:
- - USAR Systems 向我们提供了他们出色的 USB 评估套件,
- 使我们能够测试 Linux USB 驱动对最新 USB 规范的符合性。
- USAR Systems 认识到保持开放操作系统与时俱进的重要性,
- 并以硬件支持这个项目。感谢!
+ - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面提供
+ 了大力支持。
+
+ - USAR Systems 向我们提供了出色的 USB 评估套件,使我们得以测试
+ Linux USB 驱动对最新 USB 规范的符合性。USAR Systems 也认识到,
+ 让开放操作系统跟上时代很重要,因此以硬件支持了这个项目,在此
+ 致谢。
- 感谢英特尔提供的宝贵帮助。
- 我们与 Cherry 合作,使 Linux 成为首个内置 USB 支持的操作系统。
Cherry 是全球最大的键盘制造商之一。
- - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI-to-USB
- 控制卡,用于测试 OHCI 实现。
+ - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI 转 USB 控制卡,
+ 用于测试 OHCI 实现。
- - 由于他们对我们的支持,Keytronic 可以放心,
- 他们的键盘能卖给至少 300 万 Linux 用户中的一部分。
+ - 有了他们的支持,Keytronic 可以确信,其键盘能够销售给至少 300 万
+ Linux 用户中的一部分。
- - ing büro h doran [http://www.ibhdoran.com]!
- 在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的
- (我自己做的那个相当糟糕 :))。现在我知道该去哪里买漂亮的 USB
- 配件了!
+ - 特别感谢 ing büro h doran [http://www.ibhdoran.com]。
+ 在欧洲,想给主板配一个 PC 背板 USB 连接器几乎是不可能的(我自己
+ 做的那个效果并不好)。现在我知道该去哪里购买合适的 USB 配件了。
- Genius Germany 捐赠了一只 USB 鼠标,用于测试鼠标启动协议;
- 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢!
+ 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro,在此致谢。
- - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB 驱动。
- AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux 在内的
- 所有操作系统平台开放。
+ - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB
+ 驱动。AVM 是主动式和被动式 ISDN 控制器及基于 CAPI 2.0 软件的领先
+ 制造商。AVM B1 的主动式设计对包括 Linux 在内的所有操作系统平台
+ 开放。
- - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,
- 使我们能够测试批量传输代码。
+ - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,使我们能够测试
+ 批量传输代码。
- 感谢 Logitech 捐赠了一只三轴 USB 鼠标。
- Logitech 负责设计、制造并销售各种人机接口设备,
- 在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的
- 控制设备方面拥有悠久历史和丰富经验。
+ Logitech 负责设计、制造并销售各种人机接口设备,在键盘、鼠标、轨迹球、
+ 摄像头、扬声器,以及面向游戏和专业用途的控制设备方面拥有悠久历史和
+ 丰富经验。
- 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、
- 摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户
- 能在自己喜欢的操作系统上获得支持,并让所有 Linux 用户都能使用
- Logitech 以及其他 USB 硬件。
+ 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、摇杆和
+ 扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户能在自己偏爱的
+ 操作系统上获得支持,并让所有 Linux 用户都能使用 Logitech 及其他
+ USB 硬件。
Logitech 也是 1999 年 2 月 11 日维也纳 Linux 大会的官方赞助商,
我们将在会上展示 Linux USB 工作的最新进展。
- - 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI 内部实现中
- 那些不为人知的角落。
+ - 感谢 CATC 提供 USB Inspector,帮助我们看到 UHCI 内部实现中的那些
+ 隐秘角落。
- 感谢 Entrega 为开发工作提供 PCI 转 USB 卡、集线器和转换器产品。
- - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,
- 让这个驱动得以写成。
+ - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,让
+ 这个驱动得以写成。
- - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,
- 以及驱动开发过程中的宝贵建议。
+ - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,以及驱动
+ 开发过程中的宝贵建议。
- 另外还要感谢以下个人(嘿,顺序不分先后 :))
+ 另外还要感谢以下个人(排名不分先后):
- - Oren Tirosh <orenti@hishome.net>,
- 他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。
+ - Oren Tirosh <orenti@hishome.net>
+ 他非常耐心地解答我反复提出的各种 USB 问题,并提供了许多有价值的
+ 想法。
- - Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>,
- 指出了致命 bug,并给出了宝贵建议。
+ - Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>
+ 指出了严重问题,并给出了宝贵建议。
- - Edmund Humemberger <ed@atnet.at>,他在公共关系与项目管理方面
- 为 Linux-USB 项目付出了巨大的努力。
+ - Edmund Humemberger <ed@atnet.at>,他在公共关系与项目管理方面为
+ Linux-USB 项目付出了巨大的努力。
- - Alberto Menegazzi <flash@flash.iol.it> 正在着手编写 UUSBD 文档,加油!
+ - Alberto Menegazzi <flash@flash.iol.it> 正在着手编写 UUSBD 文档。
- - Ric Klaren <ia_ric@cs.utwente.nl> 编写了很好的入门文档,
- 与 Alberto 的作品形成良性竞争:)。
+ - Ric Klaren <ia_ric@cs.utwente.nl> 编写了很好的入门文档,与
+ Alberto 的作品形成了良性互补。
- - Christian Groessler <cpg@aladdin.de>,感谢他在那些棘手细节上的帮助。
+ - Christian Groessler <cpg@aladdin.de>,感谢他在诸多复杂细节上的帮助。
- - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,
- 并提供了大量的改进意见。
+ - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,并提供了
+ 大量的改进意见。
- - Fernando Herrera <fherrera@eurielec.etsit.upm.es>
- 负责撰写、维护并不断补充那份期待已久、独一无二又精彩的
- UUSBD FAQ!太棒了!
+ - Fernando Herrera <fherrera@eurielec.etsit.upm.es> 负责撰写、维护并
+ 持续补充那份期待已久、内容翔实的 UUSBD FAQ。
- - Rasca Gmelch <thron@gmx.de> 重新启用了 raw 驱动,
- 指出了一些错误,并启动了 uusbd-utils 软件包。
+ - Rasca Gmelch <thron@gmx.de> 重新启用了 raw 驱动,指出了一些错误,并
+ 启动了 uusbd-utils 软件包。
- - Peter Dettori <dettori@ozy.dec.com>,像疯了一样挖掘 bug,
- 还提出了很多很酷的建议,太棒了!
+ - Peter Dettori <dettori@ozy.dec.com>,持续发现问题,并提出了许多
+ 有价值的建议。
- - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、
- MIT X 联盟、TeX 社区等等,谢谢你们!
+ - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、MIT X 联盟、
+ TeX 社区等,在此一并致谢。
- - 特别感谢 Richard Stallman 创造了 Emacs!
+ - 特别感谢 Richard Stallman 创造了 Emacs。
- - 感谢 linux-usb 邮件列表的所有成员,读了那么多邮件——不开玩笑了,
- 感谢你们提出的所有建议!
+ - 感谢 linux-usb 邮件列表的所有成员阅读了大量邮件,并提出了诸多
+ 建议。
- 感谢 USB Implementers Forum 成员们的帮助与支持。
- - Nathan Myers <ncm@cantrip.org>,感谢他的建议!
- (希望你喜欢 Cibeles 的派对。)
+ - Nathan Myers <ncm@cantrip.org>,感谢他的建议。(也希望你喜欢
+ Cibeles 的派对。)
- - 感谢 Linus Torvalds 创建、开发并管理 Linux。
+ - 感谢 Linus Torvalds 创立、开发并维护 Linux。
- Mike Smith、Craig Keithley、Thierry Giron 和 Janet Schank
- 感谢他们让我认识到标准 USB 集线器其实也没那么“标准”,
- 这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。
+ 感谢他们让我认识到,标准 USB 集线器其实一点也不“标准”;也正因
+ 如此,我们才能在标准集线器驱动中加入厂商特定的处理。
diff --git a/Documentation/translations/zh_CN/usb/acm.rst b/Documentation/translations/zh_CN/usb/acm.rst
index 51d6eb8f5660..b2e35787af45 100644
--- a/Documentation/translations/zh_CN/usb/acm.rst
+++ b/Documentation/translations/zh_CN/usb/acm.rst
@@ -20,33 +20,26 @@ Linux ACM 驱动 v0.16
0. 免责声明
~~~~~~~~~~~
-本程序是自由软件;你可以在自由软件基金会发布的
-GNU 通用公共许可证第 2 版,或者(按你的选择)
-任何后续版本的条款下重新发布和/或修改它。
+本程序是自由软件;你可以在自由软件基金会发布的 GNU 通用公共许可证第 2 版,
+或者(按你的选择)任何后续版本的条款下重新发布和/或修改它。
-发布本程序是希望它能发挥作用,但它不附带任何担保;
-甚至不包括对适销性或特定用途适用性的默示担保。
-详情见 GNU 通用公共许可证。
+发布本程序是希望它能发挥作用,但它不附带任何担保;甚至不包括对适销性或
+特定用途适用性的默示担保。详情见 GNU 通用公共许可证。
-你应该已经随本程序收到了 GNU 通用公共许可证的副本;
-如果没有,请致信:Free Software Foundation, Inc., 59
-Temple Place, Suite 330, Boston, MA 02111-1307 USA。
+你应该已经随本程序收到了 GNU 通用公共许可证的副本;如果没有,请参见
+COPYING 文件。
-如需联系作者,可发送电子邮件至 vojtech@suse.cz,
-或邮寄至:
-Vojtech Pavlik, Ucitelska 1576, Prague 8,
-182 00, Czech Republic。
+如需联系作者,可发送电子邮件至 vojtech@suse.cz,或邮寄至:
+Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00, Czech Republic。
-为方便起见,软件包中已附带 GNU 通用公共许可证
-第 2 版:见 COPYING 文件。
+为方便起见,软件包中已附带 GNU 通用公共许可证第 2 版:见 COPYING 文件。
1. 使用方法
~~~~~~~~~~~
-``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB
-通信设备类抽象控制模型(USB CDC ACM)规范的
-USB 调制解调器和 USB ISDN 终端适配器。
+``drivers/usb/class/cdc-acm.c`` 驱动适用于符合 USB 通信设备类抽象控制模型
+(USB CDC ACM)规范的 USB 调制解调器和 USB ISDN 终端适配器。
-许多调制解调器支持此驱动,以下是我所知道的一些型号:
+已知支持该驱动的调制解调器包括:
- 3Com OfficeConnect 56k
- 3Com Voice FaxModem Pro
@@ -56,17 +49,16 @@ USB 调制解调器和 USB ISDN 终端适配器。
- Compaq 56k FaxModem
- ELSA Microlink 56k
-我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用:
+已知有一款 ISDN 终端适配器可以配合 ACM 驱动使用:
- 3Com USR ISDN Pro TA
-一些手机也可以通过 USB 连接。
-我知道以下机型可以正常工作:
+一些手机也可以通过 USB 连接,已知可用的机型有:
- SonyEricsson K800i
-遗憾的是,许多调制解调器和大多数 ISDN TA
-都使用专有接口,因此无法与此驱动配合工作。
+遗憾的是,很多调制解调器和大多数 ISDN TA 都使用专有接口,因此无法配合该
+驱动工作。
购买前请先确认设备是否符合 ACM 规范。
要使用这些调制解调器,需要加载以下模块::
@@ -75,15 +67,13 @@ USB 调制解调器和 USB ISDN 终端适配器。
uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko
cdc-acm.ko
-之后就应该可以访问这些调制解调器了。
-应当可以使用 ``minicom``、``ppp`` 和 ``mgetty``
-与它们通信。
+之后就应该能访问这些调制解调器,并用 ``minicom``、``ppp`` 和
+``mgetty`` 与它们通信。
2. 验证驱动是否正常工作
~~~~~~~~~~~~~~~~~~~~~~~
-第一步是检查 ``/sys/kernel/debug/usb/devices``,
-其内容应该类似如下::
+第一步是查看 ``/sys/kernel/debug/usb/devices``,其内容应当类似下面这样::
T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0
@@ -112,11 +102,10 @@ USB 调制解调器和 USB ISDN 终端适配器。
E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
-这三行的存在很关键(以及 ``Cls=`` 字段里出现的
-``comm`` 和 ``data`` 类);它说明这是一个 ACM
-设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。
-如果只看到 ``Cls=ff(vend.)``,那就无能为力了:
-这说明你手上的设备使用的是厂商专有接口::
+关键是看这三行,再结合 ``Cls=`` 字段里出现的 ``comm`` 和 ``data`` 类,就
+能判断这是一台 ACM 设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。如果
+只看到 ``Cls=ff(vend.)``,那就说明这台设备使用的是厂商专有接口,ACM 驱动
+无法处理::
D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
@@ -142,6 +131,5 @@ USB 调制解调器和 USB ISDN 终端适配器。
usb.c: acm driver claimed interface c7b5f3f8
usb.c: acm driver claimed interface c7691fa0
-如果以上都正常,请启动 ``minicom``,
-把它配置为连接 ``ttyACM`` 设备,然后
-尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。
+如果这些都正常,请启动 ``minicom``,把它配置为连接到 ``ttyACM`` 设备,然后
+尝试输入 ``at``。如果返回 ``OK``,说明驱动工作正常。
diff --git a/Documentation/translations/zh_CN/usb/authorization.rst b/Documentation/translations/zh_CN/usb/authorization.rst
index 2aa311f6b967..e2ff2282bd03 100644
--- a/Documentation/translations/zh_CN/usb/authorization.rst
+++ b/Documentation/translations/zh_CN/usb/authorization.rst
@@ -10,34 +10,32 @@
:校译:
-=============================
-授权或禁止 USB 设备连接到系统
-=============================
+===========================
+允许或禁止 USB 设备接入系统
+===========================
版权 (C) 2007 Inaky Perez-Gonzalez
<inaky@linux.intel.com> 英特尔公司
-此功能允许你控制 USB 设备是否可以在系统中使用。
-借助它,你可以完全通过用户空间实现对 USB 设备的锁定。
+有了这项功能,你就可以控制 USB 设备是否允许在系统中使用,并把 USB 设备锁
+定机制完全放在用户空间实现。
-目前,当插入一个 USB 设备时,系统会对其进行配置,
-其接口会立即向用户开放。
-有了这项改动,只有在 root 授权设备完成配置后,
-设备才可被使用。
+目前,USB 设备一接入系统就会被立即配置,其接口也会立刻向用户开放。引入
+这项机制后,只有在 root 明确授权后,设备才会完成配置并允许使用。
用法
====
-授权设备接入::
+授权设备接入系统::
$ echo 1 > /sys/bus/usb/devices/DEVICE/authorized
-取消对设备的授权::
+取消设备授权::
$ echo 0 > /sys/bus/usb/devices/DEVICE/authorized
-将新连接到 ``hostX`` 的设备默认设为未授权(即锁定)::
+将连接到 ``hostX`` 的新设备默认设为未授权(即锁定)::
$ echo 0 > /sys/bus/usb/devices/usbX/authorized_default
@@ -45,15 +43,14 @@
$ echo 1 > /sys/bus/usb/devices/usbX/authorized_default
-默认情况下,所有 USB 设备都是授权的。
-向 ``authorized_default`` 属性写入 ``2`` 会使内核
-默认只授权连接到内部 USB 端口的设备。
+默认情况下,所有 USB 设备都是授权的。向 ``authorized_default`` 属性写入
+``2`` 会使内核默认只授权连接到内部 USB 端口的设备。
-系统锁定示例(比较粗糙)
-------------------------
+系统锁定示例(简化版)
+----------------------
-假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入
-(例如某台带有外露 USB 端口的自助服务终端)::
+假设你想做一个锁定机制,只允许 XYZ 类型的设备接入(例如一台带有外露 USB
+端口的自助终端)::
启动系统
rc.local ->
@@ -63,21 +60,18 @@
echo 0 > $host/authorized_default
done
-给 udev 挂一个脚本,用于处理新插入的 USB 设备::
+为 udev 配置一个脚本,用于处理新插入的 USB 设备::
if device_is_my_type $DEV
then
echo 1 > $device_path/authorized
- done
+ fi
-``device_is_my_type()`` 才是锁定方案真正见功夫的
-地方。仅仅检查 class、type 和 protocol 是否匹配
-某个值,是你能做出的最糟糕的安全验证之一;
-对想绕过它的人来说,这反而是最容易利用的方案。
-如果你需要真正安全的办法,那就该使用加密、
-证书认证之类的机制。把 USB 存储设备当作
-“钥匙”的一个简单例子可以是::
+锁定方案是否可靠,关键全在 ``device_is_my_type()`` 的实现。仅仅检查
+class、type 和 protocol 是否匹配,几乎是最差的一种安全校验方式;对想绕过
+它的人来说,这种做法反而最容易伪造。如果你真要做安全控制,就该使用加密、
+证书认证之类的机制。把 USB 存储设备当作“钥匙”的一个简单示例可以写成::
function device_is_my_type()
{
@@ -87,7 +81,7 @@
sum=$(md5sum /mntpoint/.signature)
if [ $sum = $(cat /etc/lockdown/keysum) ]
then
- echo "We are good, connected"
+ echo "验证通过,已连接"
umount /mntpoint
# 再做一些额外处理,让其他人也能使用它
else
@@ -96,17 +90,16 @@
}
-当然,这个例子很粗糙;真正要做的话,
-你会想用基于 PKI 的证书校验,这样就不必依赖
-共享密钥之类的东西。不过你应该已经明白意思了。
-任何拿到设备仿真工具包的人都能伪造描述符和设备信息。
-别信这个。
+当然,这个例子仍然比较简化。真正落地时,更合适的做法是使用基于 PKI 的证
+书校验,这样就不必依赖共享密钥之类的机制了。不过意思已经很清楚:任何拿到
+设备仿真工具包的人,都能伪造描述符和设备信息,所以别把这类检查当成真正
+的安全保障。
接口授权
--------
-也有类似的方法用于允许或拒绝特定 USB 接口。
-这使得你可以只阻止某个 USB 设备中的部分接口。
+也可以用类似的方法允许或拒绝特定的 USB 接口。这样一来,你只需要阻止某个
+USB 设备中的部分接口。
授权接口::
@@ -126,14 +119,12 @@
$ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default
-默认情况下,
-``interface_authorized_default`` 位为 ``1``,
-因此所有接口默认都处于已授权状态。
+默认情况下,``interface_authorized_default`` 位为 ``1``,因此所有接口默认
+都会处于授权状态。
注意:
- 如果把一个先前未授权的接口改为已授权,
- 则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe``
- 来手动触发驱动探测。
+ 如果把一个先前未授权的接口改为已授权,则必须通过将 ``INTERFACE`` 写入
+ ``/sys/bus/usb/drivers_probe`` 来手动触发驱动探测。
-对于需要多个接口的驱动程序,应先授权所有必需接口,
-然后再触发驱动探测。这样做可以避免副作用。
+对于需要多个接口的驱动程序,应先授权所有必需接口,然后再触发驱动探测。
+这样做可以避免副作用。
diff --git a/Documentation/translations/zh_CN/usb/chipidea.rst b/Documentation/translations/zh_CN/usb/chipidea.rst
index ea0dc3043189..ee5407a4ce44 100644
--- a/Documentation/translations/zh_CN/usb/chipidea.rst
+++ b/Documentation/translations/zh_CN/usb/chipidea.rst
@@ -17,18 +17,17 @@ ChipIdea 高速双角色控制器驱动
1. 如何测试 OTG FSM(HNP 和 SRP)
---------------------------------
-下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,
-说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。
+下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,演示如何通过 sysfs 属性
+测试 OTG 的 HNP 和 SRP 功能。
-1.1 如何使能 OTG FSM
+1.1 如何启用 OTG FSM
--------------------
1.1.1 在 ``menuconfig`` 中选择 ``CONFIG_USB_OTG_FSM``,并重新编译内核
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-重新构建内核镜像和模块。如果想查看 OTG FSM 的
-一些内部变量,可以挂载 ``debugfs``;其中有两个文件
-可以显示 OTG FSM 变量以及部分控制器寄存器的值::
+重新构建内核镜像和模块。如果想查看 OTG FSM 的内部变量,可以挂载
+``debugfs``;其中有两个文件,分别显示 OTG FSM 的变量和部分控制器寄存器值::
cat /sys/kernel/debug/ci_hdrc.0/otg
cat /sys/kernel/debug/ci_hdrc.0/registers
@@ -44,11 +43,10 @@ ChipIdea 高速双角色控制器驱动
1.2 测试步骤
------------
-1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,
- 并加载 gadget 类驱动(例如 ``g_mass_storage``)。
+1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,并加载 gadget 类驱动(例如
+ ``g_mass_storage``)。
-2) 用 USB 线连接两块开发板:
- 一端是 micro A 插头,另一端是 micro B 插头。
+2) 用 USB 线连接两块开发板:一端是 micro A 插头,另一端是 micro B 插头。
插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。
@@ -66,32 +64,28 @@ ChipIdea 高速双角色控制器驱动
echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
- 或者,通过引入 HNP 轮询,B 端主机可以知道
- A 端外设希望切换为主机角色,因此这次角色切换
- 也可以通过 A 端外设响应 B 端主机的轮询,
- 在 A 侧触发。
- 这可以通过在 A 设备上执行下面的命令来完成::
+ 或者,也可以借助 HNP 轮询,让 B 端主机知道 A 端外设希望切回主机角色。
+ 因此,这次切换也可以由 A 侧触发,也就是由 A 端外设响应 B 端主机的轮询
+ 来完成。可在 A 设备上执行下面的命令::
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
A 设备应切回主机角色并枚举 B 设备。
-5) 拔掉 B 设备(拔掉 micro B 插头),
- 并在 10 秒内重新插入;
+5) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒内重新插入。
A 设备应重新枚举 B 设备。
-6) 拔掉 B 设备(拔掉 micro B 插头),
- 并在 10 秒后重新插入;
+6) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒后重新插入。
A 设备不应重新枚举 B 设备。
- 如果 A 设备希望使用总线:
+ 如果 A 设备还想继续使用总线:
在 A 设备上执行::
echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
- 如果 B 设备希望使用总线:
+ 如果 B 设备想使用总线:
在 B 设备上执行::
@@ -111,40 +105,41 @@ ChipIdea 高速双角色控制器驱动
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
- A 设备应恢复 USB 总线并枚举 B 设备。
+ A 设备应恢复 USB 总线,并枚举 B 设备。
1.3 参考文档
------------
-《On-The-Go and Embedded Host Supplement
-to the USB Revision 2.0 Specification
+《On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification
July 27, 2012 Revision 2.0 version 1.1a》
2. 如何将 USB 用作系统唤醒源
----------------------------
-下面是在 i.MX6 平台上把 USB 用作系统唤醒源的示例。
+下面给出在 i.MX6 平台上将 USB 用作系统唤醒源的示例。
-2.1 使能核心控制器的唤醒功能::
+2.1 启用核心控制器的唤醒功能::
echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup
-2.2 使能 glue 层的唤醒功能::
+2.2 启用 glue 层的唤醒功能::
echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup
-2.3 使能 PHY 的唤醒功能(可选)::
+2.3 启用 PHY 的唤醒功能(可选)::
echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup
-2.4 使能根集线器的唤醒功能::
+2.4 启用根集线器的唤醒功能::
echo enabled > /sys/bus/usb/devices/usb1/power/wakeup
-2.5 使能相关设备的唤醒功能::
+2.5 启用相关设备的唤醒功能::
echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
-如果系统只有一个 USB 端口,
-而你希望在该端口上启用 USB 唤醒功能,
-可以使用下面的脚本::
+如果系统只有一个 USB 端口,而你希望在该端口上启用 USB 唤醒功能,可以使用
+下面的脚本::
- for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done;
+ for i in $(find /sys -name wakeup | grep usb)
+ do
+ echo enabled > $i
+ done
diff --git a/Documentation/translations/zh_CN/usb/dwc3.rst b/Documentation/translations/zh_CN/usb/dwc3.rst
index 3468ce50c5ba..9584cbbf6d03 100644
--- a/Documentation/translations/zh_CN/usb/dwc3.rst
+++ b/Documentation/translations/zh_CN/usb/dwc3.rst
@@ -18,46 +18,43 @@ DWC3 驱动
待办
~~~~
-阅读时如果想顺手认领点任务,可以从下面挑一项 :)
+如果你愿意接手其中一项任务,可以从下面选择:
- 将中断处理程序改为每个端点各自使用线程化 IRQ
- 事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。
- 当前代码会一直自旋等待命令完成,这种设计并不好。
+ 实践表明,某些 DWC3 命令大约需要 ``~1 ms`` 才能完成。当前代码会一直自旋
+ 等待命令完成,这并不是好办法。
实现思路:
- - DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。
- 中断号在探测(``probe``)阶段分配,并归属于该设备。
- 如果硬件通过 ``MSI`` 为每个端点提供独立中断,
- 那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。
+ - DWC 核心实现了一个按端点分发中断的 IRQ 控制器。中断号在探测
+ (``probe``)阶段分配,并归属于该设备。如果硬件通过 ``MSI`` 为每个
+ 端点提供独立中断,那么这个“虚拟”IRQ 控制器就可以被真实的端点中断
+ 取代。
- - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,
- 在调用 ``usb_ep_disable()`` 时释放中断资源。
- 最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。
+ - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,在调用
+ ``usb_ep_disable()`` 时释放中断资源。最坏情况下需要 32 个中断,最少是
+ ``ep0/1`` 的两个中断。
- ``dwc3_send_gadget_ep_cmd()`` 将在 ``wait_for_completion_timeout()``
中休眠,直到命令完成。
- 中断处理程序分为以下几个部分:
- 设备级主中断处理程序
- 遍历每个事件,并对其调用 ``generic_handle_irq()``。
- 从 ``generic_handle_irq()`` 返回后,确认事件计数器,使中断最终消失。
+ 遍历每个事件,并调用 ``generic_handle_irq()`` 处理。返回后再确认
+ 事件计数器,让中断最终消失。
- 设备级线程化处理程序
无。
- 端点中断的主处理程序
- 读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。
- 事件保存在每个端点的数据结构中。
- 还要注意,一旦把某项工作交给线程处理,
- 就不要再在主处理程序里处理它,
- 以免出现优先级反转之类的问题。
+ 读取事件并尽量处理;凡是需要睡眠的操作都交给线程处理。事件保存在
+ 每个端点的数据结构中。一旦某项工作已经交给线程处理,主处理程序里就
+ 不要再碰它,以免出现优先级反转之类的问题。
- 端点中断的线程化处理程序
处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。
- 延迟:
+ 延迟:
- 不应增加延迟,因为中断线程具有较高优先级,
- 会在普通用户态任务之前运行
+ 不应增加额外延迟,因为中断线程优先级较高,会在普通用户任务之前运行
(除非用户更改了调度优先级)。
diff --git a/Documentation/translations/zh_CN/usb/ehci.rst b/Documentation/translations/zh_CN/usb/ehci.rst
index e05e493a30d3..c4c52303b13e 100644
--- a/Documentation/translations/zh_CN/usb/ehci.rst
+++ b/Documentation/translations/zh_CN/usb/ehci.rst
@@ -14,45 +14,37 @@
EHCI 驱动
=========
-2002年12月27日
+2002 年 12 月 27 日
-EHCI 驱动用于通过支持 USB 2.0 的主机控制器
-硬件与高速 USB 2.0 设备通信。USB 2.0 兼容
-USB 1.1 标准,它定义了三种传输速率:
+EHCI 驱动用于借助支持 USB 2.0 的主机控制器,与高速 USB 2.0 设备通信。USB
+2.0 向下兼容 USB 1.1,并定义了三种传输速率:
- “高速”(High Speed)480 Mbit/sec(60 MByte/sec)
- “全速”(Full Speed)12 Mbit/sec(1.5 MByte/sec)
- “低速”(Low Speed)1.5 Mbit/sec
-USB 1.1 仅支持全速与低速。
-高速设备可以在 USB 1.1 系统上使用,
-但速度会降到 USB 1.1 的速率。
+USB 1.1 仅支持全速与低速。高速设备可以在 USB 1.1 系统上使用,但速度会
+降到 USB 1.1 的速率。
-USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
-插入 EHCI 控制器时,会被交由 USB 1.1 的伴随
-(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
+USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们插入 EHCI 控制器时,会交给
+USB 1.1 的伴随(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
-当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过
-集线器中的事务转换器(Transaction Translator,TT)
-与 EHCI 控制器交互,该转换器将低速或全速事务转换为
-高速分割事务,从而避免浪费传输带宽。
+当 USB 1.1 设备插入 USB 2.0 集线器时,它们会通过集线器里的事务转换器
+(Transaction Translator,TT)与 EHCI 控制器通信。该转换器会把低速或全
+速事务转换为高速分割事务,从而避免浪费传输带宽。
-截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行
-(按字母顺序):Intel、NEC、Philips 和 VIA。
-其他供应商的 EHCI 实现正在陆续问世;
-预计该驱动在这些实现上也可正常运行。
+截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行(按字母顺序):
+Intel、NEC、Philips 和 VIA。随着其他供应商的 EHCI 实现陆续问世,预计该
+驱动在那些实现上也能正常运行。
-自 2001 年年中起,usb-storage 设备就已可用
-(在 2.4 版该驱动上速度相当不错),
-集线器则直到 2001 年底才开始可用,而其他类型的高速设备
-似乎要等到更多系统内置 USB 2.0 后才会出现。
-这类新系统从 2002 年初开始上市,
-并在 2002 年下半年变得更加常见。
+自 2001 年年中起,usb-storage 设备就已可用(在 2.4 版该驱动上速度表现相当
+不错),集线器则直到 2001 年底才开始可用。其他类型的高速设备似乎要等到
+更多系统内置 USB 2.0 后才会出现。这类新系统从 2002 年初开始上市,并在
+2002 年下半年变得更加常见。
-注意,USB 2.0 支持并不只是 EHCI 本身。
-它还需要对 Linux-USB 核心 API 作出其他修改,
-包括 hub 驱动;不过这些修改并不需要真正改变
-暴露给 USB 设备驱动的基本 ``usbcore`` API。
+注意,USB 2.0 的支持并不只靠 EHCI 本身。它还需要对 Linux-USB 核心 API
+做其他修改,包括 hub 驱动;不过这些修改并不需要真正改变向 USB 设备驱动
+暴露的基本 ``usbcore`` API。
- David Brownell
<dbrownell@users.sourceforge.net>
@@ -61,58 +53,46 @@ USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
功能
====
-该驱动会定期在 x86 硬件上进行测试,
-也已在 PPC 硬件上使用,因此大小端问题应当已经解决。
-因此可以认为,它已经处理好了所有必要的 PCI 细节,
-所以即便在 DMA 映射有些特殊的系统上,
-I/O 也应能正常运行。
+该驱动长期在 x86 硬件上接受测试,也在 PPC 平台上使用过,因此大小端问题应
+该都已解决。再加上各种必要的 PCI 细节都已处理妥当,即便在 DMA 映射较特
+殊的系统上,I/O 也应能正常工作。
传输类型
--------
-截至本文撰写时,该驱动应当已经能够很好地处理
-所有控制传输、批量传输和中断传输,
-包括通过 USB 2.0 集线器中的事务转换器
-与 USB 1.1 设备通信;但仍可能存在 bug。
+截至本文撰写时,该驱动应当已经能够稳定处理所有控制传输、批量传输和中断传
+输,包括经由 USB 2.0 集线器里的事务转换器访问 USB 1.1 设备;不过仍可能
+存在 bug。
-高速等时(ISO)传输支持也已可用,但截至本文撰写时,
-还没有 Linux 驱动使用这项支持。
+高速等时(ISO)传输支持也已可用,不过截至本文撰写时,还没有 Linux 驱动真
+正使用它。
-目前尚不支持通过事务转换器实现全速等时传输。
-需要注意,ISO 传输的 split transaction 支持
-与高速 ISO 传输几乎无法共用代码,
-因为 EHCI 用不同的数据结构表示它们。
-因此,目前大多数 USB 音频和视频设备
-还不能通过高速总线连接使用。
+目前尚不支持通过事务转换器实现全速等时传输。需要注意,ISO 传输的分割
+事务支持与高速 ISO 传输几乎无法共用代码,因为 EHCI 用不同的数据结构表示
+它们。因此,目前大多数 USB 音频和视频设备还无法在高速总线上使用。
驱动行为
--------
-所有类型的传输都可以排队。
-这意味着来自一个接口驱动的控制传输
-(或通过 usbfs 发出的控制传输)不会干扰
-另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期,
-而不必担心中断处理开销导致的数据丢失。
+所有类型的传输都可以排队提交。这意味着某个接口驱动发出的控制传输(或经由
+usbfs 提交的控制传输)不会干扰其他驱动的控制传输,而中断传输可以按 1 帧
+周期运行,不必担心中断处理开销导致数据丢失。
-EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
-该驱动不需要了解那些驱动的任何细节;
-一个原本就能正常工作的 OHCI 或 UHCI 驱动,
-并不会因为 EHCI 驱动也存在而需要更改。
+EHCI 根集线器代码会将 USB 1.1 设备交给其伴随控制器处理。该驱动无需了解
+那些驱动的任何细节;一个原本就能正常工作的 OHCI 或 UHCI 驱动,也不会因为
+EHCI 驱动存在而需要修改。
-电源管理方面还有一些问题;
-当前挂起/恢复的行为还不完全正确。
+电源管理方面仍有一些问题;当前挂起/恢复行为还不完全正确。
-此外,在调度周期性事务
-(中断和等时传输)时还采取了一些简化处理。
-这些简化会限制可调度的周期性事务数量,
-并且无法使用小于一帧的轮询间隔。
+此外,在调度周期性事务(中断和等时传输)时还采取了一些简化处理。这些
+简化会限制可调度的周期性事务数量,并且无法使用小于一帧的轮询间隔。
使用方式
========
-假设有一个 EHCI 控制器(位于 PCI 卡或主板上),
-并且已将此驱动编译为模块,可这样加载::
+假设系统中有一个 EHCI 控制器(位于 PCI 卡或主板上),并且此驱动是以模块形
+式编译的,那么可以这样加载::
# modprobe ehci-hcd
@@ -120,27 +100,24 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
# rmmod ehci-hcd
-还应加载一个伴随控制器驱动,
-例如 ``ohci-hcd`` 或 ``uhci-hcd``。
-如果 EHCI 驱动出现任何问题,只需卸载它的模块,
-随后该伴随控制器驱动就会接手
-此前由 EHCI 驱动处理的所有设备
-(但速度会降低)。
+还应加载一个伴随控制器驱动,例如 ``ohci-hcd`` 或 ``uhci-hcd``。如果 EHCI
+驱动出了问题,只要卸载它的模块,伴随控制器驱动就会接管此前由 EHCI 驱动处
+理的全部设备(只是速度会降低)。
模块参数(传给 ``modprobe``)包括:
log2_irq_thresh(默认值 0):
- 默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧
- (125 微秒)。最大值 6 表示 2^6 = 64 个微帧。
+ 默认中断延迟的 log2 值,单位为微帧。默认值 0 表示 1 个微帧
+ (125 微秒),最大值 6 表示 2^6 = 64 个微帧。
该值控制 EHCI 控制器发出中断的频率。
-如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,
-则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件:
+如果你在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,那么任一 EHCI 控
+制器对应的 ``sysfs`` 目录下都会看到三个文件:
``async``
转储异步调度,用于控制传输和批量传输。它会显示每个活动的 ``qh``
以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。
- (可以在 ``usb-storage`` 做磁盘 I/O 时看它;顺便观察请求队列!)
+ (可以在 ``usb-storage`` 执行磁盘 I/O 时查看;也可顺便观察请求队列。)
``periodic``
转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。
@@ -151,111 +128,81 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
这些文件的内容有助于定位驱动问题。
-设备驱动通常不需要关心自己是否运行在 EHCI 之上,
-但它们可能想检查
-``usb_device->speed == USB_SPEED_HIGH``。
-高速设备能做到全速(或低速)设备做不到的事,
-例如高带宽的周期性传输(中断或 ISO 传输)。
-另外,设备描述符中的某些值
-(例如周期性传输的轮询间隔)
-在高速模式下使用不同的编码方式。
+设备驱动通常不需要关心自己是否运行在 EHCI 之上,但有时可能会想检查
+``usb_device->speed == USB_SPEED_HIGH``。高速设备能做到全速(或低速)设备
+做不到的事,例如高带宽的周期性传输(中断或 ISO 传输)。另外,设备描述符
+中的某些值(例如周期性传输的轮询间隔)在高速模式下使用不同的编码方式。
-不过,一定要让设备驱动经过 USB 2.0 集线器的测试。
-当使用事务转换器时,这些集线器报告某些故障
-(例如断开连接)的方式会不同;
-已经见过一些驱动在遇到与 OHCI 或 UHCI
-所报告的不同故障时表现不佳。
+不过,设备驱动一定要在 USB 2.0 集线器后面测一遍。使用事务转换器时,这些
+集线器报告某些故障(例如断开连接)的方式会有所不同;已经见过一些驱动在
+遇到与 OHCI 或 UHCI 不同的故障时表现不佳。
性能
====
-USB 2.0 吞吐量主要受两个因素制约:
-主机控制器处理请求的速度,以及设备响应这些请求的速度。
-480 Mbit/sec 的“原始传输率”对所有设备都成立,
-但总吞吐量还会受到诸如单个高速包之间的延迟、
-驱动是否足够聪明,以及系统整体负载等因素的影响。
-延迟也是性能考量因素。
+USB 2.0 的吞吐量主要受两个因素制约:主机控制器处理请求的速度,以及设备响
+应这些请求的速度。480 Mbit/sec 的“原始传输率”对所有设备都一样,但整体吞
+吐量还会受到诸如高速包之间的间隔、驱动实现是否足够高效以及系统总体负载等
+因素影响。延迟同样是需要考虑的性能指标。
-批量传输最常用于关注吞吐量的场景。
-需要记住的是,批量传输总是以 512 字节包为单位,
-而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。
-8 个 USB 2.0 微帧构成一个 USB 1.1 帧;
-一个微帧的时长是 1 毫秒 / 8 = 125 微秒。
+批量传输通常用于看重吞吐量的场景。需要记住的是,批量传输总是以 512 字节包
+为单位,而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。8 个 USB 2.0 微
+帧构成一个 USB 1.1 帧,因此一个微帧的时长就是 125 微秒。
-因此,只要硬件和设备驱动软件都允许,
-批量传输可提供超过 50 MByte/sec 的带宽。
-周期性传输模式(等时和中断)允许使用更大的包大小,
-从而可以逼近所宣称的 480 Mbit/sec 传输率。
+因此,只要硬件和驱动实现都足够成熟,批量传输就可以提供 50 MByte/sec 以上
+的带宽。周期性传输模式(等时和中断)允许使用更大的包大小,从而可以逼近所
+宣称的 480 Mbit/sec 传输率。
硬件性能
--------
-截至本文撰写时,单个 USB 2.0 设备的最大传输速率
-通常约为 20 MByte/sec。
-这当然会随着时间改变:一些设备现在更快,一些更慢。
+截至本文撰写时,单个 USB 2.0 设备的最大传输速率通常约为 20 MByte/sec。
+这种情况当然会随时间变化:有些设备现在更快,有些则更慢。
-第一代 NEC EHCI 实现似乎存在
-大约 28 MByte/sec 的硬件瓶颈。
-虽然这对单个 20 MByte/sec 的设备显然已经够用,
-但把三个这样的设备挂到同一总线上,
-并不能得到 60 MByte/sec。
-问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
-因此它每个微帧只会尝试 6 次(也许是 7 次)
-USB 事务,而不是 13 次。
-(对一个比其他产品早上市一年的芯片来说,
-这是个合理的妥协!)
+第一代 NEC EHCI 实现似乎存在大约 28 MByte/sec 的硬件瓶颈。虽然这对单个
+20 MByte/sec 的设备显然已经够用,但把三个这样的设备挂到同一总线上,并不
+能得到 60 MByte/sec。问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
+因此它每个微帧只会尝试 6 次(也许是 7 次)USB 事务,而不是 13 次。
+(对一款比其他产品早上市一年的芯片来说,这样的取舍也算合理。)
-预计较新的实现会在这方面做得更好,
-通过投入更多芯片面积来解决这个问题,
-使新的主板芯片组更接近 60 MByte/sec 的目标。
-这既包括 NEC 的更新实现,也包括其他厂商的芯片。
+预计较新的实现会在这方面做得更好,通过投入更多芯片面积来解决这个问题,
+使新的主板芯片组更接近 60 MByte/sec 的目标。这既包括 NEC 的更新实现,也
+包括其他厂商的芯片。
-主机从 EHCI 控制器收到“请求已完成”中断的最小延迟
-为一个微帧(125 微秒)。该延迟可以调节;
-驱动提供了一个模块选项。默认情况下,
-``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制
-或批量请求时,通常可以在不到 250 微秒内得知它已完成
-(具体取决于传输大小)。
+主机从 EHCI 控制器收到“请求已完成”中断的最小延迟为一个微帧
+(125 微秒)。该延迟可以调节;驱动提供了一个模块选项。
+默认情况下,``ehci-hcd`` 使用最小延迟,这意味着发出控制或批量请求后,通
+常不到 250 微秒就能得知它已经完成(具体取决于传输大小)。
软件性能
--------
-即便只是要达到 20 MByte/sec 的传输速率,
-Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。
-这意味着要发出较大的请求,
-或者在需要发出一连串小请求时使用批量请求排队。
-如果驱动未做到这一点,那么会直接从性能结果上表现出来。
+即便只是要达到 20 MByte/sec 的传输速率,Linux-USB 设备驱动也必须让 EHCI
+队列始终保持满载。这意味着要发出较大的请求,或者在需要发出一连串小请求
+时使用批量请求排队。如果驱动做不到这一点,性能就会明显受影响。
-在典型情况下,使用 ``usb_bulk_msg()``
-以 4 KB 块循环写出,
-会浪费超过一半的 USB 2.0 带宽。
-I/O 完成与驱动发出下一次请求之间的延迟,
-通常会比一次 I/O 本身耗时更长。
-如果同样的循环改用 16 KB 块,会好一些;
-若使用一连串 128 KB 块,则浪费会少得多。
+在典型场景下,如果使用 ``usb_bulk_msg()`` 以 4 KB 块循环写出,会浪费超过
+一半的 USB 2.0 带宽。I/O 完成与驱动发出下一次请求之间的空档,往往比一次
+I/O 本身耗时还长。如果同样的循环改用 16 KB 块,情况会好一些;若使用一连串
+128 KB 块,则浪费会少得多。
+但与其依赖这么大的 I/O 缓冲区来提升同步 I/O 的效率,不如直接向主机控制器
+排队提交多个(批量)请求,然后等待它们全部完成(或在出错时取消)。这种
+URB 排队方式对所有 USB 1.1 主机控制器驱动同样适用。
-但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效,
-不如直接向主机控制器排入多个(批量)请求,
-然后等待它们全部完成(或在出错时取消)。
-这种 URB 排队方式对所有 USB 1.1
-主机控制器驱动也同样适用。
-
-在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;
-它们会把 scatterlist 中的所有缓冲区都排入队列。
-它们还使用 scatterlist 的 DMA 映射
-(其中可能应用 IOMMU)并减少中断次数,
-这些都有助于让高速传输尽可能快地运行。
+在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;它们会把 scatterlist
+中的所有缓冲区都排入队列。它们还使用 scatterlist 的 DMA 映射(其中可能
+应用 IOMMU)并减少中断次数,这些都有助于让高速传输尽可能快地运行。
待办:
中断传输和等时(ISO)传输的性能问题。
- 这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。
+ 这些周期性传输都是完全调度的,因此主要问题可能在于如何触发高带宽模式。
待办:
- 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,
- 可以分配超过标准 80% 的周期性带宽。
+ 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,可以分配超过标准
+ 80% 的周期性带宽。
后续将对此进行说明。
diff --git a/Documentation/translations/zh_CN/usb/index.rst b/Documentation/translations/zh_CN/usb/index.rst
index eb5aca0c13ec..df99814c6497 100644
--- a/Documentation/translations/zh_CN/usb/index.rst
+++ b/Documentation/translations/zh_CN/usb/index.rst
@@ -1,4 +1,14 @@
.. SPDX-License-Identifier: GPL-2.0
+
+.. only:: subproject and latex
+
+ .. raw:: latex
+
+ \renewcommand{\thesection}{}
+ \renewcommand{\thesubsection}{}
+ \kerneldocCJKon
+ \kerneldocBeginSC{
+
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/usb/index.rst
@@ -24,7 +34,7 @@ USB 支持
ehci
usbmon
-Todolist:
+待翻译文档:
* functionfs
* functionfs-desc
@@ -52,3 +62,9 @@ Todolist:
====
* :ref:`genindex`
+
+.. only:: subproject and latex
+
+ .. raw:: latex
+
+ }\kerneldocEndSC
diff --git a/Documentation/translations/zh_CN/usb/usbmon.rst b/Documentation/translations/zh_CN/usb/usbmon.rst
index 11b6d5b59dce..db6030cd64a1 100644
--- a/Documentation/translations/zh_CN/usb/usbmon.rst
+++ b/Documentation/translations/zh_CN/usb/usbmon.rst
@@ -16,67 +16,56 @@ usbmon
简介
====
-小写形式的 ``usbmon`` 指的是内核中的一项功能,
-用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具
-``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。
-类似地,人们希望使用 usbdump 或 USBMon
-(首字母大写)之类的工具来检查
-usbmon 生成的原始跟踪数据。
-
-usbmon 报告的是各个外设驱动
-向主机控制器驱动(HCD)发出的请求。
-因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息
-可能无法精确对应实际的总线事务。
-这和 tcpdump 的情况是一样的。
-
-目前实现了两种 API: ``text`` 和 ``binary``。
-二进制 API 通过 ``/dev`` 命名空间中的字符设备提供,
-并且属于 ABI。文本 API 自内核 2.6.35 起已废弃,
-但为了方便仍然可用。
+小写的 ``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 提供了一种接口,
-可以输出文本格式的跟踪信息。这样做有两个目的:
-第一,在更完善的格式最终确定之前,
-它作为工具间通用的跟踪交换格式;
-第二,在不使用工具的情况下,人们也可以直接阅读这些信息。
+与数据包套接字不同,usbmon 还提供了一个输出文本格式跟踪信息的接口。这样
+做主要有两个目的:一是在更完善的格式最终确定之前,将其作为工具间通用的跟
+踪交换格式;二是在没有工具时也能直接阅读这些信息。
-要收集原始文本跟踪信息,请按以下步骤进行操作。
+要收集原始文本跟踪信息,按下面的步骤做即可。
1. 准备
-------
-挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块
-(如果它是作为模块构建的)。如果 usbmon 已经编入内核,
-那么第二步可以省略。
+挂载 debugfs(内核配置里必须启用它),并加载 usbmon 模块(如果它是以模块
+方式构建的)。如果 usbmon 已经编译进内核,这一步就可以省略。
命令示例::
- # mount -t debugfs none_debugs /sys/kernel/debug
+ # mount -t debugfs none /sys/kernel/debug
# modprobe usbmon
#
-确认总线套接字是否存在::
+确认 ``usbmon`` 目录下是否有这些条目::
# ls /sys/kernel/debug/usb/usbmon
0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u
#
-现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包,
-并跳到第 3 步;
-也可以先按第 2 步找到目标设备所在的总线。
-这样可以过滤掉那些持续输出数据的烦人设备。
+现在,你可以直接用 ``0u`` 捕获所有总线上的数据包,然后跳到第 3 步;也可
+以先按第 2 步找出目标设备所在的总线。这样可以把那些持续产生流量的设备过
+滤掉。
2. 查找目标设备连接的是哪条总线
-------------------------------
-运行 ``cat /sys/kernel/debug/usb/devices``,
-找到对应设备的 T 行。通常可以通过厂商字符串来查找。
-如果有许多类似设备,可以拔掉其中一个,
-再比较前后两次 ``/sys/kernel/debug/usb/devices``
-的输出。T 行里会包含总线编号。
+运行 ``cat /sys/kernel/debug/usb/devices``,找到对应设备的 T 行。通常可以通过
+厂商字符串来查找。如果有很多相似设备,可以拔掉其中一个,再比较前后两次
+``/sys/kernel/debug/usb/devices`` 的输出。T 行里会包含总线编号。
示例::
@@ -86,8 +75,8 @@ usbmon 报告的是各个外设驱动
S: Manufacturer=ATEN
S: Product=UC100KM V2.00
-``Bus=03`` 表示它位于 3 号总线上。或者,
-也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。
+``Bus=03`` 表示它位于 3 号总线上。或者,也可以查看 ``lsusb`` 的输出,并从
+对应条目里找到总线编号。
示例如下::
@@ -97,133 +86,110 @@ usbmon 报告的是各个外设驱动
3. 启动 cat 命令
----------------
-如果只监听单条总线,可执行::
+如果只监听单条总线,执行::
# cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out
-否则,如果要监听所有总线,则执行::
+否则,如果要监听所有总线,执行::
# cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out
-此进程会一直读取,直到被终止。
-由于输出通常会很长,因此更推荐将输出重定向到某个位置。
+这个进程会一直运行到被终止为止。由于输出通常会很长,最好把它重定向到文件
+或其他位置。
4. 在 USB 总线上执行期望的操作
------------------------------
-此处需要执行一些会产生 USB 流量的动作,
-比如插入 U 盘、拷贝文件、操作摄像头等。
+这里做一些会产生 USB 流量的操作即可,比如插入 U 盘、拷贝文件、操作摄像头
+等。
5. 停止 cat
-----------
-这一步通常通过键盘中断(Control-C)完成。
+这一步通常按下键盘中断(Control-C)即可完成。
-此时输出文件(本例中为 ``/tmp/1.mon.out``)
-可以保存、通过电子邮件发送,或使用文本编辑器查看。
-如果使用最后一种方式,请确保文件不会大到编辑器无法打开。
+此时,输出文件(本例中为 ``/tmp/1.mon.out``)可以保存下来,通过电子邮件发
+送,也可以用文本编辑器查看。如果要用文本编辑器查看,请确保文件大小不会
+大到编辑器无法处理。
原始文本数据格式
================
-目前支持两种格式:原始格式,也就是 ``1t`` 格式,
-以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。
-``1u`` 格式增加了一些字段,例如 ISO 帧描述符、
-``interval`` 等。它生成的行会稍长一些,
-但在其他方面是 ``1t`` 格式的完整超集。
-
-如果程序需要区分上述两种格式,
-可以查看 ``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 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。
+目前支持两种格式:原始的 ``1t`` 格式和 ``1u`` 格式。``1t`` 格式在内核
+2.6.21 中已被废弃。``1u`` 格式增加了一些字段,例如 ISO 帧描述符和
+``interval``。它生成的行会稍长一些,但除此之外,它是 ``1t`` 格式的完整
+超集。
+
+如果程序需要区分上述两种格式,可以查看 ``address`` 字段(见下文)。如果
+其中有两个冒号,就是 ``1t`` 格式;否则是 ``1u`` 格式。
+
+任何文本格式的数据都由一系列事件构成,例如 URB 提交、URB 回调和提交错
+误。每个事件占一行,由若干以空白符分隔的字段组成。字段数量和位置会随事件
+类型变化,但下面这些字段对所有类型都通用:
+
+下面按从左到右的顺序说明这些通用字段:
+
+- URB 标识(URB Tag)。用于标识 URB,通常是 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),报告的是实际长度。
+ 如果某个等时传输事件带有描述符,会先打印该 URB 的描述符总数,再为每个描
+ 述符打印一个字段,最多 5 个。每个字段由三个用冒号分隔的十进制数组成,依
+ 次表示状态(status)、偏移(offset)和长度(length)。对于提交事件,报
+ 告的是初始长度;对于回调事件,报告的是实际长度。
-- 数据长度:
- 对于提交,表示请求的长度;对于回调,表示实际传输的长度。
+- 数据长度:对于提交,表示请求的长度;对于回调,表示实际传输的长度。
-- 数据标签:
- 即使数据长度非零,usbmon 也不一定会捕获数据。
- 仅当标签为 ``=`` 时,才会有数据字段。
+- 数据标签:即使数据长度非零,usbmon 也不一定会捕获数据。只有标签为
+ ``=`` 时,才会有数据字段。
-- 数据字段:
- 以大端十六进制格式显示。注意,这些并不是真正的机器字,
- 而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含
- 1 到 4 个字节。
- 收集的数据长度是有限的,可能小于数据长度字段中报告的值。
- 因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区,
- 所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下,
- 收集的数据长度可能大于数据长度字段的值。
+- 数据字段:以大端十六进制格式显示。注意,这些并不是真正的机器字,只是为
+ 了便于阅读,把字节流按“字”分组显示。因此最后一个字可能只包含 1 到 4 个
+ 字节。捕获的数据长度是有限的,可能小于数据长度字段中报告的值。对于等时
+ 输入(Zi)完成事件,如果缓冲区里的接收数据比较稀疏,捕获数据的长度甚至
+ 可能大于数据长度字段,因为后者只统计实际接收到的字节,而数据字段展示的
+ 是整个传输缓冲区。
@@ -234,18 +200,19 @@ usbmon 报告的是各个外设驱动
d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 <
d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000
-向地址为 5 的存储设备发送
-31 字节 Bulk 包装的 SCSI 命令 ``0x28``
-(``READ_10``)的输出批量传输::
+向地址为 5 的存储设备发送一个输出批量传输,其中 31 字节的 Bulk 封装用于承
+载 SCSI 命令 ``0x28``(``READ_10``)。为便于排版,下面的第一条记录按两行
+显示,但实际 usbmon 输出仍是一行::
- dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000
+ dd65f0e8 4128379752 S Bo:1:005:2 -115 31 =
+ 55534243 ad000000 00800000 80010a28 20000000 20000040
+ 00000000 000000
dd65f0e8 4128379808 C Bo:1:005:2 0 31 >
原始二进制格式与 API
====================
-API 的整体架构与前文大体相同,只是事件以二进制格式传递。
-每个事件都通过下面的结构发送
-(这个名字是为了叙述方便而虚构的)::
+API 的整体架构与前文大体相同,只是事件以二进制格式传递。每个事件都通过
+下面的结构发送(这个结构名只是为了叙述方便而虚构的)::
struct usbmon_packet {
@@ -275,29 +242,22 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
unsigned int ndesc; /* 60: 实际 ISO 描述符数量 */
}; /* 64 总长度 */
-可以用 ``read(2)``、``ioctl(2)``,
-或者通过 ``mmap`` 访问缓冲区,
-从字符设备接收这些事件。
-不过,出于兼容性原因,``read(2)``
-只返回前 48 个字节。
+可以用 ``read(2)``、``ioctl(2)``,或者通过 ``mmap`` 访问缓冲区,从字符设
+备接收这些事件。不过,出于兼容性原因,``read(2)`` 只返回前 48 个字节。
-字符设备通常命名为 ``/dev/usbmonN``,
-其中 ``N`` 是 USB 总线号。
-编号为零的设备(``/dev/usbmon0``)比较特殊,
-表示“所有总线”。
-请注意,具体命名策略由 Linux 发行版决定。
+字符设备通常命名为 ``/dev/usbmonN``,其中 ``N`` 是 USB 总线号。编号为零的
+设备(``/dev/usbmon0``)比较特殊,表示“所有总线”。具体命名策略由 Linux
+发行版决定。
-如果你手动创建 ``/dev/usbmon0``,
-请确保它归 root 所有,并且权限为 ``0600``。
-否则,非特权用户将能够窃听键盘流量。
+如果你手动创建 ``/dev/usbmon0``,请确保它归 root 所有,并且权限为 ``0600``。
+否则,非特权用户就能窃听键盘输入流量。
以下 ``MON_IOC_MAGIC`` 为 ``0x92`` 的 ioctl 调用可用:
``MON_IOCQ_URB_LEN``,定义为 ``_IO(MON_IOC_MAGIC, 1)``
-该调用返回下一个事件的数据长度。
-注意大多数事件不包含数据,
-因此如果该调用返回零,并不意味着没有事件。
+该调用返回下一个事件的数据长度。注意大多数事件不包含数据,因此如果它返回
+零,并不意味着没有事件。
``MON_IOCG_STATS``,定义为
``_IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)``
@@ -309,18 +269,16 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
u32 dropped;
};
-成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,
-而不是自上次重置以来处理过的事件数量。
+成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,而不是自上次重置以来
+处理过的事件数量。
-成员 ``dropped`` 表示自上次调用
-``MON_IOCG_STATS`` 以来丢失的事件数量。
+成员 ``dropped`` 表示自上次调用 ``MON_IOCG_STATS`` 以来丢失的事件数量。
``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)``
-此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。
-大小可能会向下取整到下一个块(或页)。
-如果请求的大小超出该内核的 [未指定] 范围,
-则调用会失败并返回 ``-EINVAL``。
+此调用设置缓冲区大小。参数是以字节为单位的缓冲区大小。大小可能会向下取整
+到下一个块(或页)。如果请求的大小超出当前内核允许的范围,则调用会失败并
+返回 ``-EINVAL``。
``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)``
@@ -331,9 +289,8 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
``MON_IOCX_GETX``,定义为
``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)``
-如果内核缓冲区中没有事件,
-这些调用就会一直等待,直到有事件到达,
-然后返回第一个事件。
+如果内核缓冲区中没有事件,这些调用就会一直等待,直到有事件到达,然后返回
+第一个事件。
参数是指向以下结构的指针::
struct mon_get_arg {
@@ -343,20 +300,18 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
};
-调用前,应填好 ``hdr``、``data`` 和 ``alloc``。
-调用返回后,``hdr`` 指向的区域中包含下一个事件的结构;
-如果存在数据,那么数据缓冲区中也会包含相应数据。
-该事件会从内核缓冲区中移除。
+调用前,应填好 ``hdr``、``data`` 和 ``alloc``。调用返回后,``hdr`` 指向的
+内存区域中会写入下一个事件的结构;如果存在数据,数据缓冲区中也会填入相应
+内容。该事件会从内核缓冲区中移除。
-``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,
-``MON_IOCX_GETX`` 会复制 64 字节。
+``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,``MON_IOCX_GETX``
+会复制 64 字节。
``MON_IOCX_MFETCH``,定义为
``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)``
-当应用程序通过 ``mmap(2)`` 访问缓冲区时,
-主要使用这个 ioctl。
-其参数是指向以下结构的指针::
+应用程序通过 ``mmap(2)`` 访问缓冲区时,主要使用这个 ioctl。其参数是指向
+以下结构的指针::
struct mon_mfetch_arg {
uint32_t *offvec; /* 获取的事件偏移向量 */
@@ -365,41 +320,36 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
};
-该 ioctl 的操作分为三个阶段:
+这个 ioctl 的流程分为三个阶段:
-首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。
-实际丢弃的事件数量会写回 ``nflush``。
+首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。实际丢弃的事件数量会
+写回 ``nflush``。
-其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待,
-直到缓冲区中出现事件。
+其次,除非设备以 ``O_NONBLOCK`` 打开,否则会一直等待,直到缓冲区中出现
+事件。
-第三,将最多 ``nfetch`` 个偏移量提取到 mmap
-缓冲区,并存入 ``offvec`` 中。
-实际提取到的事件偏移数量会存回 ``nfetch``。
+第三,将最多 ``nfetch`` 个偏移量提取到 mmap 缓冲区,并存入 ``offvec`` 中。
+实际提取到的事件偏移数量会写回 ``nfetch``。
``MON_IOCH_MFLUSH``,定义为 ``_IO(MON_IOC_MAGIC, 8)``
-此调用从内核缓冲区移除若干事件。
-其参数为要移除的事件数量。
-如果缓冲区中的事件少于请求数量,
-则移除所有事件,且不报告错误。
-当没有事件时也可使用。
+此调用从内核缓冲区移除若干事件。其参数是要移除的事件数量。如果缓冲区中的
+事件少于请求数量,则移除全部现有事件,且不报告错误。即使当前没有事件,也
+可以调用。
``FIONBIO``
如果有需要,将来可能会实现 ``FIONBIO`` ioctl。
-除了 ``ioctl(2)`` 和 ``read(2)`` 之外,
-二进制 API 的特殊文件也可以用 ``select(2)`` 和
-``poll(2)`` 轮询。
-但 ``lseek(2)`` 不起作用。
+除了 ``ioctl(2)`` 和 ``read(2)`` 之外,二进制 API 对应的特殊文件还可以用
+``select(2)`` 和 ``poll(2)`` 轮询,但 ``lseek(2)`` 不可用。
* 二进制 API 的内核缓冲区内存映射访问
-基本思想很简单:
+基本思路很简单:
-准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。
-然后执行类似下面伪代码的循环::
+准备时,先查询当前大小,再用 ``mmap(2)`` 映射缓冲区。之后运行与下面伪代码
+类似的循环::
struct mon_mfetch_arg fetch;
struct usbmon_packet *hdr;
@@ -411,7 +361,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
ioctl(fd, MON_IOCX_MFETCH, &fetch); // 同时处理错误
nflush = fetch.nfetch; // 完成后要刷新这么多包
for (i = 0; i < nflush; i++) {
- hdr = (struct ubsmon_packet *) &mmap_area[vec[i]];
+ hdr = (struct usbmon_packet *) &mmap_area[vec[i]];
if (hdr->type == '@') // 填充包
continue;
caddr_t data = &mmap_area[vec[i]] + 64;
@@ -421,7 +371,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递ã
-因此,主要思想是每 N 个事件只执行一次 ioctl。
+因此,这里的核心思路就是每 N 个事件只执行一次 ioctl。
-虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端,
-因此上面的伪代码无需任何合并操作。
+虽然缓冲区是环形的,但返回的头部和数据不会跨越缓冲区末端,因此上面的伪代
+码无需做任何拼接。
diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst
index f7487b0d8057..b189cf5626ba 100644
--- a/Documentation/usb/functionfs.rst
+++ b/Documentation/usb/functionfs.rst
@@ -72,6 +72,82 @@ have been written to their ep0's.
Conversely, the gadget is unregistered after the first USB function
closes its endpoints.
+Endpoint IOCTLs
+===============
+
+FunctionFS supports additional IOCTLs that can be performed on data endpoints
+(ie. not ep0). For a full list of these IOCTLs, please refer to the documentation
+in ``include/uapi/linux/usb/functionfs.h``.
+
+One such IOCTL is:
+
+ ``FUNCTIONFS_ENDPOINT_ENABLE_ZLP(__u32 *)``
+ Enable or disable automatic zero-length packet (ZLP) appending for the
+ endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to
+ enable. When enabled, the kernel will automatically append a ZLP at the end
+ of a transfer if the payload length is an exact multiple of the endpoint's
+ max packet size. This is useful for compatibility with legacy protocols
+ which require automatic ZLP appending to data written from userspace. This
+ IOCTL can only be used on IN endpoints. It can be called at any time after
+ the FunctionFS instance is active, even before the host has connected or
+ enabled the endpoint. Returns zero on success, or a negative errno value on
+ error:
+
+ * ``-ENODEV``: The FunctionFS instance is not active.
+ * ``-EINVAL``: The endpoint is not an IN endpoint.
+ * ``-EFAULT``: Invalid user space pointer for the argument.
+
+RW Proxy Endpoints
+==================
+
+If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header
+(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a
+bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair
+of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware
+endpoints, allowing userspace to use a single file descriptor for both reading
+(OUT) and writing (IN).
+
+This flag requires the total number of hardware endpoints to be an even number.
+FunctionFS will automatically walk the provided endpoints and group them into
+adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the
+second pair). Each pair must consist of exactly one IN endpoint and one OUT
+endpoint.
+
+For each valid pair, a rw_proxy file is created and named after the first
+endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are
+paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired,
+"ep3_rw" is created.
+
+If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be
+named using their physical endpoint address in hexadecimal instead of their
+index. RW proxy files will inherit this naming convention. For example, if the
+first endpoint of a pair maps to address 0x02, the rw_proxy file will be
+named "ep02_rw".
+
+When this flag is enabled, userspace has the choice of performing data transfers
+via the single rw_proxy file descriptor or the two base file descriptors. The
+rw_proxy file descriptor acts as a pure VFS alias that proxies all operations
+directly to the underlying base file descriptors.
+
+Because it is a pure proxy, there are no data races or buffer corruptions if
+userspace uses both the rw_proxy endpoint and the base endpoints concurrently.
+The native mutexes of the base endpoints perfectly serialize all concurrent
+transfers. However, userspace should generally pick one method and stick to it
+to avoid interleaving its own data stream.
+
+- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and
+ will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue
+ the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base
+ endpoint file descriptor.
+- **Intentional Stalls:** The traditional mechanism for intentionally halting an
+ endpoint by issuing a reverse-direction data operation (e.g., attempting to
+ read from an IN endpoint) continues to work, but it must be issued on the
+ base endpoint. RW proxy endpoints cannot be used to trigger a stall because
+ they are fully bidirectional.
+
+Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported
+via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF
+transfers are required, users must use the standard base endpoints.
DMABUF interface
================
@@ -79,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the
userspace can attach DMABUF objects (externally created) to an endpoint,
and subsequently use them for data transfers.
+Note: The DMABUF interface is unsupported on rw_proxy endpoints. See
+the RW Proxy Endpoints section for details on using DMABUF alongside
+the ``FUNCTIONFS_RW_PROXY_EPS`` flag.
+
A userspace application can then use this interface to share DMABUF
objects between several interfaces, allowing it to transfer data in a
zero-copy fashion, for instance between IIO and the USB stack.
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index a6e8292f320a..4921e5307d49 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -714,9 +714,6 @@ The uac1 function provides these attributes in its function directory:
=============== ====================================
audio_buf_size audio buffer size
- fn_cap capture pcm device file name
- fn_cntl control device file name
- fn_play playback pcm device file name
req_buf_size ISO OUT endpoint request buffer size
req_count ISO OUT endpoint request count
=============== ====================================
diff --git a/drivers/media/usb/as102/as102_usb_drv.c b/drivers/media/usb/as102/as102_usb_drv.c
index a11024451ceb..be2f8be560fa 100644
--- a/drivers/media/usb/as102/as102_usb_drv.c
+++ b/drivers/media/usb/as102/as102_usb_drv.c
@@ -24,35 +24,33 @@ static void as102_usb_stop_stream(struct as102_dev_t *dev);
static int as102_open(struct inode *inode, struct file *file);
static int as102_release(struct inode *inode, struct file *file);
-static const struct usb_device_id as102_usb_id_table[] = {
- { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) },
- { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) },
- { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) },
- { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) },
- { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) },
- { } /* Terminating entry */
+struct as102_dev_info {
+ const char *name;
+ /*
+ * eLNA configuration: devices built on the reference design work best
+ * with 0xA0, while custom designs seem to require 0xC0
+ */
+ uint8_t elna_cfg;
};
-/* Note that this table must always have the same number of entries as the
- as102_usb_id_table struct */
-static const char * const as102_device_names[] = {
- AS102_REFERENCE_DESIGN,
- AS102_PCTV_74E,
- AS102_ELGATO_EYETV_DTT_NAME,
- AS102_NBOX_DVBT_DONGLE_NAME,
- AS102_SKY_IT_DIGITAL_KEY_NAME,
- NULL /* Terminating entry */
-};
+#define DRIVER_INFO(dev_name, dev_elna_cfg) \
+ .driver_info = (kernel_ulong_t)&(const struct as102_dev_info){ \
+ .name = (dev_name), \
+ .elna_cfg = (dev_elna_cfg), \
+ }
-/* eLNA configuration: devices built on the reference design work best
- with 0xA0, while custom designs seem to require 0xC0 */
-static uint8_t const as102_elna_cfg[] = {
- 0xA0,
- 0xC0,
- 0xC0,
- 0xA0,
- 0xA0,
- 0x00 /* Terminating entry */
+static const struct usb_device_id as102_usb_id_table[] = {
+ { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001),
+ DRIVER_INFO(AS102_REFERENCE_DESIGN, 0xA0) },
+ { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID),
+ DRIVER_INFO(AS102_PCTV_74E, 0xC0) },
+ { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID),
+ DRIVER_INFO(AS102_ELGATO_EYETV_DTT_NAME, 0xC0) },
+ { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID),
+ DRIVER_INFO(AS102_NBOX_DVBT_DONGLE_NAME, 0xA0) },
+ { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID),
+ DRIVER_INFO(AS102_SKY_IT_DIGITAL_KEY_NAME, 0xA0) },
+ { } /* Terminating entry */
};
struct usb_driver as102_usb_driver = {
@@ -336,29 +334,18 @@ static int as102_usb_probe(struct usb_interface *intf,
{
int ret;
struct as102_dev_t *as102_dev;
- int i;
-
- /* This should never actually happen */
- if (ARRAY_SIZE(as102_usb_id_table) !=
- (sizeof(as102_device_names) / sizeof(const char *))) {
- pr_err("Device names table invalid size");
- return -EINVAL;
- }
+ const struct as102_dev_info *info = (const struct as102_dev_info *)id->driver_info;
as102_dev = kzalloc_obj(struct as102_dev_t);
if (as102_dev == NULL)
return -ENOMEM;
- /* Assign the user-friendly device name */
- for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) {
- if (id == &as102_usb_id_table[i]) {
- as102_dev->name = as102_device_names[i];
- as102_dev->elna_cfg = as102_elna_cfg[i];
- }
- }
-
- if (as102_dev->name == NULL)
+ if (info) {
+ as102_dev->name = info->name;
+ as102_dev->elna_cfg = info->elna_cfg;
+ } else {
as102_dev->name = "Unknown AS102 device";
+ }
/* set private callback functions */
as102_dev->bus_adap.ops = &as102_priv_ops;
diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c
index 2a1728621887..71c1aafca4d3 100644
--- a/drivers/net/thunderbolt/main.c
+++ b/drivers/net/thunderbolt/main.c
@@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev)
dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
}
-static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id)
+static int tbnet_probe(struct tb_service *svc)
{
struct tb_xdomain *xd = tb_service_parent(svc);
struct net_device *dev;
@@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume);
static const struct tb_service_id tbnet_ids[] = {
{ TB_SERVICE("network", 1) },
- { },
+ { }
};
MODULE_DEVICE_TABLE(tbsvc, tbnet_ids);
diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c
index 8700eeb8e22d..aba1a640fc26 100644
--- a/drivers/net/usb/pegasus.c
+++ b/drivers/net/usb/pegasus.c
@@ -43,21 +43,12 @@ static bool loopback;
static bool mii_mode;
static char *devid;
-static struct usb_eth_dev usb_dev_id[] = {
-#define PEGASUS_DEV(pn, vid, pid, flags) \
- {.name = pn, .vendor = vid, .device = pid, .private = flags},
-#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \
- PEGASUS_DEV(pn, vid, pid, flags)
-#include "pegasus.h"
-#undef PEGASUS_DEV
-#undef PEGASUS_DEV_CLASS
- {NULL, 0, 0, 0},
- {NULL, 0, 0, 0}
-};
+static struct usb_eth_dev dynamic_id_info = {};
static struct usb_device_id pegasus_ids[] = {
#define PEGASUS_DEV(pn, vid, pid, flags) \
- {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid},
+ {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid, \
+ .driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}},
/*
* The Belkin F8T012xx1 bluetooth adaptor has the same vendor and product
* IDs as the Belkin F5D5050, so we need to teach the pegasus driver to
@@ -66,7 +57,8 @@ static struct usb_device_id pegasus_ids[] = {
*/
#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \
{.match_flags = (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS), \
- .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass},
+ .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass, \
+ .driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}},
#include "pegasus.h"
#undef PEGASUS_DEV
#undef PEGASUS_DEV_CLASS
@@ -402,12 +394,12 @@ static inline int reset_mac(pegasus_t *pegasus)
if (i == REG_TIMEOUT)
return -ETIMEDOUT;
- if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
- usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) {
+ if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS ||
+ le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) {
set_register(pegasus, Gpio0, 0x24);
set_register(pegasus, Gpio0, 0x26);
}
- if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_ELCON) {
+ if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_ELCON) {
__u16 auxmode;
ret = read_mii_word(pegasus, 3, 0x1b, &auxmode);
if (ret < 0)
@@ -445,9 +437,9 @@ static int enable_net_traffic(struct net_device *dev, struct usb_device *usb)
memcpy(pegasus->eth_regs, data, sizeof(data));
ret = set_registers(pegasus, EthCtrl0, 3, data);
- if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
- usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS2 ||
- usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) {
+ if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS ||
+ le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS2 ||
+ le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) {
u16 auxmode;
ret = read_mii_word(pegasus, 0, 0x1b, &auxmode);
if (ret < 0)
@@ -1153,7 +1145,7 @@ static int pegasus_probe(struct usb_interface *intf,
struct usb_device *dev = interface_to_usbdev(intf);
struct net_device *net;
pegasus_t *pegasus;
- int dev_index = id - pegasus_ids;
+ const struct usb_eth_dev *info = (const struct usb_eth_dev *)id->driver_info;
int res = -ENOMEM;
static const u8 bulk_ep_addr[] = {
PEGASUS_USB_EP_BULK_IN | USB_DIR_IN,
@@ -1178,7 +1170,6 @@ static int pegasus_probe(struct usb_interface *intf,
goto out;
pegasus = netdev_priv(net);
- pegasus->dev_index = dev_index;
pegasus->intf = intf;
res = alloc_urbs(pegasus);
@@ -1206,7 +1197,7 @@ static int pegasus_probe(struct usb_interface *intf,
pegasus->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV
| NETIF_MSG_PROBE | NETIF_MSG_LINK);
- pegasus->features = usb_dev_id[dev_index].private;
+ pegasus->features = info ? info->private : DEFAULT_GPIO_RESET;
res = get_interrupt_interval(pegasus);
if (res)
goto out2;
@@ -1235,7 +1226,7 @@ static int pegasus_probe(struct usb_interface *intf,
queue_delayed_work(system_long_wq, &pegasus->carrier_check,
CARRIER_CHECK_DELAY);
dev_info(&intf->dev, "%s, %s, %pM\n", net->name,
- usb_dev_id[dev_index].name, net->dev_addr);
+ info ? info->name : "(unknown)", net->dev_addr);
return 0;
out3:
@@ -1325,8 +1316,9 @@ static struct usb_driver pegasus_driver = {
static void __init parse_id(char *id)
{
- unsigned int vendor_id = 0, device_id = 0, flags = 0, i = 0;
+ unsigned int vendor_id = 0, device_id = 0, flags = 0;
char *token, *name = NULL;
+ int dyn_id_index = ARRAY_SIZE(pegasus_ids) - 2;
token = strsep(&id, ":");
if (token)
@@ -1348,14 +1340,12 @@ static void __init parse_id(char *id)
if (device_id > 0x10000 || device_id == 0)
return;
- for (i = 0; usb_dev_id[i].name; i++);
- usb_dev_id[i].name = name;
- usb_dev_id[i].vendor = vendor_id;
- usb_dev_id[i].device = device_id;
- usb_dev_id[i].private = flags;
- pegasus_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
- pegasus_ids[i].idVendor = vendor_id;
- pegasus_ids[i].idProduct = device_id;
+ dynamic_id_info.name = name;
+ dynamic_id_info.private = flags;
+ pegasus_ids[dyn_id_index].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
+ pegasus_ids[dyn_id_index].idVendor = vendor_id;
+ pegasus_ids[dyn_id_index].idProduct = device_id;
+ pegasus_ids[dyn_id_index].driver_info = (kernel_ulong_t)&dynamic_id_info;
}
static int __init pegasus_init(void)
diff --git a/drivers/net/usb/pegasus.h b/drivers/net/usb/pegasus.h
index a05b143155ba..ccdedcef52e7 100644
--- a/drivers/net/usb/pegasus.h
+++ b/drivers/net/usb/pegasus.h
@@ -85,7 +85,6 @@ typedef struct pegasus {
unsigned features;
u32 msg_enable;
u32 wolopts;
- int dev_index;
int intr_interval;
struct tasklet_struct rx_tl;
struct delayed_work carrier_check;
@@ -102,8 +101,6 @@ typedef struct pegasus {
struct usb_eth_dev {
char *name;
- __u16 vendor;
- __u16 device;
__u32 private; /* LSB is gpio reset value */
};
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c
index 47f904e7e652..d3491ff08e6e 100644
--- a/drivers/net/wireless/ath/ath9k/hif_usb.c
+++ b/drivers/net/wireless/ath/ath9k/hif_usb.c
@@ -1087,7 +1087,7 @@ static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
}
kfree(buf);
- if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
+ if (IS_AR7010_DEVICE(hif_dev->id_info))
firm_offset = AR7010_FIRMWARE_TEXT;
else
firm_offset = AR9271_FIRMWARE_TEXT;
@@ -1182,7 +1182,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev,
if (MAJOR_VERSION_REQ == 1 && hif_dev->fw_minor_index == 3) {
const char *filename;
- if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
+ if (IS_AR7010_DEVICE(hif_dev->id_info))
filename = FIRMWARE_AR7010_1_1;
else
filename = FIRMWARE_AR9271;
@@ -1198,7 +1198,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev,
return -ENOENT;
} else {
- if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
+ if (IS_AR7010_DEVICE(hif_dev->id_info))
chip = "7010";
else
chip = "9271";
@@ -1255,9 +1255,9 @@ static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context)
ret = ath9k_htc_hw_init(hif_dev->htc_handle,
&hif_dev->interface->dev,
- hif_dev->usb_device_id->idProduct,
+ le16_to_cpu(hif_dev->udev->descriptor.idProduct),
hif_dev->udev->product,
- hif_dev->usb_device_id->driver_info);
+ hif_dev->id_info);
if (ret) {
ret = -EINVAL;
goto err_htc_hw_init;
@@ -1369,7 +1369,7 @@ static int ath9k_hif_usb_probe(struct usb_interface *interface,
hif_dev->udev = udev;
hif_dev->interface = interface;
- hif_dev->usb_device_id = id;
+ hif_dev->id_info = id->driver_info;
#ifdef CONFIG_PM
udev->reset_resume = 1;
#endif
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.h b/drivers/net/wireless/ath/ath9k/hif_usb.h
index dc0b0fa5c325..b3e7b0fb54b8 100644
--- a/drivers/net/wireless/ath/ath9k/hif_usb.h
+++ b/drivers/net/wireless/ath/ath9k/hif_usb.h
@@ -115,7 +115,7 @@ struct cmd_buf {
struct hif_device_usb {
struct usb_device *udev;
struct usb_interface *interface;
- const struct usb_device_id *usb_device_id;
+ int id_info;
const void *fw_data;
size_t fw_size;
struct completion fw_done;
diff --git a/drivers/target/target_core_fabric_configfs.c b/drivers/target/target_core_fabric_configfs.c
index 166dbf4c4061..ab8f81650710 100644
--- a/drivers/target/target_core_fabric_configfs.c
+++ b/drivers/target/target_core_fabric_configfs.c
@@ -690,6 +690,14 @@ static void target_fabric_port_unlink(
}
core_dev_del_lun(se_tpg, lun);
+
+ if (tf->tf_ops->fabric_post_unlink) {
+ /*
+ * Allow fabrics to release state that must remain valid until
+ * core_dev_del_lun() has drained all active LUN references.
+ */
+ tf->tf_ops->fabric_post_unlink(se_tpg, lun);
+ }
}
static void target_fabric_port_release(struct config_item *item)
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index f5cf0e177f40..6e9080e7bcec 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf,
if (!access_ok(user_buf, *count))
return ERR_PTR(-EFAULT);
- buf = (void *)get_zeroed_page(GFP_KERNEL);
+ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
nbytes = min_t(size_t, *count, PAGE_SIZE);
if (copy_from_user(buf, user_buf, nbytes)) {
- free_page((unsigned long)buf);
+ kfree(buf);
return ERR_PTR(-EFAULT);
}
@@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port,
out:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
- free_page((unsigned long)buf);
+ kfree(buf);
return ret < 0 ? ret : count;
}
@@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf
out:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
- free_page((unsigned long)buf);
+ kfree(buf);
return ret < 0 ? ret : count;
}
@@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file,
out:
pm_runtime_mark_last_busy(&rt->dev);
pm_runtime_put_autosuspend(&rt->dev);
- free_page((unsigned long)buf);
+ kfree(buf);
return ret < 0 ? ret : count;
}
@@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf,
margining->ber_level = val;
out_free:
- free_page((unsigned long)buf);
+ kfree(buf);
out_unlock:
mutex_unlock(&tb->lock);
@@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf,
}
}
- free_page((unsigned long)buf);
+ kfree(buf);
if (lane == -1)
return -EINVAL;
@@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
else
goto err_free;
- free_page((unsigned long)buf);
+ kfree(buf);
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
if (!margining->software)
@@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
return count;
err_free:
- free_page((unsigned long)buf);
+ kfree(buf);
return -EINVAL;
}
@@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file,
mutex_unlock(&tb->lock);
out_free:
- free_page((unsigned long)buf);
+ kfree(buf);
return ret ? ret : count;
}
@@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file,
mutex_unlock(&tb->lock);
out_free:
- free_page((unsigned long)buf);
+ kfree(buf);
return ret ? ret : count;
}
@@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file,
mutex_unlock(&tb->lock);
out_free:
- free_page((unsigned long)buf);
+ kfree(buf);
return ret ? ret : count;
}
@@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file,
ret = -EINVAL;
}
- free_page((unsigned long)buf);
+ kfree(buf);
return ret ? ret : count;
}
@@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf,
out:
pm_runtime_mark_last_busy(&sw->dev);
pm_runtime_put_autosuspend(&sw->dev);
- free_page((unsigned long)buf);
+ kfree(buf);
return ret < 0 ? ret : count;
}
diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c
index 7877319b1b03..519c67678b08 100644
--- a/drivers/thunderbolt/dma_test.c
+++ b/drivers/thunderbolt/dma_test.c
@@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc)
debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops);
}
-static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id)
+static int dma_test_probe(struct tb_service *svc)
{
struct tb_xdomain *xd = tb_service_parent(svc);
struct dma_test *dt;
@@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = {
static const struct tb_service_id dma_test_ids[] = {
{ TB_SERVICE("dma_test", 1) },
- { },
+ { }
};
MODULE_DEVICE_TABLE(tbsvc, dma_test_ids);
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 479fa4d265c2..12c88509a54f 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev)
{
struct tb_service *svc = tb_to_service(dev);
struct tb_service_driver *driver;
- const struct tb_service_id *id;
driver = container_of(dev->driver, struct tb_service_driver, driver);
- id = __tb_service_match(dev, &driver->driver);
- return driver->probe(svc, id);
+ return driver->probe(svc);
}
static void tb_service_remove(struct device *dev)
@@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
transmit_ring, receive_path, receive_ring);
}
+static void tb_domain_reset_interface(struct tb *tb)
+{
+ struct tb_nhi *nhi = tb->nhi;
+
+ if (!nhi->ops->reset_interface)
+ return;
+
+ guard(mutex)(&tb->lock);
+
+ /* The reset clears the ring state so stop the control channel */
+ tb_ctl_stop(tb->ctl);
+ nhi->ops->reset_interface(nhi);
+ tb_ctl_start(tb->ctl);
+}
+
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
@@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring)
{
+ int ret;
+
if (!tb->cm_ops->disconnect_xdomain_paths)
return -ENOTSUPP;
- return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
+ ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
transmit_ring, receive_path, receive_ring);
+ if (ret)
+ return ret;
+
+ if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN)
+ tb_domain_reset_interface(tb);
+
+ return 0;
}
static int disconnect_xdomain(struct device *dev, void *data)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 35e3c119d5ee..5809809f64d4 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring)
{
struct ring_frame *frame, *n;
struct ring_desc *descriptor;
+ u32 flags;
+
+ flags = RING_DESC_POSTED;
+ if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
+ flags |= RING_DESC_INTERRUPT;
+
list_for_each_entry_safe(frame, n, &ring->queue, list) {
if (ring_full(ring))
break;
@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring)
descriptor = &ring->descriptors[ring->head];
descriptor->phys = frame->buffer_phy;
descriptor->time = 0;
- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT;
+ descriptor->flags = flags;
if (ring->is_tx) {
descriptor->length = frame->size;
descriptor->eof = frame->eof;
@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue);
* @ring: Ring to poll
*
* This function can be called when @start_poll callback of the @ring
- * has been called. It will read one completed frame from the ring and
- * return it to the caller.
+ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT.
+ * It will read one completed frame from the ring and return it to the
+ * caller.
*
* Return: Pointer to &struct ring_frame, %NULL if there is no more
* completed frames.
@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n",
transmit ? "TX" : "RX", hop, size);
+ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) {
+ dev_WARN(nhi->dev,
+ "start_poll() and NO_INTERRUPT cannot be used at the same time\n");
+ return NULL;
+ }
+
ring = kzalloc_obj(*ring);
if (!ring)
return NULL;
@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
if (!ring->descriptors)
goto err_free_ring;
- if (nhi->ops->request_ring_irq) {
+ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) {
if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND))
goto err_free_descs;
}
@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring)
ring_iowrite32options(ring, flags, 0);
}
- ring_interrupt_active(ring, true);
+ if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
+ ring_interrupt_active(ring, true);
ring->running = true;
err:
spin_unlock(&ring->lock);
@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring)
RING_TYPE(ring), ring->hop);
goto err;
}
- ring_interrupt_active(ring, false);
+ if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
+ ring_interrupt_active(ring, false);
ring_iowrite32options(ring, 0, 0);
ring_iowrite64desc(ring, 0, 0);
@@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi)
dev_warn(nhi->dev, "timeout resetting host router\n");
}
+/**
+ * nhi_reset_interface() - Reset the host interface
+ * @nhi: Host interface to reset
+ *
+ * Brings the registers in the memory BAR back to their default state and
+ * clears the End-to-End Flow Control state. The caller is responsible for
+ * stopping the control channel over the reset because it clears the ring
+ * state as well.
+ */
+void nhi_reset_interface(struct tb_nhi *nhi)
+{
+ u32 val;
+
+ val = ioread32(nhi->iobase + REG_CAPS);
+ /* Only v1 host interfaces implement the reset */
+ if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2)
+ return;
+
+ dev_dbg(nhi->dev, "issuing host interface reset\n");
+
+ iowrite32(REG_HOST_INTERFACE_RESET_RST,
+ nhi->iobase + REG_HOST_INTERFACE_RESET);
+ /* Wait for tHIReset (10 ms) to complete */
+ usleep_range(10000, 20000);
+}
+
static struct tb *nhi_select_cm(struct tb_nhi *nhi)
{
struct tb *tb;
@@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi)
dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
+ nhi->host_reset = host_reset;
+
res = tb_domain_add(tb, host_reset);
if (res) {
/*
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index d488eadadfce..f72d6b274501 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data);
irqreturn_t ring_msix(int irq, void *data);
int nhi_probe(struct tb_nhi *nhi);
void nhi_shutdown(struct tb_nhi *nhi);
+void nhi_reset_interface(struct tb_nhi *nhi);
+
extern const struct dev_pm_ops nhi_pm_ops;
/**
@@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
* @release_ring_irq: NHI specific interrupt release hook
* @is_present: Whether the device is currently present on the parent bus
* @init_interrupts: NHI specific interrupt initialization hook
+ * @reset_interface: Resets the host interface
*/
struct tb_nhi_ops {
int (*init)(struct tb_nhi *nhi);
@@ -66,6 +69,7 @@ struct tb_nhi_ops {
void (*release_ring_irq)(struct tb_ring *ring);
bool (*is_present)(struct tb_nhi *nhi);
int (*init_interrupts)(struct tb_nhi *nhi);
+ void (*reset_interface)(struct tb_nhi *nhi);
};
/*
@@ -116,11 +120,24 @@ struct tb_nhi_ops {
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434
+#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120
+#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121
+#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b
+#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c
+#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155
+#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158
+#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159
+#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c
+#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d
+#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d
+#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e
+
#define PCI_CLASS_SERIAL_USB_USB4 0x0c0340
/* Host interface quirks */
-#define QUIRK_AUTO_CLEAR_INT BIT(0)
-#define QUIRK_E2E BIT(1)
+#define QUIRK_AUTO_CLEAR_INT BIT(0)
+#define QUIRK_E2E BIT(1)
+#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2)
/*
* Minimal number of vectors when we use MSI-X. Two for control channel
diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h
index d6a197fabc74..99df60b6db36 100644
--- a/drivers/thunderbolt/nhi_regs.h
+++ b/drivers/thunderbolt/nhi_regs.h
@@ -115,6 +115,10 @@ struct ring_desc {
#define REG_CAPS_VERSION_MASK GENMASK(23, 16)
#define REG_CAPS_VERSION_2 0x40
+/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */
+#define REG_HOST_INTERFACE_RESET 0x39858
+#define REG_HOST_INTERFACE_RESET_RST BIT(0)
+
#define REG_DMA_MISC 0x39864
#define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2)
#define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17)
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
index bbd186c29ef7..99333729f3c2 100644
--- a/drivers/thunderbolt/pci.c
+++ b/drivers/thunderbolt/pci.c
@@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci)
nhi->quirks |= QUIRK_E2E;
break;
}
+ } else if (pdev->vendor == PCI_VENDOR_ID_AMD) {
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1:
+ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0:
+ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1:
+ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0:
+ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1:
+ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2:
+ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0:
+ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1:
+ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0:
+ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1:
+ /*
+ * These AMD hosts may hang the Tx ring when the
+ * DMA paths are torn down so they need the host
+ * interface reset after each teardown.
+ */
+ nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN;
+ break;
+ }
}
}
@@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
{
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
struct pci_dev *pdev = to_pci_dev(nhi->dev);
- struct device *dev = &pdev->dev;
int res, irq, nvec;
ida_init(&nhi_pci->msix_ida);
@@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
res = devm_request_irq(&pdev->dev, irq, nhi_msi,
IRQF_NO_SUSPEND, "thunderbolt", nhi);
if (res)
- return dev_err_probe(dev, res, "request_irq failed, aborting\n");
+ return res;
}
return 0;
@@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring)
ring->irq = 0;
}
-static void nhi_pci_shutdown(struct tb_nhi *nhi)
+static void nhi_pci_release_irq(struct tb_nhi *nhi)
{
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
struct pci_dev *pdev = to_pci_dev(nhi->dev);
@@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = {
.post_nvm_auth = nhi_pci_complete_dma_port,
.request_ring_irq = nhi_pci_ring_request_msix,
.release_ring_irq = nhi_pci_ring_release_msix,
- .shutdown = nhi_pci_shutdown,
+ .shutdown = nhi_pci_release_irq,
.is_present = nhi_pci_is_present,
.init_interrupts = nhi_pci_init_msi,
+ .reset_interface = nhi_reset_interface,
};
/* Ice Lake specific NHI operations */
@@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi)
static void icl_nhi_shutdown(struct tb_nhi *nhi)
{
- nhi_pci_shutdown(nhi);
+ nhi_pci_release_irq(nhi);
icl_nhi_force_power(nhi, false);
}
@@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = {
.release_ring_irq = nhi_pci_ring_release_msix,
.is_present = nhi_pci_is_present,
.init_interrupts = nhi_pci_init_msi,
+ .reset_interface = nhi_reset_interface,
};
static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
@@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return nhi_probe(&nhi_pci->nhi);
}
-static void nhi_pci_remove(struct pci_dev *pdev)
+static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset)
{
struct tb *tb = pci_get_drvdata(pdev);
struct tb_nhi *nhi = tb->nhi;
+ /*
+ * On system shutdown/reboot force a host router reset so the
+ * connection manager asserts DPR on connected Thunderbolt 3 devices
+ * before the router tree is removed (see tb_stop()).
+ */
+ if (reset)
+ nhi->host_reset = true;
+
pm_runtime_get_sync(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_forbid(&pdev->dev);
@@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev)
nhi_shutdown(nhi);
}
+static void nhi_pci_remove(struct pci_dev *pdev)
+{
+ nhi_pci_do_remove(pdev, false);
+}
+
+static void nhi_pci_shutdown(struct pci_dev *pdev)
+{
+ nhi_pci_do_remove(pdev, true);
+}
+
static struct pci_device_id nhi_ids[] = {
/*
* We have to specify class, the TB bridges use the same device and
@@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = {
.id_table = nhi_ids,
.probe = nhi_pci_probe,
.remove = nhi_pci_remove,
- .shutdown = nhi_pci_remove,
+ .shutdown = nhi_pci_shutdown,
.driver.pm = &nhi_pm_ops,
};
diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c
index 4cc86d8d6491..c737dd0ca6e7 100644
--- a/drivers/thunderbolt/stream.c
+++ b/drivers/thunderbolt/stream.c
@@ -9,10 +9,12 @@
#define pr_fmt(fmt) "tbstream: " fmt
+#include <linux/delay.h>
#include <linux/configfs.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/idr.h>
+#include <linux/ktime.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -128,6 +130,7 @@ struct tbstream_ring {
* @out_hopid: Out HopID
* @ring_size: Size of the rings
* @throttling: Interrupt throttling rate in ns
+ * @busy_poll: Instead of interrupts, busy poll the rings
* @users: Number of times @cdev has been opened
* @closed: CLOSE packet was received
* @removed: Userspace removed the ConfigFS group underneath.
@@ -147,6 +150,7 @@ struct tbstream_dev {
int out_hopid;
unsigned int ring_size;
unsigned int throttling;
+ bool busy_poll;
int users;
bool closed;
bool removed;
@@ -512,8 +516,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf,
dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size,
DMA_TO_DEVICE);
if (pdf == TBSTREAM_DATA) {
- if (copy_page_from_iter(sf->page, 0, size, from) != size)
+ if (copy_page_from_iter(sf->page, 0, size, from) != size) {
+ sdev->tx_ring.cons--;
return ERR_PTR(-EFAULT);
+ }
} else {
memset(page_address(sf->page), 0, size);
}
@@ -534,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from,
return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
}
+static void
+tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring)
+{
+ struct ring_frame *frame;
+
+ if (!sdev->busy_poll)
+ return;
+
+ while ((frame = tb_ring_poll(ring->ring)))
+ frame->callback(ring->ring, frame, false);
+}
+
static int tbstream_dev_send_close(struct tbstream_dev *sdev)
{
struct tbstream_frame *sf;
+ if (sdev->busy_poll) {
+ /*
+ * When busy polling it's the write(2) path that
+ * advances the completions so it is possible that the
+ * ring is full at this point. Advance the ring here so
+ * that there is room for the CLOSE packet to be sent.
+ */
+ ktime_t timeout = ktime_add_ms(ktime_get(), 500);
+
+ do {
+ if (tbstream_ring_available(&sdev->tx_ring))
+ break;
+ tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
+ fsleep(15);
+ } while (ktime_before(ktime_get(), timeout));
+ }
+
sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256);
if (IS_ERR(sf))
return PTR_ERR(sf);
@@ -547,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev)
static int tbstream_dev_start(struct tbstream_dev *sdev)
{
struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
+ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E;
u16 sof_mask, eof_mask;
struct tb_ring *ring;
int ret, e2e_tx_hop;
- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size,
- RING_FLAG_FRAME | RING_FLAG_E2E);
+ if (sdev->busy_poll)
+ flags |= RING_FLAG_NO_INTERRUPT;
+
+ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags);
if (!ring)
return -ENOMEM;
sdev->tx_ring.ring = ring;
@@ -565,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
sof_mask = BIT(TBSTREAM_FRAME_START);
eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE);
- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size,
- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop,
- sof_mask, eof_mask, NULL, NULL);
+ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags,
+ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL);
if (!ring) {
ret = -ENOMEM;
goto err_free_tx_buffers;
@@ -605,15 +642,43 @@ err_free_tx:
return ret;
}
+static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev)
+{
+ const struct tbstream_ring *ring = &sdev->tx_ring;
+
+ /*
+ * Everything is completed when number of free TX slots is back
+ * to the maximum.
+ */
+ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1;
+}
+
static void tbstream_dev_stop(struct tbstream_dev *sdev)
{
struct tb_xdomain *xd;
- /* Wait for the ring to complete any outstanding frames */
- tb_ring_flush(sdev->tx_ring.ring, 500);
- tb_ring_stop(sdev->tx_ring.ring);
- tb_ring_flush(sdev->rx_ring.ring, 500);
- tb_ring_stop(sdev->rx_ring.ring);
+ if (sdev->busy_poll) {
+ /*
+ * When busy polling we must advance the ring ourselves
+ * to push all outstanding frames on the wire.
+ */
+ ktime_t timeout = ktime_add_ms(ktime_get(), 500);
+
+ do {
+ if (tbstream_dev_tx_drained(sdev))
+ break;
+ tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
+ fsleep(15);
+ } while (ktime_before(ktime_get(), timeout));
+
+ tb_ring_stop(sdev->tx_ring.ring);
+ tb_ring_stop(sdev->rx_ring.ring);
+ } else {
+ tb_ring_flush(sdev->tx_ring.ring, 500);
+ tb_ring_stop(sdev->tx_ring.ring);
+ tb_ring_flush(sdev->rx_ring.ring, 500);
+ tb_ring_stop(sdev->rx_ring.ring);
+ }
xd = tbstream_dev_xdomain(sdev);
if (xd) {
@@ -631,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev)
sdev->tx_ring.ring = NULL;
}
+/* Use only with read_iter/write_iter() to handle nowait */
+static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait)
+{
+ if (nowait) {
+ if (!mutex_trylock(&sdev->lock))
+ return -EAGAIN;
+ } else {
+ if (mutex_lock_interruptible(&sdev->lock))
+ return -ERESTARTSYS;
+ }
+ return 0;
+}
+
static ssize_t
tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
{
struct file *file = kiocb->ki_filp;
+ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT;
struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
size_t nbytes;
int ret;
@@ -643,35 +722,54 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
if (ret)
return ret;
- if (mutex_lock_interruptible(&sdev->lock))
- return -ERESTARTSYS;
+ ret = tbstream_dev_lock(sdev, nowait);
+ if (ret)
+ return ret;
- while (!tbstream_ring_available(&sdev->rx_ring)) {
- mutex_unlock(&sdev->lock);
-
- if (file->f_flags & O_NONBLOCK)
- return -EAGAIN;
- ret = wait_event_interruptible(sdev->wait,
- tbstream_ring_available(&sdev->rx_ring) ||
- tbstream_dev_valid(sdev) != 0 ||
- tbstream_dev_closed(sdev) ||
- tbstream_dev_removed(sdev));
- if (ret)
- return ret;
+ for (;;) {
+ /* When busy polling, advance any completions manually */
+ tbstream_dev_poll_ring(sdev, &sdev->rx_ring);
ret = tbstream_dev_valid(sdev);
- if (ret)
+ if (ret) {
+ mutex_unlock(&sdev->lock);
return ret;
+ }
- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
+ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) {
+ mutex_unlock(&sdev->lock);
return 0;
+ }
- if (mutex_lock_interruptible(&sdev->lock))
- return -ERESTARTSYS;
+ if (tbstream_ring_available(&sdev->rx_ring))
+ break;
+
+ mutex_unlock(&sdev->lock);
+
+ if (nowait)
+ return -EAGAIN;
+
+ if (sdev->busy_poll) {
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ cond_resched();
+ } else {
+ ret = wait_event_interruptible(sdev->wait,
+ tbstream_ring_available(&sdev->rx_ring) ||
+ tbstream_dev_valid(sdev) != 0 ||
+ tbstream_dev_closed(sdev) ||
+ tbstream_dev_removed(sdev));
+ if (ret)
+ return ret;
+ }
+
+ ret = tbstream_dev_lock(sdev, nowait);
+ if (ret)
+ return ret;
}
nbytes = 0;
- while (nbytes < iov_iter_count(to)) {
+ while (iov_iter_count(to)) {
struct tbstream_frame *sf;
size_t size, sf_size;
@@ -693,7 +791,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
}
sf_size = tb_ring_frame_size(&sf->frame);
- size = min(iov_iter_count(to) - nbytes, sf_size);
+ size = min(iov_iter_count(to), sf_size);
if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) {
ret = -EFAULT;
@@ -727,6 +825,7 @@ static ssize_t
tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
{
struct file *file = kiocb->ki_filp;
+ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT;
struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
size_t nbytes;
int ret;
@@ -735,38 +834,56 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
if (ret)
return ret;
- if (mutex_lock_interruptible(&sdev->lock))
- return -ERESTARTSYS;
+ ret = tbstream_dev_lock(sdev, nowait);
+ if (ret)
+ return ret;
- while (!tbstream_ring_available(&sdev->tx_ring)) {
- mutex_unlock(&sdev->lock);
-
- if (file->f_flags & O_NONBLOCK)
- return -EAGAIN;
- ret = wait_event_interruptible(sdev->wait,
- tbstream_ring_available(&sdev->tx_ring) ||
- tbstream_dev_valid(sdev) != 0 ||
- tbstream_dev_closed(sdev) ||
- tbstream_dev_removed(sdev));
- if (ret)
- return ret;
+ for (;;) {
+ tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
ret = tbstream_dev_valid(sdev);
- if (ret)
+ if (ret) {
+ mutex_unlock(&sdev->lock);
return ret;
+ }
- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
+ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) {
+ mutex_unlock(&sdev->lock);
return -ENXIO;
+ }
- if (mutex_lock_interruptible(&sdev->lock))
- return -ERESTARTSYS;
+ if (tbstream_ring_available(&sdev->tx_ring))
+ break;
+
+ mutex_unlock(&sdev->lock);
+
+ if (nowait)
+ return -EAGAIN;
+
+ if (sdev->busy_poll) {
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ cond_resched();
+ } else {
+ ret = wait_event_interruptible(sdev->wait,
+ tbstream_ring_available(&sdev->tx_ring) ||
+ tbstream_dev_valid(sdev) != 0 ||
+ tbstream_dev_closed(sdev) ||
+ tbstream_dev_removed(sdev));
+ if (ret)
+ return ret;
+ }
+
+ ret = tbstream_dev_lock(sdev, nowait);
+ if (ret)
+ return ret;
}
nbytes = 0;
- while (nbytes < iov_iter_count(from)) {
+ while (iov_iter_count(from)) {
size_t size;
- size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE);
+ size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE);
ret = tbstream_dev_send_data(sdev, from, size);
if (ret) {
/*
@@ -793,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait)
struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
__poll_t mask = 0;
+ /*
+ * Without interrupts there is nothing that can wake us up so
+ * return failure instead.
+ */
+ if (sdev->busy_poll)
+ return EPOLLERR;
+
poll_wait(file, &sdev->wait, wait);
guard(mutex)(&sdev->lock);
if (tbstream_dev_valid(sdev) != 0) {
@@ -902,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group)
return container_of(group, struct tbstream_dev, group);
}
+static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ return sysfs_emit(buf, "%u\n", sdev->busy_poll);
+}
+
+static ssize_t
+tbstream_dev_busy_poll_store(struct config_item *item, const char *buf,
+ size_t count)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+ bool busy_poll;
+ int ret;
+
+ ret = kstrtobool(buf, &busy_poll);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&sdev->lock);
+ if (sdev->users)
+ return -EBUSY;
+ sdev->busy_poll = busy_poll;
+ return count;
+}
+CONFIGFS_ATTR(tbstream_dev_, busy_poll);
+
static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf)
{
struct config_group *group = to_config_group(item);
@@ -1208,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf,
CONFIGFS_ATTR(tbstream_dev_, throttling);
static struct configfs_attribute *tbstream_dev_attrs[] = {
+ &tbstream_dev_attr_busy_poll,
&tbstream_dev_attr_index,
&tbstream_dev_attr_in_hopid,
&tbstream_dev_attr_out_hopid,
@@ -1540,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream)
config_group_put(&sg->group);
}
-static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id)
+static int tbstream_probe(struct tb_service *svc)
{
struct tbstream *stream;
@@ -1630,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = {
static const struct tb_service_id tbstream_ids[] = {
{ TB_SERVICE("stream", 1) },
- { },
+ { }
};
MODULE_DEVICE_TABLE(tbsvc, tbstream_ids);
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index a830c82bb905..404c0693df50 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port)
return __tb_port_enable(port, false);
}
-static int tb_port_reset(struct tb_port *port)
+/**
+ * tb_port_reset() - Reset the port
+ * @port: Port to reset
+ *
+ * Resets @port. For USB4 ports this issues a USB4 port reset and for
+ * legacy ports the link controller port is reset.
+ *
+ * Return: %0 on success, negative errno otherwise.
+ */
+int tb_port_reset(struct tb_port *port)
{
if (tb_switch_is_usb4(port->sw))
return port->cap_usb4 ? usb4_port_reset(port) : 0;
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index f43f2d952372..47753a5c0f2e 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
static void tb_stop(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
+ struct tb_nhi *nhi = tb->nhi;
struct tb_tunnel *tunnel;
+ struct tb_port *port;
struct tb_tunnel *n;
cancel_delayed_work(&tcm->remove_work);
@@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb)
tb_tunnel_deactivate(tunnel);
tb_tunnel_put(tunnel);
}
+ /*
+ * Signal disconnect to connected devices before the router tree is
+ * removed below. A Thunderbolt 3 device directly connected to a USB4
+ * host otherwise never receives a disconnect indication, leaving
+ * firmware to poll the dead link for up to ~60 s which on some
+ * platforms turns the shutdown into a warm reset. Asserting
+ * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device
+ * detects SBRX low and goes to Uninitialized Unplugged immediately.
+ */
+ if (nhi->host_reset) {
+ tb_switch_for_each_port(tb->root_switch, port) {
+ if (!tb_port_is_null(port) || !tb_port_has_remote(port))
+ continue;
+ if (tb_switch_is_usb4(port->remote->sw))
+ continue;
+ if (tb_port_reset(port))
+ tb_port_dbg(port, "downstream port reset failed, continuing\n");
+ }
+ }
tb_switch_remove(tb->root_switch);
tb->root_switch = NULL;
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index ec9192b61bc0..4373336d9425 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter);
int tb_port_unlock(struct tb_port *port);
int tb_port_enable(struct tb_port *port);
int tb_port_disable(struct tb_port *port);
+int tb_port_reset(struct tb_port *port);
int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
void tb_port_release_in_hopid(struct tb_port *port, int hopid);
int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid);
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index b7f32305f14a..7e8284575dff 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -48,6 +48,9 @@
#define TB_DP_AUX_PRIORITY 2
#define TB_DP_AUX_WEIGHT 1
+/* struct tb_regs_hop::initial_credits is 7 bits wide */
+#define TB_MAX_CREDITS 127
+
/* Minimum number of credits needed for PCIe path */
#define TB_MIN_PCIE_CREDITS 6U
/*
@@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits)
if (available < TB_MIN_DMA_CREDITS)
return -ENOSPC;
- while (credits > available)
- credits--;
+ credits = min(credits, available);
tb_port_dbg(port, "reserving %u credits for DMA path\n",
credits);
@@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
struct tb_tunnel *tunnel;
size_t npaths = 0, i = 0;
struct tb_path *path;
- int credits;
+ unsigned int credits;
/* Ring 0 is reserved for control channel */
if (WARN_ON(!receive_ring || !transmit_ring))
@@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
tunnel->destroy = tb_dma_destroy;
credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits);
+ if (credits > TB_MAX_CREDITS) {
+ tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n",
+ credits, TB_MAX_CREDITS);
+ credits = TB_MAX_CREDITS;
+ }
if (receive_ring > 0) {
path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0,
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 86b2f7474670..c179bd751fe4 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr,
*/
int tb_register_service_driver(struct tb_service_driver *drv)
{
+ if (!drv->probe)
+ return -EINVAL;
+
drv->driver.bus = &tb_bus_type;
return driver_register(&drv->driver);
}
@@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work)
tb_xdomain_failed(xd);
} else {
xd->state = XDOMAIN_STATE_ENUMERATED;
+ tb_xdomain_queue_properties_changed(xd);
}
break;
diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c
index 0befbf63d1cc..5c1e1f521555 100644
--- a/drivers/usb/atm/xusbatm.c
+++ b/drivers/usb/atm/xusbatm.c
@@ -79,7 +79,7 @@ static int xusbatm_bind(struct usbatm_data *usbatm,
struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *usb_dev = interface_to_usbdev(intf);
- int drv_ix = id - xusbatm_usb_ids;
+ int drv_ix = id->driver_info;
int rx_alt = rx_altsetting[drv_ix];
int tx_alt = tx_altsetting[drv_ix];
struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]);
@@ -168,7 +168,8 @@ static struct usb_driver xusbatm_usb_driver = {
.name = xusbatm_driver_name,
.probe = xusbatm_usb_probe,
.disconnect = usbatm_usb_disconnect,
- .id_table = xusbatm_usb_ids
+ .id_table = xusbatm_usb_ids,
+ .no_dynamic_id = 1,
};
static int __init xusbatm_init(void)
@@ -190,6 +191,7 @@ static int __init xusbatm_init(void)
xusbatm_usb_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
xusbatm_usb_ids[i].idVendor = vendor[i];
xusbatm_usb_ids[i].idProduct = product[i];
+ xusbatm_usb_ids[i].driver_info = i;
xusbatm_drivers[i].driver_name = xusbatm_driver_name;
xusbatm_drivers[i].bind = xusbatm_bind;
diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c
index a832f5696f2a..ae6c94f41cb3 100644
--- a/drivers/usb/c67x00/c67x00-sched.c
+++ b/drivers/usb/c67x00/c67x00-sched.c
@@ -761,13 +761,13 @@ static int c67x00_add_iso_urb(struct c67x00_hcd *c67x00, struct urb *urb)
ret);
urb->iso_frame_desc[urbp->cnt].actual_length = 0;
urb->iso_frame_desc[urbp->cnt].status = ret;
- if (urbp->cnt + 1 == urb->number_of_packets)
- c67x00_giveback_urb(c67x00, urb, 0);
}
urbp->ep_data->next_frame =
frame_add(urbp->ep_data->next_frame, urb->interval);
urbp->cnt++;
+ if (ret && urbp->cnt == urb->number_of_packets)
+ c67x00_giveback_urb(c67x00, urb, 0);
}
return 0;
}
diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c
index bb5405460035..e3f32c3e9535 100644
--- a/drivers/usb/cdns3/cdns3-plat.c
+++ b/drivers/usb/cdns3/cdns3-plat.c
@@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev)
cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy");
if (IS_ERR(cdns->usb2_phy))
return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy),
- "Failed to get cdn3,usb2-phy\n");
+ "Failed to get cdns3,usb2-phy\n");
cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy");
if (IS_ERR(cdns->usb3_phy))
return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy),
- "Failed to get cdn3,usb3-phy\n");
+ "Failed to get cdns3,usb3-phy\n");
ret = phy_init(cdns->usb2_phy);
if (ret)
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 56d2ba824a0b..282314eea7fc 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -528,7 +528,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
if (data->wakeup_irq > 0) {
irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name);
if (!irq_name) {
- dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
+ ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
goto err_clk;
}
diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c
index af9ae55dae14..51cd9320a736 100644
--- a/drivers/usb/class/usbtmc.c
+++ b/drivers/usb/class/usbtmc.c
@@ -71,7 +71,6 @@ struct usbtmc_dev_capabilities {
* allocated for each USBTMC device in the driver's probe function.
*/
struct usbtmc_device_data {
- const struct usb_device_id *id;
struct usb_device *usb_dev;
struct usb_interface *intf;
struct list_head file_list;
@@ -2394,7 +2393,6 @@ static int usbtmc_probe(struct usb_interface *intf,
return -ENOMEM;
data->intf = intf;
- data->id = id;
data->usb_dev = usb_get_dev(interface_to_usbdev(intf));
usb_set_intfdata(intf, data);
kref_init(&data->kref);
diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
index 346a2faa9bb8..0e79b7f8a73d 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno,
usb_endpoint_xfer_int(&ep->desc)) &&
desc->bmAttributes != 0) {
dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n",
- usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk",
+ usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt",
desc->bmAttributes,
cfgno, inum, asnum, ep->desc.bEndpointAddress);
ep->ss_ep_comp.bmAttributes = 0;
diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c
index a247da73f34d..6f0354aba38b 100644
--- a/drivers/usb/core/devices.c
+++ b/drivers/usb/core/devices.c
@@ -37,6 +37,7 @@
*/
#include <linux/fs.h>
+#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/usb.h>
@@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes,
return 0;
/* allocate 2^1 pages = 8K (on i386);
* should be more than enough for one device */
- pages_start = (char *)__get_free_pages(GFP_NOIO, 1);
+ pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO);
if (!pages_start)
return -ENOMEM;
@@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes,
if (length > *nbytes)
length = *nbytes;
if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) {
- free_pages((unsigned long)pages_start, 1);
+ kfree(pages_start);
return -EFAULT;
}
*nbytes -= length;
@@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes,
} else
*skip_bytes -= length;
- free_pages((unsigned long)pages_start, 1);
+ kfree(pages_start);
/* Now look at all of this device's children. */
usb_hub_for_each_child(usbdev, chix, childdev) {
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index e191934623c7..101cb9425480 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -1113,7 +1113,6 @@ static int usbdev_release(struct inode *inode, struct file *file)
if (!ps->suspend_allowed)
usb_autosuspend_device(dev);
usb_unlock_device(dev);
- usb_put_dev(dev);
put_pid(ps->disc_pid);
put_cred(ps->cred);
@@ -1122,6 +1121,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
free_async(as);
as = async_getcompleted(ps);
}
+ usb_put_dev(dev);
kfree(ps);
return 0;
@@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
if (!connected(ps))
return -ENODEV;
+ if (ps->dev->state != USB_STATE_CONFIGURED)
+ return -EHOSTUNREACH;
+
+ intf = usb_ifnum_to_if(ps->dev, ctl->ifno);
+ if (!intf)
+ return -EINVAL;
+
/* alloc buffer */
size = _IOC_SIZE(ctl->ioctl_code);
if (size > 0) {
@@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
}
}
- if (ps->dev->state != USB_STATE_CONFIGURED)
- retval = -EHOSTUNREACH;
- else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno)))
- retval = -EINVAL;
- else switch (ctl->ioctl_code) {
+ switch (ctl->ioctl_code) {
/* disconnect kernel driver from interface */
case USBDEVFS_DISCONNECT:
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index f63004417058..7f33fe5ba03b 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -228,14 +228,16 @@ static void usb_free_dynids(struct usb_driver *usb_drv)
}
static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf,
- const struct usb_driver *drv)
+ const struct usb_driver *drv,
+ struct usb_device_id *id_copy)
{
struct usb_dynid *dynid;
guard(mutex)(&usb_dynids_lock);
list_for_each_entry(dynid, &drv->dynids.list, node) {
if (usb_match_one_id(intf, &dynid->id)) {
- return &dynid->id;
+ *id_copy = dynid->id;
+ return id_copy;
}
}
return NULL;
@@ -321,6 +323,7 @@ static int usb_probe_interface(struct device *dev)
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *udev = interface_to_usbdev(intf);
const struct usb_device_id *id;
+ struct usb_device_id id_copy;
int error = -ENODEV;
int lpm_disable_error = -ENODEV;
@@ -340,7 +343,7 @@ static int usb_probe_interface(struct device *dev)
return error;
}
- id = usb_match_dynamic_id(intf, driver);
+ id = usb_match_dynamic_id(intf, driver, &id_copy);
if (!id)
id = usb_match_id(intf, driver->id_table);
if (!id)
@@ -892,6 +895,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)
struct usb_interface *intf;
const struct usb_driver *usb_drv;
const struct usb_device_id *id;
+ struct usb_device_id id_copy;
/* device drivers never match interfaces */
if (is_usb_device_driver(drv))
@@ -904,7 +908,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)
if (id)
return 1;
- id = usb_match_dynamic_id(intf, usb_drv);
+ id = usb_match_dynamic_id(intf, usb_drv, &id_copy);
if (id)
return 1;
}
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index d92bf887739d..3345b3298daf 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
mutex_lock(&hub->status_mutex);
ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len);
if (ret < len) {
- if (ret != -ENODEV)
- dev_err(hub->intfdev,
- "%s failed (err = %d)\n", __func__, ret);
if (ret >= 0)
ret = -EIO;
+ if (ret != -ENODEV)
+ dev_dbg(hub->intfdev,
+ "get_port_status failed: err = %d\n", ret);
} else {
*status = le16_to_cpu(hub->status->port.wPortStatus);
*change = le16_to_cpu(hub->status->port.wPortChange);
@@ -753,10 +753,12 @@ void usb_wakeup_notification(struct usb_device *hdev,
{
struct usb_hub *hub;
struct usb_port *port_dev;
+ unsigned long flags;
if (!hdev)
return;
+ spin_lock_irqsave(&device_state_lock, flags);
hub = usb_hub_to_struct_hub(hdev);
if (hub) {
port_dev = hub->ports[portnum - 1];
@@ -766,6 +768,7 @@ void usb_wakeup_notification(struct usb_device *hdev,
set_bit(portnum, hub->wakeup_bits);
kick_hub_wq(hub);
}
+ spin_unlock_irqrestore(&device_state_lock, flags);
}
EXPORT_SYMBOL_GPL(usb_wakeup_notification);
@@ -991,10 +994,12 @@ static int hub_hub_status(struct usb_hub *hub,
mutex_lock(&hub->status_mutex);
ret = get_hub_status(hub->hdev, &hub->status->hub);
- if (ret < 0) {
+ if (ret < (int)sizeof(hub->status->hub)) {
+ if (ret >= 0)
+ ret = -EIO;
if (ret != -ENODEV)
- dev_err(hub->intfdev,
- "%s failed (err = %d)\n", __func__, ret);
+ dev_dbg(hub->intfdev,
+ "get_hub_status failed: err = %d\n", ret);
} else {
*status = le16_to_cpu(hub->status->hub.wHubStatus);
*change = le16_to_cpu(hub->status->hub.wHubChange);
diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c
index 7d3e641806f8..9565e6a52725 100644
--- a/drivers/usb/dwc2/core_intr.c
+++ b/drivers/usb/dwc2/core_intr.c
@@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg)
/**
* dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state
- *
* @hsotg: Programming view of DWC_otg controller
- *
+ * @remotewakeup: Whether this is a remote wakeup
*/
void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup)
{
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index c8b02c27d27d..102169b40419 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -4680,6 +4680,7 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
{
struct dwc2_hsotg *hsotg = to_hsotg(gadget);
unsigned long flags;
+ int ret = 0;
dev_dbg(hsotg->dev, "%s: is_on: %d op_state: %d\n", __func__, is_on,
hsotg->op_state);
@@ -4691,6 +4692,13 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
}
spin_lock_irqsave(&hsotg->lock, flags);
+ if (hsotg->in_ppd) {
+ ret = dwc2_exit_partial_power_down(hsotg, 0, true);
+ if (ret) {
+ dev_err(hsotg->dev, "exit partial_power_down failed\n");
+ goto exit;
+ }
+ }
if (is_on) {
hsotg->enabled = 1;
dwc2_hsotg_core_init_disconnected(hsotg, false);
@@ -4704,9 +4712,10 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
}
hsotg->gadget.speed = USB_SPEED_UNKNOWN;
+exit:
spin_unlock_irqrestore(&hsotg->lock, flags);
- return 0;
+ return ret;
}
static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index ceb49f2f8004..fd5c2cd36c59 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work)
ret, dwc->current_limit);
}
-static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc)
+static int dwc3_psy_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
{
- struct power_supply *usb_psy;
- const char *usb_psy_name;
+ struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb);
+ struct power_supply *psy = data;
+ unsigned long flags;
+
+ if (dwc->usb_psy)
+ return NOTIFY_DONE;
+
+ if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0)
+ return NOTIFY_DONE;
+
+ /* Explicitly get the reference for this psy */
+ psy = power_supply_get_by_name(dwc->usb_psy_name);
+ if (!psy)
+ return NOTIFY_DONE;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ /*
+ * The USB power_supply may already be set. This can happen if notifier
+ * callbacks for the USB power_supply race, or if a previous notifier
+ * callback has already successfully fetched and associated the instance.
+ * In such cases, release the newly acquired reference and ignore
+ * subsequent notifications until the notifier is unregistered.
+ */
+ if (dwc->usb_psy) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ power_supply_put(psy);
+ return NOTIFY_DONE;
+ }
+
+ dwc->usb_psy = psy;
+ if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED)
+ schedule_work(&dwc->vbus_draw_work);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return NOTIFY_OK;
+}
+
+static void dwc3_get_usb_power_supply(struct dwc3 *dwc)
+{
+ struct power_supply *psy;
+ unsigned long flags;
int ret;
- ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name);
+ ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name);
if (ret < 0)
- return NULL;
-
- usb_psy = power_supply_get_by_name(usb_psy_name);
- if (!usb_psy)
- return ERR_PTR(-EPROBE_DEFER);
+ return;
INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work);
- return usb_psy;
+
+ dwc->current_limit = DWC3_CURRENT_UNSPECIFIED;
+ dwc->psy_nb.notifier_call = dwc3_psy_notifier;
+ ret = power_supply_reg_notifier(&dwc->psy_nb);
+ if (ret) {
+ dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret);
+ dwc->psy_nb.notifier_call = NULL;
+ return;
+ }
+
+ psy = power_supply_get_by_name(dwc->usb_psy_name);
+ if (!psy)
+ return;
+
+ /* Unregister the notifier now that we have the power supply */
+ power_supply_unreg_notifier(&dwc->psy_nb);
+ dwc->psy_nb.notifier_call = NULL;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ /*
+ * It is possible that the notifier callback ran before we reached here
+ * and successfully fetched the power supply. In that case we need to
+ * release the above reference.
+ */
+ if (dwc->usb_psy) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ power_supply_put(psy);
+ return;
+ }
+
+ dwc->usb_psy = psy;
+ spin_unlock_irqrestore(&dwc->lock, flags);
}
int dwc3_core_probe(const struct dwc3_probe_data *data)
@@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
dwc3_get_software_properties(dwc, &data->properties);
- dwc->usb_psy = dwc3_get_usb_power_supply(dwc);
- if (IS_ERR(dwc->usb_psy))
- return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n");
+ spin_lock_init(&dwc->lock);
+
+ dwc3_get_usb_power_supply(dwc);
if (!data->ignore_clocks_and_resets) {
dwc->reset = devm_reset_control_array_get_optional_shared(dev);
@@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
dwc->num_usb3_ports = 1;
}
- spin_lock_init(&dwc->lock);
mutex_init(&dwc->mutex);
pm_runtime_get_noresume(dev);
@@ -2373,6 +2439,8 @@ err_disable_clks:
err_assert_reset:
reset_control_assert(dwc->reset);
err_put_psy:
+ if (dwc->psy_nb.notifier_call)
+ power_supply_unreg_notifier(&dwc->psy_nb);
if (dwc->usb_psy)
power_supply_put(dwc->usb_psy);
@@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc)
dwc3_free_event_buffers(dwc);
+ if (dwc->psy_nb.notifier_call)
+ power_supply_unreg_notifier(&dwc->psy_nb);
+
if (dwc->usb_psy) {
cancel_work_sync(&dwc->vbus_draw_work);
power_supply_put(dwc->usb_psy);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index e0dee9d28740..608daeb7ef10 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -722,7 +722,6 @@ struct dwc3_event_buffer {
* @cancelled_list: list of cancelled requests for this endpoint
* @pending_list: list of pending requests for this endpoint
* @started_list: list of started requests on this endpoint
- * @regs: pointer to first endpoint register
* @trb_pool: array of transaction buffers
* @trb_pool_dma: dma address of @trb_pool
* @trb_enqueue: enqueue 'pointer' into TRB array
@@ -1059,6 +1058,8 @@ struct dwc3_glue_ops {
* @role_switch_default_mode: default operation mode of controller while
* usb role is USB_ROLE_NONE.
* @usb_psy: pointer to power supply interface.
+ * @usb_psy_name: name of the USB power supply
+ * @psy_nb: power supply notifier block
* @vbus_draw_work: Work to set the vbus drawing limit
* @current_limit: How much current to draw from vbus, in milliAmperes.
* @usb2_phy: pointer to USB2 PHY
@@ -1251,9 +1252,13 @@ struct dwc3 {
enum usb_dr_mode role_switch_default_mode;
struct power_supply *usb_psy;
+ const char *usb_psy_name;
+ struct notifier_block psy_nb;
struct work_struct vbus_draw_work;
unsigned int current_limit;
+#define DWC3_CURRENT_UNSPECIFIED UINT_MAX
+
u32 fladj;
u32 ref_clk_per;
u32 irq_gadget;
diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c
index e11d7643f966..632634d6e81e 100644
--- a/drivers/usb/dwc3/dwc3-am62.c
+++ b/drivers/usb/dwc3/dwc3-am62.c
@@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62)
dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg);
- clk_prepare_enable(am62->usb2_refclk);
+ ret = clk_prepare_enable(am62->usb2_refclk);
+ if (ret)
+ return ret;
/* Set mode valid bit to indicate role is valid */
reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL);
@@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev)
{
struct dwc3_am62 *am62 = dev_get_drvdata(dev);
u32 reg;
+ int ret;
reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG);
if (reg != USBSS_DEBUG_CFG_DISABLED) {
/* lost power/context */
- dwc3_ti_init(am62);
+ ret = dwc3_ti_init(am62);
+ if (ret)
+ return ret;
} else {
dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF);
- clk_prepare_enable(am62->usb2_refclk);
+ ret = clk_prepare_enable(am62->usb2_refclk);
+ if (ret)
+ return ret;
}
if (device_may_wakeup(dev)) {
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index f43f73ac36ff..ac68b4218b56 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on)
pm_runtime_mark_last_busy(qcom->dev);
}
-struct dwc3_glue_ops dwc3_qcom_glue_ops = {
+static struct dwc3_glue_ops dwc3_qcom_glue_ops = {
.pre_set_role = dwc3_qcom_set_role_notifier,
.pre_run_stop = dwc3_qcom_run_stop_notifier,
};
diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c
index 9b9525592a85..b832505e1b04 100644
--- a/drivers/usb/dwc3/dwc3-xilinx.c
+++ b/drivers/usb/dwc3/dwc3-xilinx.c
@@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data)
dwc3_xlnx_mask_phy_rst(priv_data, false);
- /* Assert and De-assert reset */
- ret = reset_control_assert(crst);
- if (ret < 0) {
- dev_err_probe(dev, ret, "failed to assert Reset\n");
- return ret;
- }
-
- ret = reset_control_deassert(crst);
- if (ret < 0) {
- dev_err_probe(dev, ret, "failed to De-assert Reset\n");
- return ret;
- }
+ /* assert and deassert reset */
+ ret = reset_control_reset(crst);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to assert and deassert reset\n");
dwc3_xlnx_mask_phy_rst(priv_data, true);
dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG);
@@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data)
}
if (priv_data->usb3_phy) {
- /* Set PIPE Power Present signal in FPD Power Present Register*/
+ /* Set PIPE Power Present signal in FPD Power Present Register */
writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT);
/* Set the PIPE Clock Select bit in FPD PIPE Clock register */
writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK);
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 1082e9c9afaa..fa944856f956 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -3124,15 +3124,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g,
static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
{
struct dwc3 *dwc = gadget_to_dwc(g);
+ unsigned long flags;
if (dwc->usb2_phy)
return usb_phy_set_power(dwc->usb2_phy, mA);
- if (!dwc->usb_psy)
- return -EOPNOTSUPP;
+ spin_lock_irqsave(&dwc->lock, flags);
+ if (!dwc->usb_psy) {
+ if (!dwc->psy_nb.notifier_call) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return -EOPNOTSUPP;
+ }
+ dwc->current_limit = mA;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA);
+ return 0;
+ }
dwc->current_limit = mA;
schedule_work(&dwc->vbus_draw_work);
+ spin_unlock_irqrestore(&dwc->lock, flags);
return 0;
}
@@ -3497,6 +3508,7 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc)
}
dwc3_debugfs_remove_endpoint_dir(dep);
+ cancel_delayed_work_sync(&dep->nostream_work);
kfree(dep);
}
}
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 96b588bd08cd..c5674161b2b0 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -12,6 +12,7 @@
#include <linux/platform_device.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
+#include <linux/bitfield.h>
#include "../host/xhci-port.h"
#include "../host/xhci-ext-caps.h"
@@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
return;
}
- op_regs_base = HC_LENGTH(readl(xhci_regs));
+ op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs));
reg = readl(xhci_regs + XHCI_HCSPARAMS1);
- port_num = HCS_MAX_PORTS(reg);
+ port_num = FIELD_GET(HCS_MAX_PORTS, reg);
for (i = 1; i <= port_num; i++) {
offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1);
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 183a25f65ac8..51df6d1d1487 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy)
if (copy) {
str = copy;
} else {
- str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL);
+ str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL);
if (!str)
return -ENOMEM;
}
- strcpy(str, s);
+ memcpy(str, s, ret + 1);
if (str[ret - 1] == '\n')
str[ret - 1] = '\0';
*s_copy = str;
@@ -1177,7 +1177,7 @@ static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page)
struct gadget_info *gi = os_desc_item_to_gadget_info(item);
int res;
- res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN,
+ res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN / 2,
UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1);
page[res++] = '\n';
@@ -1199,7 +1199,7 @@ static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page,
mutex_lock(&gi->lock);
res = utf8s_to_utf16s(page, l,
UTF16_LITTLE_ENDIAN, (wchar_t *) gi->qw_sign,
- OS_STRING_QW_SIGN_LEN);
+ OS_STRING_QW_SIGN_LEN / 2);
if (res > 0)
res = len;
mutex_unlock(&gi->lock);
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 44218be1e676..43962e05eacf 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -159,7 +159,9 @@ struct ffs_epfile {
struct mutex mutex;
struct ffs_data *ffs;
- struct ffs_ep *ep; /* P: ffs->eps_lock */
+ struct ffs_ep *ep; /* P: ffs->eps_lock */
+ struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */
+ struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */
/*
* Buffer for holding data from partial reads which may happen since
@@ -219,12 +221,13 @@ struct ffs_epfile {
struct ffs_buffer *read_buffer;
#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
- char name[5];
+ char name[8];
unsigned char in; /* P: ffs->eps_lock */
unsigned char isoc; /* P: ffs->eps_lock */
- unsigned char _pad;
+ u8 zlp_enabled; /* P: ffs->eps_lock */
+ bool is_rw_proxy;
/* Protects dmabufs */
struct mutex dmabufs_mutex;
@@ -548,6 +551,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
return -EIDRM;
+retry:
/* Acquire mutex */
ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
if (ret < 0)
@@ -582,10 +586,15 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
break;
}
- if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq,
- ffs->ev.count)) {
- ret = -EINTR;
- break;
+ if (!ffs->ev.count) {
+ spin_unlock_irq(&ffs->ev.waitq.lock);
+ mutex_unlock(&ffs->mutex);
+
+ if (wait_event_interruptible_exclusive(ffs->ev.waitq,
+ ffs->ev.count))
+ return -EINTR;
+
+ goto retry;
}
/* unlocks spinlock */
@@ -867,9 +876,15 @@ static void ffs_user_copy_worker(struct work_struct *work)
bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD;
if (io_data->read && ret > 0) {
- kthread_use_mm(io_data->mm);
- ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data);
- kthread_unuse_mm(io_data->mm);
+ if (mmget_not_zero(io_data->mm)) {
+ kthread_use_mm(io_data->mm);
+ ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data);
+ kthread_unuse_mm(io_data->mm);
+ mmput(io_data->mm);
+ } else {
+ ret = -EFAULT;
+ }
+ mmdrop(io_data->mm);
}
io_data->kiocb->ki_complete(io_data->kiocb, ret);
@@ -979,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
return ret;
}
-static struct ffs_ep *ffs_epfile_wait_ep(struct file *file)
+static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file)
{
- struct ffs_epfile *epfile = file->private_data;
struct ffs_ep *ep;
int ret;
@@ -1008,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
char *data = NULL;
ssize_t ret, data_len = -EINVAL;
int halt;
+ bool is_rw_proxy = epfile->is_rw_proxy;
/* Are we still active? */
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
- ep = ffs_epfile_wait_ep(file);
+ /* Proxy to base endpoint if rw_proxy */
+ if (is_rw_proxy)
+ epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in;
+
+ ep = ffs_epfile_wait_ep(epfile, file);
if (IS_ERR(ep))
return PTR_ERR(ep);
/* Do we halt? */
- halt = (!io_data->read == !epfile->in);
+ halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in);
if (halt && epfile->isoc)
return -EINVAL;
@@ -1115,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
req->buf = data;
req->num_sgs = 0;
}
+
+ req->zero = !io_data->read ? epfile->zlp_enabled : 0;
req->length = data_len;
io_data->buf = data;
@@ -1166,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
req->buf = data;
req->num_sgs = 0;
}
+
+ req->zero = !io_data->read ? epfile->zlp_enabled : 0;
req->length = data_len;
io_data->buf = data;
@@ -1264,16 +1287,22 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from)
kiocb->private = p;
- if (p->aio)
+ if (p->aio) {
+ mmgrab(p->mm);
kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);
+ }
res = ffs_epfile_io(kiocb->ki_filp, p);
if (res == -EIOCBQUEUED)
return res;
- if (p->aio)
+ if (p->aio) {
+ kiocb->ki_complete(kiocb, res);
+ mmdrop(p->mm);
kfree(p);
- else
+ return -EIOCBQUEUED;
+ } else {
*from = p->data;
+ }
return res;
}
@@ -1308,16 +1337,21 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to)
kiocb->private = p;
- if (p->aio)
+ if (p->aio) {
+ mmgrab(p->mm);
kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);
+ }
res = ffs_epfile_io(kiocb->ki_filp, p);
if (res == -EIOCBQUEUED)
return res;
if (p->aio) {
+ kiocb->ki_complete(kiocb, res);
+ mmdrop(p->mm);
kfree(p->to_free);
kfree(p);
+ return -EIOCBQUEUED;
} else {
*to = p->data;
}
@@ -1643,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file,
priv = attach->importer_priv;
- ep = ffs_epfile_wait_ep(file);
+ ep = ffs_epfile_wait_ep(epfile, file);
if (IS_ERR(ep)) {
ret = PTR_ERR(ep);
goto err_attachment_put;
@@ -1682,13 +1716,13 @@ static int ffs_dmabuf_transfer(struct file *file,
/* In the meantime, endpoint got disabled or changed. */
if (epfile->ep != ep) {
ret = -ESHUTDOWN;
- goto err_fence_put;
+ goto err_fence_free;
}
usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC);
if (!usb_req) {
ret = -ENOMEM;
- goto err_fence_put;
+ goto err_fence_free;
}
/*
@@ -1709,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file,
/* Now that the dma_fence is in place, queue the transfer. */
+ usb_req->zero = epfile->zlp_enabled;
usb_req->length = req->length;
usb_req->buf = NULL;
usb_req->sg = priv->sgt->sgl;
@@ -1737,9 +1772,9 @@ static int ffs_dmabuf_transfer(struct file *file,
return ret;
-err_fence_put:
+err_fence_free:
spin_unlock_irq(&epfile->ffs->eps_lock);
- dma_fence_put(&fence->base);
+ kfree(fence);
err_resv_unlock:
dma_resv_unlock(dmabuf->resv);
err_attachment_put:
@@ -1756,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
struct ffs_epfile *epfile = file->private_data;
struct ffs_ep *ep;
int ret;
+ __u32 enable_zlp = 0;
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
+ if (epfile->is_rw_proxy)
+ return -ENOTTY;
+
switch (code) {
case FUNCTIONFS_DMABUF_ATTACH:
{
@@ -1788,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
return ffs_dmabuf_transfer(file, &req);
}
+ /*
+ * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace
+ * to configure ZLP behavior immediately without blocking indefinitely
+ * while waiting for the USB host to connect and enable the endpoint.
+ */
+ case FUNCTIONFS_ENDPOINT_ENABLE_ZLP:
+ if (!epfile->in)
+ return -EINVAL;
+
+ if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp)))
+ return -EFAULT;
+
+ spin_lock_irq(&epfile->ffs->eps_lock);
+ epfile->zlp_enabled = !!enable_zlp;
+ spin_unlock_irq(&epfile->ffs->eps_lock);
+
+ return 0;
default:
break;
}
/* Wait for endpoint to be enabled */
- ep = ffs_epfile_wait_ep(file);
+ ep = ffs_epfile_wait_ep(epfile, file);
if (IS_ERR(ep))
return PTR_ERR(ep);
@@ -2191,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs)
if (epfiles)
ffs_epfiles_destroy(ffs->sb, epfiles,
- ffs->eps_count);
+ ffs->epfiles_count);
if (ffs->setup_state == FFS_SETUP_PENDING)
__ffs_ep0_stall(ffs);
@@ -2250,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs)
* copy of epfile will save us from use-after-free.
*/
if (epfiles) {
- ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count);
+ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count);
ffs->epfiles = NULL;
}
@@ -2348,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs)
static int ffs_epfiles_create(struct ffs_data *ffs)
{
struct ffs_epfile *epfile, *epfiles;
- unsigned i, count;
+ unsigned int i, count, epfiles_count;
int err;
count = ffs->eps_count;
- epfiles = kzalloc_objs(*epfiles, count);
+ epfiles_count = count;
+ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS)
+ epfiles_count += count / 2;
+ ffs->epfiles_count = epfiles_count;
+
+ epfiles = kzalloc_objs(*epfiles, epfiles_count);
if (!epfiles)
return -ENOMEM;
@@ -2375,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
}
}
+ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) {
+ struct ffs_epfile *comp = epfiles + count;
+
+ for (i = 0; i < count; i += 2, ++comp) {
+ struct ffs_epfile *ep1 = &epfiles[i];
+ struct ffs_epfile *ep2 = &epfiles[i + 1];
+ bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK;
+
+ comp->ffs = ffs;
+ comp->is_rw_proxy = true;
+ comp->epfile_in = ep1_in ? ep1 : ep2;
+ comp->epfile_out = ep1_in ? ep2 : ep1;
+ mutex_init(&comp->mutex);
+ mutex_init(&comp->dmabufs_mutex);
+ INIT_LIST_HEAD(&comp->dmabufs);
+ snprintf(comp->name, sizeof(comp->name), "%s_rw",
+ epfiles[i].name);
+ err = ffs_sb_create_file(ffs->sb, comp->name,
+ comp, &ffs_epfile_operations);
+ if (err) {
+ ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2));
+ return err;
+ }
+ }
+ }
+
ffs->epfiles = epfiles;
return 0;
}
@@ -2952,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
FUNCTIONFS_VIRTUAL_ADDR |
FUNCTIONFS_EVENTFD |
FUNCTIONFS_ALL_CTRL_RECIP |
- FUNCTIONFS_CONFIG0_SETUP)) {
+ FUNCTIONFS_CONFIG0_SETUP |
+ FUNCTIONFS_RW_PROXY_EPS)) {
ret = -ENOSYS;
goto error;
}
@@ -3040,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
goto error;
}
+ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) {
+ if (ffs->eps_count % 2) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ for (i = 1; i < ffs->eps_count; i += 2) {
+ if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) ==
+ (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) {
+ ret = -EINVAL;
+ goto error;
+ }
+ }
+ }
+
ffs->raw_descs_data = _data;
ffs->raw_descs = raw_descs;
ffs->raw_descs_length = data - raw_descs;
@@ -3335,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
struct usb_request *req;
struct usb_ep *ep;
u8 bEndpointAddress;
- u16 wMaxPacketSize;
+ __le16 wMaxPacketSize;
/*
* We back up bEndpointAddress because autoconfig overwrites
diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c
index b7b06cb79ff5..a50743caf083 100644
--- a/drivers/usb/gadget/function/f_mass_storage.c
+++ b/drivers/usb/gadget/function/f_mass_storage.c
@@ -203,7 +203,6 @@
/*------------------------------------------------------------------------*/
#define FSG_DRIVER_DESC "Mass Storage Function"
-#define FSG_DRIVER_VERSION "2009/09/11"
static const char fsg_string_interface[] = "Mass Storage";
@@ -3513,8 +3512,6 @@ static struct usb_function_instance *fsg_alloc_inst(void)
if (rc)
goto release_common;
- pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
-
memset(&config, 0, sizeof(config));
config.removable = true;
rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0",
diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c
index 19fdac024343..a4b72a6fad8a 100644
--- a/drivers/usb/gadget/function/f_midi2.c
+++ b/drivers/usb/gadget/function/f_midi2.c
@@ -2473,6 +2473,7 @@ static void f_midi2_ep_opts_release(struct config_item *item)
{
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);
+ configfs_remove_default_groups(&opts->group);
kfree(opts->info.ep_name);
kfree(opts->info.product_id);
kfree(opts);
@@ -2639,6 +2640,7 @@ static void f_midi2_free_inst(struct usb_function_instance *f)
opts = container_of(f, struct f_midi2_opts, func_inst);
+ configfs_remove_default_groups(&opts->func_inst.group);
kfree(opts->info.iface_name);
kfree(opts);
}
diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index b3fa5a17fd2d..9e6d4f39900a 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -1675,19 +1675,25 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts,
func_inst);
- mutex_lock(&opts->dep_lock);
- if (!opts->ready)
- goto unlock_dep;
+ if (!READ_ONCE(opts->ready))
+ goto unlock_inst;
if (opts->has_dep) {
if (!try_module_get(opts->dependent))
- goto unlock_dep;
+ goto unlock_inst;
} else {
+ /*
+ * configfs_depend_item_unlocked() may acquire the configfs
+ * root inode lock when the target belongs to a different
+ * subsystem. Calling it under dep_lock would create a
+ * circular dependency:
+ * dep_lock -> configfs inode lock -> su_mutex -> dep_lock
+ */
ret = configfs_depend_item_unlocked(
wwn->wwn_group.cg_subsys,
&opts->func_inst.group.cg_item);
if (ret)
- goto unlock_dep;
+ goto unlock_inst;
}
tpg = kzalloc_obj(struct usbg_tpg);
@@ -1714,7 +1720,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
tpg_instances[i].tpg = tpg;
tpg->fi = tpg_instances[i].func_inst;
- mutex_unlock(&opts->dep_lock);
mutex_unlock(&tpg_instances_lock);
return &tpg->se_tpg;
@@ -1727,8 +1732,6 @@ unref_dep:
module_put(opts->dependent);
else
configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
-unlock_dep:
- mutex_unlock(&opts->dep_lock);
unlock_inst:
mutex_unlock(&tpg_instances_lock);
@@ -2024,7 +2027,7 @@ static const struct target_core_fabric_ops usbg_ops = {
.fabric_enable_tpg = usbg_enable_tpg,
.fabric_drop_tpg = usbg_drop_tpg,
.fabric_post_link = usbg_port_link,
- .fabric_pre_unlink = usbg_port_unlink,
+ .fabric_post_unlink = usbg_port_unlink,
.fabric_init_nodeacl = usbg_init_nodeacl,
.tfc_wwn_attrs = usbg_wwn_attrs,
@@ -2666,9 +2669,7 @@ static int tcm_set_name(struct usb_function_instance *f, const char *name)
pr_debug("tcm: Activating %s\n", name);
- mutex_lock(&opts->dep_lock);
- opts->ready = true;
- mutex_unlock(&opts->dep_lock);
+ WRITE_ONCE(opts->ready, true);
return 0;
}
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 85c502e98f57..7a81cd176abd 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -1594,7 +1594,8 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- char *split_page = NULL; \
+ char *buf = NULL; \
+ char *split_page; \
int ret = -EINVAL; \
char *token; \
u32 num; \
@@ -1608,18 +1609,22 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
\
i = 0; \
memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
- split_page = kstrdup(page, GFP_KERNEL); \
+ buf = kstrdup(page, GFP_KERNEL); \
+ split_page = buf; \
while ((token = strsep(&split_page, ",")) != NULL) { \
ret = kstrtou32(token, 0, &num); \
if (ret) \
goto end; \
- \
+ if (i >= UAC_MAX_RATES) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
opts->name##s[i++] = num; \
ret = len; \
}; \
\
end: \
- kfree(split_page); \
+ kfree(buf); \
mutex_unlock(&opts->lock); \
return ret; \
} \
diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c
index 5d201a2e30e7..3f52099a4fdd 100644
--- a/drivers/usb/gadget/function/f_uac1_legacy.c
+++ b/drivers/usb/gadget/function/f_uac1_legacy.c
@@ -888,60 +888,10 @@ UAC1_INT_ATTRIBUTE(req_buf_size);
UAC1_INT_ATTRIBUTE(req_count);
UAC1_INT_ATTRIBUTE(audio_buf_size);
-#define UAC1_STR_ATTRIBUTE(name) \
-static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = sprintf(page, "%s\n", opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
- int ret = -EBUSY; \
- char *tmp; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) \
- goto end; \
- \
- tmp = kstrndup(page, len, GFP_KERNEL); \
- if (tmp) { \
- ret = -ENOMEM; \
- goto end; \
- } \
- if (opts->name##_alloc) \
- kfree(opts->name); \
- opts->name##_alloc = true; \
- opts->name = tmp; \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac1_opts_, name)
-
-UAC1_STR_ATTRIBUTE(fn_play);
-UAC1_STR_ATTRIBUTE(fn_cap);
-UAC1_STR_ATTRIBUTE(fn_cntl);
-
static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_attr_req_buf_size,
&f_uac1_opts_attr_req_count,
&f_uac1_opts_attr_audio_buf_size,
- &f_uac1_opts_attr_fn_play,
- &f_uac1_opts_attr_fn_cap,
- &f_uac1_opts_attr_fn_cntl,
NULL,
};
@@ -956,12 +906,6 @@ static void f_audio_free_inst(struct usb_function_instance *f)
struct f_uac1_legacy_opts *opts;
opts = container_of(f, struct f_uac1_legacy_opts, func_inst);
- if (opts->fn_play_alloc)
- kfree(opts->fn_play);
- if (opts->fn_cap_alloc)
- kfree(opts->fn_cap);
- if (opts->fn_cntl_alloc)
- kfree(opts->fn_cntl);
kfree(opts);
}
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 897787d0803c..d8cf710085a0 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -2012,7 +2012,8 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- char *split_page = NULL; \
+ char *buf = NULL; \
+ char *split_page; \
int ret = -EINVAL; \
char *token; \
u32 num; \
@@ -2026,18 +2027,22 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
\
i = 0; \
memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
- split_page = kstrdup(page, GFP_KERNEL); \
+ buf = kstrdup(page, GFP_KERNEL); \
+ split_page = buf; \
while ((token = strsep(&split_page, ",")) != NULL) { \
ret = kstrtou32(token, 0, &num); \
if (ret) \
goto end; \
- \
+ if (i >= UAC_MAX_RATES) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
opts->name##s[i++] = num; \
ret = len; \
}; \
\
end: \
- kfree(split_page); \
+ kfree(buf); \
mutex_unlock(&opts->lock); \
return ret; \
} \
diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 73dc7e42875f..d1bf3ea75197 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -889,9 +889,12 @@ error_unlock:
v4l2_error:
v4l2_device_unregister(&uvc->v4l2_dev);
error:
- if (uvc->control_req)
+ if (uvc->control_req) {
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
+ uvc->control_req = NULL;
+ }
kfree(uvc->control_buf);
+ uvc->control_buf = NULL;
usb_free_all_descriptors(f);
return ret;
@@ -1075,7 +1078,9 @@ static void uvc_function_unbind(struct usb_configuration *c,
uvc->vdev_release_done = NULL;
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
+ uvc->control_req = NULL;
kfree(uvc->control_buf);
+ uvc->control_buf = NULL;
usb_free_all_descriptors(f);
}
diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c
index e53f2927b539..f0ee83abcebc 100644
--- a/drivers/usb/gadget/function/u_audio.c
+++ b/drivers/usb/gadget/function/u_audio.c
@@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev)
ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback);
if (ret < 0) {
dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret);
- return ret; // TODO: Clean up out_ep
+ goto err_out_ep;
}
- prm->fb_ep_enabled = true;
ret = usb_ep_enable(ep_fback);
if (ret < 0) {
dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret);
- return ret; // TODO: Clean up out_ep
+ goto err_out_ep;
}
+ prm->fb_ep_enabled = true;
req_len = ep_fback->maxpacket;
req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC);
@@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev)
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return 0;
+
+err_out_ep:
+ set_active(prm, false);
+ free_ep(prm, ep);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(u_audio_start_capture);
@@ -1177,6 +1183,20 @@ static struct snd_kcontrol_new u_audio_controls[] = {
},
};
+static void u_audio_card_free(struct snd_card *card)
+{
+ struct snd_uac_chip *uac = card->private_data;
+
+ if (!uac)
+ return;
+
+ kfree(uac->p_prm.reqs);
+ kfree(uac->c_prm.reqs);
+ kfree(uac->p_prm.rbuf);
+ kfree(uac->c_prm.rbuf);
+ kfree(uac);
+}
+
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
const char *card_name)
{
@@ -1256,6 +1276,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
goto fail;
uac->card = card;
+ card->private_data = uac;
+ card->private_free = u_audio_card_free;
/*
* Create first PCM device
@@ -1424,6 +1446,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
snd_fail:
snd_card_free(card);
+ return err;
+
fail:
kfree(uac->p_prm.reqs);
kfree(uac->c_prm.reqs);
@@ -1449,12 +1473,6 @@ void g_audio_cleanup(struct g_audio *g_audio)
card = uac->card;
if (card)
snd_card_free_when_closed(card);
-
- kfree(uac->p_prm.reqs);
- kfree(uac->c_prm.reqs);
- kfree(uac->p_prm.rbuf);
- kfree(uac->c_prm.rbuf);
- kfree(uac);
}
EXPORT_SYMBOL_GPL(g_audio_cleanup);
diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h
index 6a80182aadd7..c280c495fbd2 100644
--- a/drivers/usb/gadget/function/u_fs.h
+++ b/drivers/usb/gadget/function/u_fs.h
@@ -252,8 +252,14 @@ struct ffs_data {
unsigned short strings_count;
unsigned short interfaces_count;
+
+ /*
+ * eps_count tracks the number of underlying hardware endpoints.
+ * epfiles_count tracks the total number of VFS endpoint files.
+ * When companion endpoints are active, epfiles_count > eps_count.
+ */
unsigned short eps_count;
- unsigned short _pad1;
+ unsigned short epfiles_count;
/* filled by __ffs_data_got_strings() */
/* ids in stringtabs are set in functionfs_bind() */
diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h
index b5df9bcbbeba..b9ddae550ff3 100644
--- a/drivers/usb/gadget/function/u_uac1_legacy.h
+++ b/drivers/usb/gadget/function/u_uac1_legacy.h
@@ -62,9 +62,6 @@ struct f_uac1_legacy_opts {
char *fn_cap;
char *fn_cntl;
unsigned bound:1;
- unsigned fn_play_alloc:1;
- unsigned fn_cap_alloc:1;
- unsigned fn_cntl_alloc:1;
struct mutex lock;
int refcnt;
};
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 2f9700b3f1b6..9ba09118bb74 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -821,7 +821,7 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
/* Allocate a kthread for asynchronous hw submit handler. */
video->kworker = kthread_run_worker(0, "UVCG");
if (IS_ERR(video->kworker)) {
- uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n");
+ uvcg_err(&uvc->func, "failed to create UVCG kworker\n");
return PTR_ERR(video->kworker);
}
diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c
index d87a8ab51510..db961aaa3740 100644
--- a/drivers/usb/gadget/legacy/inode.c
+++ b/drivers/usb/gadget/legacy/inode.c
@@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work)
struct kiocb *iocb = priv->iocb;
size_t ret;
- kthread_use_mm(mm);
- ret = copy_to_iter(priv->buf, priv->actual, &priv->to);
- kthread_unuse_mm(mm);
- if (!ret)
+ if (mmget_not_zero(mm)) {
+ kthread_use_mm(mm);
+ ret = copy_to_iter(priv->buf, priv->actual, &priv->to);
+ kthread_unuse_mm(mm);
+ mmput(mm);
+ if (!ret)
+ ret = -EFAULT;
+ } else {
ret = -EFAULT;
+ }
+ mmdrop(mm);
/* completing the iocb can drop the ctx and mm, don't touch mm after */
iocb->ki_complete(iocb, ret);
@@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req)
* complete the aio request immediately.
*/
if (priv->to_free == NULL || unlikely(req->actual == 0)) {
+ mmdrop(priv->mm);
kfree(req->buf);
kfree(priv->to_free);
kfree(priv);
@@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb,
priv->epdata = epdata;
priv->actual = 0;
priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */
+ mmgrab(priv->mm);
/* each kiocb is coupled to one usb_request, but we can't
* allocate or submit those if the host disconnected.
@@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb,
fail:
spin_unlock_irq(&epdata->dev->lock);
+ mmdrop(priv->mm);
kfree(priv->to_free);
kfree(priv);
put_ep(epdata);
diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c
index 2e15f9a32ce9..396605a84cd1 100644
--- a/drivers/usb/gadget/legacy/nokia.c
+++ b/drivers/usb/gadget/legacy/nokia.c
@@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS;
*/
#define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS
-#endif /* CONFIG_USB_DEBUG */
+#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data);
diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 26460340fbc9..7ddbc2606686 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -90,7 +90,7 @@ config USB_BCM63XX_UDC
config USB_FSL_USB2
tristate "Freescale Highspeed USB DR Peripheral Controller"
- depends on FSL_SOC
+ depends on FSL_SOC || COMPILE_TEST
help
Some of Freescale PowerPC and i.MX processors have a High Speed
Dual-Role(DR) USB controller, which supports device mode.
diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c
index 75f9c831b21a..54f81e668009 100644
--- a/drivers/usb/gadget/udc/aspeed_udc.c
+++ b/drivers/usb/gadget/udc/aspeed_udc.c
@@ -1431,25 +1431,12 @@ static void ast_udc_init_hw(struct ast_udc_dev *udc)
ast_udc_write(udc, 0, AST_UDC_EP0_CTRL);
}
-static void ast_udc_remove(struct platform_device *pdev)
+static void ast_udc_cleanup(struct platform_device *pdev)
{
struct ast_udc_dev *udc = platform_get_drvdata(pdev);
unsigned long flags;
u32 ctrl;
- usb_del_gadget_udc(&udc->gadget);
- if (udc->driver) {
- /*
- * This is broken as only some cleanup is skipped, *udev is
- * freed and the register mapping goes away. Any further usage
- * probably crashes. Also the device is unbound, so the skipped
- * cleanup is never catched up later.
- */
- dev_alert(&pdev->dev,
- "Driver is busy and still going away. Fasten your seat belts!\n");
- return;
- }
-
spin_lock_irqsave(&udc->lock, flags);
/* Disable upstream port connection */
@@ -1469,6 +1456,26 @@ static void ast_udc_remove(struct platform_device *pdev)
udc->ep0_buf = NULL;
}
+static void ast_udc_remove(struct platform_device *pdev)
+{
+ struct ast_udc_dev *udc = platform_get_drvdata(pdev);
+
+ usb_del_gadget_udc(&udc->gadget);
+ if (udc->driver) {
+ /*
+ * This is broken as only some cleanup is skipped, *udev is
+ * freed and the register mapping goes away. Any further usage
+ * probably crashes. Also the device is unbound, so the skipped
+ * cleanup is never catched up later.
+ */
+ dev_alert(&pdev->dev,
+ "Driver is busy and still going away. Fasten your seat belts!\n");
+ return;
+ }
+
+ ast_udc_cleanup(pdev);
+}
+
static int ast_udc_probe(struct platform_device *pdev)
{
enum usb_device_speed max_speed;
@@ -1521,6 +1528,12 @@ static int ast_udc_probe(struct platform_device *pdev)
AST_UDC_NUM_ENDPOINTS,
&udc->ep0_buf_dma, GFP_KERNEL);
+ if (!udc->ep0_buf) {
+ clk_disable_unprepare(udc->clk);
+ rc = -ENOMEM;
+ goto err;
+ }
+
udc->gadget.speed = USB_SPEED_UNKNOWN;
udc->gadget.max_speed = USB_SPEED_HIGH;
udc->creq = udc->reg + AST_UDC_SETUP0;
@@ -1550,20 +1563,20 @@ static int ast_udc_probe(struct platform_device *pdev)
udc->irq = platform_get_irq(pdev, 0);
if (udc->irq < 0) {
rc = udc->irq;
- goto err;
+ goto err_cleanup;
}
rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0,
KBUILD_MODNAME, udc);
if (rc) {
dev_err(&pdev->dev, "Failed to request interrupt\n");
- goto err;
+ goto err_cleanup;
}
rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
if (rc) {
dev_err(&pdev->dev, "Failed to add gadget udc\n");
- goto err;
+ goto err_cleanup;
}
dev_info(&pdev->dev, "Initialized udc in USB%s mode\n",
@@ -1571,9 +1584,10 @@ static int ast_udc_probe(struct platform_device *pdev)
return 0;
+err_cleanup:
+ ast_udc_cleanup(pdev);
err:
dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc);
- ast_udc_remove(pdev);
return rc;
}
diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c
index 5aa360ba4f03..8e5d7fb71549 100644
--- a/drivers/usb/gadget/udc/at91_udc.c
+++ b/drivers/usb/gadget/udc/at91_udc.c
@@ -1794,6 +1794,19 @@ static void at91udc_of_init(struct at91_udc *udc, struct device_node *np)
udc->caps = match->data;
}
+/*
+ * The work handler re-arms this timer, so shut the timer down before
+ * draining the work; otherwise it restarts the polling cycle.
+ */
+static void at91_udc_shutdown_vbus_timer(struct at91_udc *udc)
+{
+ if (!(udc->board.vbus_pin && udc->board.vbus_polled))
+ return;
+
+ timer_shutdown_sync(&udc->vbus_timer);
+ cancel_work_sync(&udc->vbus_timer_work);
+}
+
static int at91udc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1907,7 +1920,7 @@ static int at91udc_probe(struct platform_device *pdev)
}
retval = usb_add_gadget_udc(dev, &udc->gadget);
if (retval)
- goto err_unprepare_iclk;
+ goto err_shutdown_vbus;
dev_set_drvdata(dev, udc);
device_init_wakeup(dev, 1);
create_debug_file(udc);
@@ -1915,6 +1928,8 @@ static int at91udc_probe(struct platform_device *pdev)
INFO("%s version %s\n", driver_name, DRIVER_VERSION);
return 0;
+err_shutdown_vbus:
+ at91_udc_shutdown_vbus_timer(udc);
err_unprepare_iclk:
clk_unprepare(udc->iclk);
err_unprepare_fclk:
@@ -1933,6 +1948,9 @@ static void at91udc_remove(struct platform_device *pdev)
DBG("remove\n");
usb_del_gadget_udc(&udc->gadget);
+
+ at91_udc_shutdown_vbus_timer(udc);
+
if (udc->driver) {
dev_err(&pdev->dev,
"Driver still in use but removing anyhow\n");
diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c
index bf87285ad13c..603c77ff129f 100644
--- a/drivers/usb/gadget/udc/fsl_qe_udc.c
+++ b/drivers/usb/gadget/udc/fsl_qe_udc.c
@@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value,
u16 index, u16 length)
{
u16 usb_status = 0;
+ struct usb_request *usb_req;
struct qe_req *req;
struct qe_ep *ep;
int status = 0;
@@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value,
}
}
- req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL),
- struct qe_req, req);
+ usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL);
+ if (!usb_req)
+ goto stall;
+
+ req = container_of(usb_req, struct qe_req, req);
req->req.length = 2;
req->req.buf = udc->statusbuf;
*(u16 *)req->req.buf = cpu_to_le16(usb_status);
diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c
index 0a6886428739..99b3ce28210f 100644
--- a/drivers/usb/gadget/udc/pch_udc.c
+++ b/drivers/usb/gadget/udc/pch_udc.c
@@ -320,7 +320,6 @@ struct pch_vbus_gpio_data {
* @lock: protects all state
* @stall: stall requested
* @prot_stall: protcol stall requested
- * @registered: driver registered with system
* @suspended: driver in suspended state
* @connected: gadget driver associated
* @vbus_session: required vbus_session state
diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c
index e7a5d8553c0e..d190e16d43fc 100644
--- a/drivers/usb/gadget/udc/r8a66597-udc.c
+++ b/drivers/usb/gadget/udc/r8a66597-udc.c
@@ -1951,7 +1951,6 @@ static int r8a66597_probe(struct platform_device *pdev)
return 0;
err_add_udc:
- r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req);
clean_up2:
if (r8a66597->pdata->on_chip)
clk_disable_unprepare(r8a66597->clk);
diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c
index db842a6de643..0ee3ed185d9c 100644
--- a/drivers/usb/gadget/udc/snps_udc_plat.c
+++ b/drivers/usb/gadget/udc/snps_udc_plat.c
@@ -159,10 +159,9 @@ static int udc_plat_probe(struct platform_device *pdev)
if (of_property_present(dev->of_node, "extcon")) {
udc->edev = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(udc->edev)) {
- if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- dev_err(dev, "Invalid or missing extcon\n");
ret = PTR_ERR(udc->edev);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Invalid or missing extcon\n");
goto exit_phy;
}
diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c
index edfb63543029..7d46586e2e21 100644
--- a/drivers/usb/host/fsl-mph-dr-of.c
+++ b/drivers/usb/host/fsl-mph-dr-of.c
@@ -15,6 +15,7 @@
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
+#include <linux/regulator/consumer.h>
struct fsl_usb2_dev_data {
char *dr_mode; /* controller mode */
@@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev)
const struct of_device_id *match;
const unsigned char *prop;
static unsigned int idx;
- int i;
+ int i, err;
if (!of_device_is_available(np))
return -ENODEV;
@@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev)
}
}
+ err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus");
+ if (err)
+ return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n");
+
for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) {
if (!dev_data->drivers[i])
continue;
diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c
index 9e0e06bbc570..23dc9eddc06c 100644
--- a/drivers/usb/host/ohci-dbg.c
+++ b/drivers/usb/host/ohci-dbg.c
@@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf)
int ret;
if (!buf->page)
- buf->page = (char *)get_zeroed_page(GFP_KERNEL);
+ buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf->page) {
ret = -ENOMEM;
@@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file)
{
struct debug_buffer *buf = file->private_data;
- if (buf) {
- if (buf->page)
- free_page((unsigned long)buf->page);
- kfree(buf);
- }
+ kfree(buf->page);
+ kfree(buf);
return 0;
}
diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h
index 2f59b6ab1e45..68c27831e07e 100644
--- a/drivers/usb/host/xhci-caps.h
+++ b/drivers/usb/host/xhci-caps.h
@@ -5,22 +5,23 @@
*/
#include <linux/bits.h>
+#include <linux/bitfield.h>
/* hc_capbase - bitmasks */
/* bits 7:0 - Capability Registers Length */
-#define HC_LENGTH(p) ((p) & 0xff)
+#define HC_LENGTH GENMASK(7, 0)
/* bits 15:8 - Rsvd */
/* bits 31:16 - Host Controller Interface Version Number */
-#define HC_VERSION(p) (((p) >> 16) & 0xffff)
+#define HC_VERSION GENMASK(31, 16)
/* HCSPARAMS1 - hcs_params1 - bitmasks */
/* bits 7:0 - Number of Device Slots */
-#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff)
-#define HCS_SLOTS_MASK 0xff
-/* bits 18:8 - Number of Interrupters, max values is 1024 */
-#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff)
-/* bits 31:24, Max Ports - max value is 255 */
-#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff)
+#define HCS_SLOTS_MASK GENMASK(7, 0)
+/* bits 18:8 - Number of Interrupters, max values is 1024 */
+#define HCS_MAX_INTRS GENMASK(18, 8)
+/* bits 23:19 - Rsvd */
+/* bits 31:24 - Max Ports, max values is 255 */
+#define HCS_MAX_PORTS GENMASK(31, 24)
/* HCSPARAMS2 - hcs_params2 - bitmasks */
/*
@@ -33,24 +34,25 @@
* Note: 1 Frame = 8 Microframes
* xHCI specification section 5.3.4.
*/
-#define HCS_IST_VALUE(p) ((p) & 0x7)
+#define HCS_IST_VALUE GENMASK(2, 0)
#define HCS_IST_UNIT BIT(3)
/* bits 7:4 - Event Ring Segment Table Max, 2^(n) */
-#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf)
+#define HCS_ERST_MAX GENMASK(7, 4)
/* bits 20:8 - Rsvd */
/* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */
-#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f)
+#define HCS_MAX_SP_HI GENMASK(25, 21)
/* bit 26 - Scratchpad restore, for save/restore HW state */
/* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */
-#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f)
-#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p))
+#define HCS_MAX_SP_LO GENMASK(31, 27)
+#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \
+ FIELD_GET(HCS_MAX_SP_LO, (p)))
/* HCSPARAMS3 - hcs_params3 - bitmasks */
/* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */
-#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff)
+#define HCS_U1_LATENCY GENMASK(7, 0)
/* bits 15:8 - Rsvd */
/* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */
-#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff)
+#define HCS_U2_LATENCY GENMASK(31, 16)
/* HCCPARAMS1 - hcc_params - bitmasks */
/* bit 0 - 64-bit Addressing Capability */
@@ -77,19 +79,20 @@
/* bit 11 - Contiguous Frame ID Capability */
#define HCC_CFC BIT(11)
/* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */
-#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1))
+#define HCC_MAX_PSA GENMASK(15, 12)
+#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1))
/* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */
-#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff)
+#define HCC_EXT_CAPS GENMASK(31, 16)
/* DBOFF - db_off - bitmasks */
/* bits 1:0 - Rsvd */
/* bits 31:2 - Doorbell Array Offset */
-#define DBOFF_MASK (0xfffffffc)
+#define DBOFF_MASK GENMASK(31, 2)
/* RTSOFF - run_regs_off - bitmasks */
/* bits 4:0 - Rsvd */
/* bits 31:5 - Runtime Register Space Offse */
-#define RTSOFF_MASK (~0x1f)
+#define RTSOFF_MASK GENMASK(31, 5)
/* HCCPARAMS2 - hcc_params2 - bitmasks */
/* bit 0 - U3 Entry Capability */
diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c
index 2e7384c6b6ec..3d51e8d82659 100644
--- a/drivers/usb/host/xhci-dbgtty.c
+++ b/drivers/usb/host/xhci-dbgtty.c
@@ -628,8 +628,8 @@ int dbc_tty_init(void)
dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(dbc_tty_driver)) {
- idr_destroy(&dbc_tty_minors);
- return PTR_ERR(dbc_tty_driver);
+ ret = PTR_ERR(dbc_tty_driver);
+ goto fail;
}
dbc_tty_driver->driver_name = "dbc_serial";
@@ -649,20 +649,27 @@ int dbc_tty_init(void)
ret = tty_register_driver(dbc_tty_driver);
if (ret) {
pr_err("Can't register dbc tty driver\n");
- tty_driver_kref_put(dbc_tty_driver);
- idr_destroy(&dbc_tty_minors);
+ goto fail_put;
}
return ret;
+
+fail_put:
+ tty_driver_kref_put(dbc_tty_driver);
+fail:
+ idr_destroy(&dbc_tty_minors);
+ dbc_tty_driver = NULL;
+
+ return ret;
}
void dbc_tty_exit(void)
{
- if (dbc_tty_driver) {
- tty_unregister_driver(dbc_tty_driver);
- tty_driver_kref_put(dbc_tty_driver);
- dbc_tty_driver = NULL;
- }
+ if (IS_ERR_OR_NULL(dbc_tty_driver))
+ return;
+ tty_unregister_driver(dbc_tty_driver);
+ tty_driver_kref_put(dbc_tty_driver);
idr_destroy(&dbc_tty_minors);
+ dbc_tty_driver = NULL;
}
diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c
index d07276192256..2aa01d99f23d 100644
--- a/drivers/usb/host/xhci-debugfs.c
+++ b/drivers/usb/host/xhci-debugfs.c
@@ -9,6 +9,7 @@
#include <linux/slab.h>
#include <linux/uaccess.h>
+#include <linux/bitfield.h>
#include "xhci.h"
#include "xhci-debugfs.h"
@@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci)
xhci->debugfs_root, "reg-cap");
xhci_debugfs_regset(xhci,
- HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)),
+ FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)),
xhci_op_regs, ARRAY_SIZE(xhci_op_regs),
xhci->debugfs_root, "reg-op");
diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c
index 02396c8721dc..fddb43bf6323 100644
--- a/drivers/usb/host/xhci-histb.c
+++ b/drivers/usb/host/xhci-histb.c
@@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev)
if (ret)
goto put_usb3_hcd;
- if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
+ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4)
xhci->shared_hcd->can_do_streams = 1;
ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index b0264bd8577a..470bafe1802b 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,
if ((xhci->quirks & XHCI_LPM_SUPPORT)) {
reg = readl(&xhci->cap_regs->hcs_params3);
- ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);
- ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));
+ ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg);
+ ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg));
}
if (wLength < le16_to_cpu(bos->wTotalLength))
@@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
}
/* In spec software should not attempt to suspend
* a port unless the port reports that it is in the
- * enabled (PED = ‘1’,PLS < ‘3’) state.
+ * enabled (PED = '1',PLS < '3') state.
*/
portsc = xhci_portsc_readl(port);
if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) ||
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index a5e7f363922f..7a21ac81f9c8 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -14,6 +14,7 @@
#include <linux/slab.h>
#include <linux/dmapool.h>
#include <linux/dma-mapping.h>
+#include <linux/bitfield.h>
#include "xhci.h"
#include "xhci-trace.h"
@@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
for (i = 0; i < 31; i++) {
dev->eps[i].ep_index = i;
dev->eps[i].vdev = dev;
- dev->eps[i].xhci = xhci;
INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list);
INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list);
}
@@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
return -ENOMEM;
virt_dev->eps[ep_index].skip = false;
+ virt_dev->eps[ep_index].next_uframe = -1;
ep_ring = virt_dev->eps[ep_index].new_ring;
xhci_ring_init(xhci, ep_ring);
@@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
addr, port_offset, port_count, major_revision);
/* Port count includes the current port offset */
if (port_offset == 0 || (port_offset + port_count - 1) > num_ports)
- /* WTF? "Valid values are ‘1’ to MaxPorts" */
+ /* WTF? "Valid values are '1' to MaxPorts" */
return;
port_cap = &xhci->port_caps[xhci->num_port_caps++];
@@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags)
if (!segs)
segs = ERST_DEFAULT_SEGS;
- max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2));
+ max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2;
segs = min(segs, max_segs);
ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev));
diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
index d9b865546a67..60af5fe29bd4 100644
--- a/drivers/usb/host/xhci-mtk.c
+++ b/drivers/usb/host/xhci-mtk.c
@@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci)
* MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream,
* and it's 3 when support it.
*/
- if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4)
+ if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4)
xhci->quirks |= XHCI_BROKEN_STREAMS;
}
@@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
}
usb3_hcd = xhci_get_usb3_hcd(xhci);
- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 &&
+ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 &&
!(xhci->quirks & XHCI_BROKEN_STREAMS))
usb3_hcd->can_do_streams = 1;
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 6b3fcba44b08..a8889081ae82 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -14,6 +14,7 @@
#include <linux/acpi.h>
#include <linux/reset.h>
#include <linux/suspend.h>
+#include <linux/bitfield.h>
#include "xhci.h"
#include "xhci-trace.h"
@@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
}
usb3_hcd = xhci_get_usb3_hcd(xhci);
- if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4)
+ if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) &&
+ GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4)
usb3_hcd->can_do_streams = 1;
/* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 074d9c731639..6fd595f81a30 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s
}
usb3_hcd = xhci_get_usb3_hcd(xhci);
- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 &&
+ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 &&
!(xhci->quirks & XHCI_BROKEN_STREAMS))
usb3_hcd->can_do_streams = 1;
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 4f98d8269625..97a1b53c18ef 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -57,6 +57,8 @@
#include <linux/slab.h>
#include <linux/string_choices.h>
#include <linux/dma-mapping.h>
+#include <linux/bitfield.h>
+
#include "xhci.h"
#include "xhci-trace.h"
@@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
* pointer command pending because the device can choose to start any
* stream once the endpoint is on the HW schedule.
*/
- if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) ||
- (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT))
+ if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED |
+ EP_CLEARING_TT | EP_DROP_PENDING))
return;
trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id));
@@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
}
/* Ring the doorbell for any rings with pending URBs */
-static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
- unsigned int slot_id,
- unsigned int ep_index)
+void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id,
+ unsigned int ep_index)
{
unsigned int stream_id;
struct xhci_virt_ep *ep;
@@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
}
}
-void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
- unsigned int slot_id,
- unsigned int ep_index)
-{
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
-}
-
static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci,
unsigned int slot_id,
unsigned int ep_index)
@@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev,
return le64_to_cpu(ep_ctx->deq);
}
-static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
- unsigned int slot_id, unsigned int ep_index,
- unsigned int stream_id, struct xhci_td *td)
+/*
+ * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or
+ * to enqueue if no TDs are queued (empty ring)
+ * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list
+ * before calling this function
+ */
+static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci,
+ struct xhci_virt_ep *ep,
+ unsigned int stream_id)
{
- struct xhci_virt_device *dev = xhci->devs[slot_id];
- struct xhci_virt_ep *ep = &dev->eps[ep_index];
- struct xhci_ring *ep_ring;
struct xhci_command *cmd;
- struct xhci_segment *new_seg;
- union xhci_trb *new_deq;
- int new_cycle;
+ struct xhci_ring *ring;
+ struct xhci_td *td;
dma_addr_t addr;
- u64 hw_dequeue;
- bool hw_dequeue_found = false;
- bool td_last_trb_found = false;
+ int new_cycle;
u32 trb_sct = 0;
- int ret;
+ int ret = 0;
- ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id,
- ep_index, stream_id);
- if (!ep_ring) {
+ ring = xhci_virt_ep_to_ring(xhci, ep, stream_id);
+ if (!ring) {
xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n",
stream_id);
return -ENODEV;
}
- hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK;
- new_seg = ep_ring->deq_seg;
- new_deq = ep_ring->dequeue;
- new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE;
-
- /*
- * Walk the ring until both the next TRB and hw_dequeue are found (don't
- * move hw_dequeue back if it went forward due to a HW bug). Cycle state
- * is loaded from a known good TRB, track later toggles to maintain it.
- */
- do {
- if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq)
- == (dma_addr_t)hw_dequeue) {
- hw_dequeue_found = true;
- if (td_last_trb_found)
- break;
- }
- if (new_deq == td->end_trb)
- td_last_trb_found = true;
-
- if (td_last_trb_found && trb_is_link(new_deq) &&
- link_trb_toggles_cycle(new_deq))
- new_cycle ^= 0x1;
-
- next_trb(&new_seg, &new_deq);
-
- /* Search wrapped around, bail out */
- if (new_deq == ep->ring->dequeue) {
- xhci_err(xhci, "Error: Failed finding new dequeue state\n");
- return -EINVAL;
- }
-
- } while (!hw_dequeue_found || !td_last_trb_found);
-
- /* Don't update the ring cycle state for the producer (us). */
- addr = xhci_trb_virt_to_dma(new_seg, new_deq);
- if (addr == 0) {
- xhci_warn(xhci, "Can't find dma of new dequeue ptr\n");
- xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq);
- return -EINVAL;
- }
-
if ((ep->ep_state & SET_DEQ_PENDING)) {
- xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n",
- &addr);
+ xhci_warn(xhci, "Set TR Deq already pending\n");
return -EBUSY;
}
/* This function gets called from contexts where it cannot sleep */
cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC);
if (!cmd) {
- xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr);
+ xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n");
return -ENOMEM;
}
+ /*
+ * Move dequeue to the beginning of next td, or to enqueue if ring is
+ * empty. Avoid moving dequeue to a link trb (empty ring) as it causes
+ * issues on some hosts. In that case advance the enqueue to next segment
+ * before moving dequeue to it
+ */
+
+ if (list_empty(&ring->td_list)) {
+ if (trb_is_link(ring->enqueue))
+ inc_enq_past_link(xhci, ring, 0);
+ ep->queued_deq_seg = ring->enq_seg;
+ ep->queued_deq_ptr = ring->enqueue;
+ new_cycle = ring->cycle_state;
+ } else {
+ td = list_first_entry(&ring->td_list, struct xhci_td, td_list);
+ ep->queued_deq_seg = td->start_seg;
+ ep->queued_deq_ptr = td->start_trb;
+ new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE;
+ }
+
+ addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr);
+ if (addr == 0) {
+ xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n",
+ ep->queued_deq_seg, ep->queued_deq_ptr);
+ ret = -EINVAL;
+ goto err_out;
+ }
+
if (stream_id)
trb_sct = SCT_FOR_TRB(SCT_PRI_TR);
ret = queue_command(xhci, cmd,
lower_32_bits(addr) | trb_sct | new_cycle,
upper_32_bits(addr),
- STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) |
- EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false);
- if (ret < 0) {
- xhci_free_command(xhci, cmd);
- return ret;
- }
- ep->queued_deq_seg = new_seg;
- ep->queued_deq_ptr = new_deq;
+ STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) |
+ EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false);
+ if (ret < 0)
+ goto err_out;
xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
"Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle);
- /* Stop the TD queueing code from ringing the doorbell until
- * this command completes. The HC won't set the dequeue pointer
- * if the ring is running, and ringing the doorbell starts the
- * ring running.
+ /*
+ * Stop the TD queueing code from ringing the doorbell until this
+ * command completes. The HC won't set the dequeue pointer if the ring
+ * is running, and ringing the doorbell starts the ring.
*/
ep->ep_state |= SET_DEQ_PENDING;
xhci_ring_cmd_db(xhci);
+
return 0;
+
+err_out:
+ xhci_free_command(xhci, cmd);
+ ep->queued_deq_seg = NULL;
+ ep->queued_deq_ptr = NULL;
+
+ return ret;
}
/* flip_cycle means flip the cycle bit of all but the first and last TRB.
@@ -934,7 +916,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh
}
/* Complete the cancelled URBs we unlinked from td_list. */
-static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep)
+static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
{
struct xhci_ring *ring;
struct xhci_td *td, *tmp_td;
@@ -942,17 +924,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep)
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list,
cancelled_td_list) {
- ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb);
+ ring = xhci_urb_to_transfer_ring(xhci, td->urb);
if (td->cancel_status == TD_CLEARED) {
- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n",
+ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n",
__func__, td->urb);
- xhci_td_cleanup(ep->xhci, td, ring, td->status);
+ xhci_td_cleanup(xhci, td, ring, td->status);
} else {
- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
+ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
__func__, td->urb, td->cancel_status);
}
- if (ep->xhci->xhc_state & XHCI_STATE_DYING)
+ if (xhci->xhc_state & XHCI_STATE_DYING)
return;
}
}
@@ -986,15 +968,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
struct xhci_td *td,
enum xhci_ep_reset_type reset_type)
{
+ struct xhci_port *rhub_port = ep->vdev->rhub_port;
unsigned int slot_id = ep->vdev->slot_id;
int err;
/*
- * Avoid resetting endpoint if link is inactive. Can cause host hang.
- * Device will be reset soon to recover the link so don't do anything
+ * Avoid resetting endpoint if link is inactive or device disonnected.
+ * Can cause host hang.
+ * Device will be reset to recover an inactive link, so don't do anything
*/
- if (ep->vdev->flags & VDEV_PORT_ERROR)
+ if (rhub_port->link_inactive || !rhub_port->connected) {
+ ep->ep_state |= EP_DROP_PENDING;
return -ENODEV;
+ }
/* add td to cancelled list and let reset ep handler take care of it */
if (reset_type == EP_HARD_RESET) {
@@ -1031,15 +1017,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
* only call this when ring is not in a running state
*/
-static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
+static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
{
- struct xhci_hcd *xhci;
struct xhci_td *td = NULL;
struct xhci_td *tmp_td = NULL;
struct xhci_td *cached_td = NULL;
struct xhci_ring *ring;
u64 hw_deq;
- unsigned int slot_id = ep->vdev->slot_id;
int err;
/*
@@ -1049,8 +1033,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
if (ep->ep_state & SET_DEQ_PENDING)
return 0;
- xhci = ep->xhci;
-
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) {
xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
"Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p",
@@ -1064,6 +1046,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
td->urb, td->urb->stream_id);
continue;
}
+
+ /* device disconnected or link error, ep will be dropped */
+ if (ep->ep_state & EP_DROP_PENDING) {
+ td->cancel_status = TD_CLEARED;
+ continue;
+ }
+
/*
* If a ring stopped on the TD we need to cancel then we have to
* move the xHC endpoint ring dequeue pointer past this TD.
@@ -1115,9 +1104,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
if (!cached_td)
return 0;
- err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index,
- cached_td->urb->stream_id,
- cached_td);
+ err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id);
+
if (err) {
/* Failed to move past cached td, just set cached TDs to no-op */
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) {
@@ -1145,23 +1133,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
*
* Call under xhci->lock on a stopped endpoint.
*/
-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep)
+void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
{
- xhci_invalidate_cancelled_tds(ep);
- xhci_giveback_invalidated_tds(ep);
+ xhci_invalidate_cancelled_tds(xhci, ep);
+ xhci_giveback_invalidated_tds(xhci, ep);
}
/*
* Returns the TD the endpoint ring halted on.
* Only call for non-running rings without streams.
*/
-static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep)
+static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
{
struct xhci_td *td;
u64 hw_deq;
if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */
- hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0);
+ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0);
hw_deq &= TR_DEQ_PTR_MASK;
td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list);
if (trb_in_td(td, hw_deq))
@@ -1236,7 +1224,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id,
reset_type = EP_SOFT_RESET;
} else {
reset_type = EP_HARD_RESET;
- td = find_halted_td(ep);
+ td = find_halted_td(xhci, ep);
if (td)
td->status = -EPROTO;
}
@@ -1294,13 +1282,17 @@ reset_done:
}
}
+ /* link is inactive or disconnected, ep is not running and shouldn't be restarted */
+ if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected)
+ ep->ep_state |= EP_DROP_PENDING;
+
/* will queue a set TR deq if stopped on a cancelled, uncleared TD */
- xhci_invalidate_cancelled_tds(ep);
+ xhci_invalidate_cancelled_tds(xhci, ep);
ep->ep_state &= ~EP_STOP_CMD_PENDING;
/* Otherwise ring the doorbell(s) to restart queued transfers */
- xhci_giveback_invalidated_tds(ep);
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
+ xhci_giveback_invalidated_tds(xhci, ep);
+ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
}
static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring)
@@ -1521,14 +1513,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
/* HW cached TDs cleared from cache, give them back */
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list,
cancelled_td_list) {
- ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb);
+ ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb);
if (td->cancel_status == TD_CLEARING_CACHE) {
td->cancel_status = TD_CLEARED;
- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n",
+ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n",
__func__, td->urb);
- xhci_td_cleanup(ep->xhci, td, ep_ring, td->status);
+ xhci_td_cleanup(xhci, td, ep_ring, td->status);
} else {
- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
+ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
__func__, td->urb, td->cancel_status);
}
}
@@ -1539,17 +1531,17 @@ cleanup:
/* Check for deferred or newly cancelled TDs */
if (!list_empty(&ep->cancelled_td_list)) {
- xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n",
+ xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n",
__func__);
- xhci_invalidate_cancelled_tds(ep);
+ xhci_invalidate_cancelled_tds(xhci, ep);
/* Try to restart the endpoint if all is done */
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
+ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
/* Start giving back any TDs invalidated above */
- xhci_giveback_invalidated_tds(ep);
+ xhci_giveback_invalidated_tds(xhci, ep);
} else {
/* Restart any rings with pending URBs */
- xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__);
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
+ xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__);
+ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
}
}
@@ -1575,16 +1567,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id,
"Ignoring reset ep completion code of %u", cmd_comp_code);
/* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */
- xhci_invalidate_cancelled_tds(ep);
+ xhci_invalidate_cancelled_tds(xhci, ep);
/* Clear our internal halted state */
ep->ep_state &= ~EP_HALTED;
- xhci_giveback_invalidated_tds(ep);
+ xhci_giveback_invalidated_tds(xhci, ep);
/* if this was a soft reset, then restart */
if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP)
- ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
+ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
}
static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command,
@@ -1992,13 +1984,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci)
static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
{
struct xhci_virt_device *vdev = NULL;
- struct usb_hcd *hcd;
- u32 port_id;
- u32 portsc, cmd_reg;
- unsigned int hcd_portnum;
struct xhci_bus_state *bus_state;
- bool bogus_port_status = false;
struct xhci_port *port;
+ struct usb_hcd *hcd;
+ bool bogus_port_status = false;
+ unsigned int hcd_portnum;
+ u32 cmd_reg;
+ u32 port_id;
+ u32 portsc;
+ u32 pls;
/* Port status change events always have a successful completion code */
if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS)
@@ -2025,7 +2019,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
vdev = xhci->devs[port->slot_id];
/* We might get interrupts after shared_hcd is removed */
- if (port->rhub == &xhci->usb3_rhub && xhci->shared_hcd == NULL) {
+ if (port->rhub == &xhci->usb3_rhub && xhci_get_usb3_hcd(xhci) == NULL) {
xhci_dbg(xhci, "ignore port event for removed USB3 hcd\n");
bogus_port_status = true;
goto cleanup;
@@ -2035,6 +2029,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
bus_state = &port->rhub->bus_state;
hcd_portnum = port->hcd_portnum;
portsc = xhci_portsc_readl(port);
+ pls = portsc & PORT_PLS_MASK;
xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n",
hcd->self.busnum, hcd_portnum + 1, port_id, portsc);
@@ -2046,11 +2041,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
usb_hcd_resume_root_hub(hcd);
}
- if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) {
- if (!(portsc & PORT_RESET))
- vdev->flags |= VDEV_PORT_ERROR;
- } else if (vdev && portsc & PORT_RC) {
- vdev->flags &= ~VDEV_PORT_ERROR;
+ /*
+ * Tag broken links to avoid retries while hub driver sorts it out.
+ * Link status is not relible while port is in reset.
+ */
+ if (!(portsc & PORT_RESET)) {
+ port->link_inactive = (pls == XDEV_INACTIVE);
+ port->connected = !!(portsc & PORT_CONNECT);
}
if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) {
@@ -2600,6 +2597,17 @@ static bool xhci_spurious_success_tx_event(struct xhci_hcd *xhci,
}
}
+static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, dma_addr_t dma)
+{
+ struct xhci_td *td;
+
+ if (dma)
+ list_for_each_entry(td, &ep_ring->td_list, td_list)
+ if (trb_in_td(td, dma))
+ return td;
+ return NULL;
+}
+
/*
* If this function returns an error condition, it means it got a Transfer
* event with a corrupted Slot ID, Endpoint ID, or TRB DMA address.
@@ -2794,8 +2802,11 @@ static int handle_tx_event(struct xhci_hcd *xhci,
xhci_dequeue_td(xhci, td, ep_ring, td->status);
}
- /* If the TRB pointer is NULL, missed TDs will be skipped on the next event */
- if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !ep_trb_dma)
+ /*
+ * We don't know how many TDs were missed when ep_trb_dma is zero (as permitted by
+ * xHCI 1.0) or bogus. Bail out leaving ep->skip set, next event will sort it out.
+ */
+ if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !find_td_by_dma(ep_ring, ep_trb_dma))
return 0;
if (list_empty(&ep_ring->td_list)) {
@@ -3948,88 +3959,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci,
/* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */
static int xhci_ist_microframes(struct xhci_hcd *xhci)
{
- int ist = HCS_IST_VALUE(xhci->hcs_params2);
+ int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2);
if (xhci->hcs_params2 & HCS_IST_UNIT)
ist *= 8;
return ist;
}
+
+static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb,
+ struct xhci_virt_ep *ep, int i)
+{
+ if (urb->transfer_flags & URB_ISO_ASAP)
+ return false;
+
+ if (xhci->hcc_params & HCC_CFC)
+ return true;
+
+ /* set frame id for first TD of first URB in stream */
+ if (ep->next_uframe == -1 && i == 0)
+ return true;
+
+ return false;
+}
+
/*
- * Calculates Frame ID field of the isochronous TRB identifies the
- * target frame that the Interval associated with this Isochronous
- * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec.
- *
- * Returns actual frame id on success, negative value on error.
+ * Check if frame is in the valid frame window, including start and end.
+ * If start > end then assume window wrapped around at a limit the frame
+ * value won't exceed.
*/
-static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci,
- struct urb *urb, int index)
+static bool xhci_frame_in_range(u32 frame, u32 start, u32 end)
{
- int start_frame, ist, ret = 0;
- int start_frame_id, end_frame_id, current_frame_id;
+ /* frame window end wrapped around */
+ if (start > end)
+ return frame >= start || frame <= end;
- if (urb->dev->speed == USB_SPEED_LOW ||
- urb->dev->speed == USB_SPEED_FULL)
- start_frame = urb->start_frame + index * urb->interval;
- else
- start_frame = (urb->start_frame + index * urb->interval) >> 3;
+ return frame >= start && frame <= end;
+}
- ist = xhci_ist_microframes(xhci);
+/*
+ * Set the urb->start_frame of the URB.
+ *
+ * Returns microframe index of first TD
+ */
+static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb,
+ struct xhci_virt_ep *ep)
+{
+ u32 curr_frame, start_uframe;
+ u32 urb_start, urb_end;
+ u32 win_start, win_end;
+ bool frame_unit;
+ int uinterval;
+ u32 mfindex;
+ int ist;
- /* Software shall not schedule an Isoch TD with a Frame ID value that
- * is less than the Start Frame ID or greater than the End Frame ID,
- * where:
- *
- * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048
- * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048
- *
- * Both the End Frame ID and Start Frame ID values are calculated
- * in microframes. When software determines the valid Frame ID value;
- * The End Frame ID value should be rounded down to the nearest Frame
- * boundary, and the Start Frame ID value should be rounded up to the
- * nearest Frame boundary.
- */
- current_frame_id = readl(&xhci->run_regs->microframe_index);
- start_frame_id = roundup(current_frame_id + ist + 1, 8);
- end_frame_id = rounddown(current_frame_id + 895 * 8, 8);
+ /* check if urb uses frame units instead of microframes */
+ frame_unit = (urb->dev->speed == USB_SPEED_FULL ||
+ urb->dev->speed == USB_SPEED_LOW);
- start_frame &= 0x7ff;
- start_frame_id = (start_frame_id >> 3) & 0x7ff;
- end_frame_id = (end_frame_id >> 3) & 0x7ff;
+ uinterval = urb->interval;
+ if (frame_unit)
+ uinterval *= 8;
- if (start_frame_id < end_frame_id) {
- if (start_frame > end_frame_id ||
- start_frame < start_frame_id)
- ret = -EINVAL;
- } else if (start_frame_id > end_frame_id) {
- if ((start_frame > end_frame_id &&
- start_frame < start_frame_id))
- ret = -EINVAL;
+ /* get current microframe index and isoc scheduling threshold */
+ mfindex = readl(&xhci->run_regs->microframe_index);
+ ist = xhci_ist_microframes(xhci);
+
+ /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */
+ curr_frame = MFINDEX_TO_FRAME(mfindex);
+ win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES;
+ win_end = (curr_frame + 895) % MAX_FRAMES;
+
+ /* Is this the first URB starting the whole isoc data flow? */
+ if (ep->next_uframe < 0) {
+ /* align first URB to next interval boundary, or at last to full frame */
+ start_uframe = mfindex + ist + XHCI_CFC_DELAY;
+ start_uframe = roundup(start_uframe, 8);
+ start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES;
} else {
- ret = -EINVAL;
- }
+ /* URB is mid stream and expected to handle the next frame */
+ start_uframe = ep->next_uframe;
+ urb_start = start_uframe / 8;
+ urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8;
+ urb_end %= MAX_FRAMES;
- if (index == 0) {
- if (ret == -EINVAL || start_frame == start_frame_id) {
- start_frame = start_frame_id + 1;
- if (urb->dev->speed == USB_SPEED_LOW ||
- urb->dev->speed == USB_SPEED_FULL)
- urb->start_frame = start_frame;
- else
- urb->start_frame = start_frame << 3;
- ret = 0;
- }
- }
+ if (!xhci_frame_in_range(urb_start, win_start, win_end))
+ xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n",
+ urb, urb_start, win_start, win_end);
- if (ret) {
- xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n",
- start_frame, current_frame_id, index,
- start_frame_id, end_frame_id);
- xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n");
- return ret;
+ if (!xhci_frame_in_range(urb_end, win_start, win_end))
+ xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n",
+ urb, urb_start, win_start, win_end);
}
+ /* set urb->start_frame */
+ urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe;
- return start_frame;
+ return start_uframe;
}
/* Check if we should generate event interrupt for a TD in an isoc URB */
@@ -4069,7 +4094,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
int i, j;
bool more_trbs_coming;
struct xhci_virt_ep *xep;
- int frame_id;
+ int uinterval = urb->interval;
+ int start_uframe;
xep = &xhci->devs[slot_id]->eps[ep_index];
ep_ring = xhci->devs[slot_id]->eps[ep_index].ring;
@@ -4085,6 +4111,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
start_cycle = ep_ring->cycle_state;
urb_priv = urb->hcpriv;
+
+ if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW)
+ uinterval = urb->interval * 8;
+
+ start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep);
+
/* Queue the TRBs for each TD, even if they are zero-length */
for (i = 0; i < num_tds; i++) {
unsigned int total_pkt_count, max_pkt;
@@ -4116,14 +4148,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
goto cleanup;
}
td = &urb_priv->td[i];
- /* use SIA as default, if frame id is used overwrite it */
- sia_frame_id = TRB_SIA;
- if (!(urb->transfer_flags & URB_ISO_ASAP) &&
- (xhci->hcc_params & HCC_CFC)) {
- frame_id = xhci_get_isoc_frame_id(xhci, urb, i);
- if (frame_id >= 0)
- sia_frame_id = TRB_FRAME_ID(frame_id);
+
+
+ /* Choose SIA or frame ID based scheduling for this TD */
+ if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) {
+ sia_frame_id = (start_uframe + i * uinterval) / 8;
+ sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES);
+ } else {
+ sia_frame_id = TRB_SIA;
}
+
/*
* Set isoc specific data for the first TRB in a TD.
* Prevent HW from getting the TRBs by keeping the cycle state
@@ -4202,9 +4236,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
}
}
- /* store the next frame id */
- if (xhci->hcc_params & HCC_CFC)
- xep->next_frame_id = urb->start_frame + num_tds * urb->interval;
+ xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES;
if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) {
if (xhci->quirks & XHCI_AMD_PLL_FIX)
@@ -4251,11 +4283,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
struct xhci_virt_device *xdev;
struct xhci_ring *ep_ring;
struct xhci_ep_ctx *ep_ctx;
- int start_frame;
+ struct xhci_virt_ep *xep;
int num_tds, num_trbs, i;
int ret;
- struct xhci_virt_ep *xep;
- int ist;
xdev = xhci->devs[slot_id];
xep = &xhci->devs[slot_id]->eps[ep_index];
@@ -4281,38 +4311,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
*/
check_interval(urb, ep_ctx);
- /* Calculate the start frame and put it in urb->start_frame. */
- if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) {
- if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) {
- urb->start_frame = xep->next_frame_id;
- goto skip_start_over;
- }
- }
-
- start_frame = readl(&xhci->run_regs->microframe_index);
- start_frame &= 0x3fff;
/*
- * Round up to the next frame and consider the time before trb really
- * gets scheduled by hardare.
+ * Check if this starts the isoc data flow. Relies on hw setting ep ctx
+ * state after doorbell ring. Consider adding list_empty(td_list) check
*/
- ist = xhci_ist_microframes(xhci);
- start_frame += ist + XHCI_CFC_DELAY;
- start_frame = roundup(start_frame, 8);
-
- /*
- * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT
- * is greate than 8 microframes.
- */
- if (urb->dev->speed == USB_SPEED_LOW ||
- urb->dev->speed == USB_SPEED_FULL) {
- start_frame = roundup(start_frame, urb->interval << 3);
- urb->start_frame = start_frame >> 3;
- } else {
- start_frame = roundup(start_frame, urb->interval);
- urb->start_frame = start_frame;
- }
-
-skip_start_over:
+ if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING)
+ xep->next_uframe = -1;
return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index);
}
diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c
index e7e6d569f1db..6f235d1e117e 100644
--- a/drivers/usb/host/xhci-tegra.c
+++ b/drivers/usb/host/xhci-tegra.c
@@ -6,6 +6,7 @@
* Copyright (C) 2014 Google, Inc.
*/
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
@@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra)
u32 value;
cap_regs = tegra->regs;
- op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase));
+ op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)),
ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000);
@@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev)
goto remove_usb2;
}
- if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
+ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4)
xhci->shared_hcd->can_do_streams = 1;
err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED);
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 091c82ca8ee2..a9e47e178c28 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -21,6 +21,7 @@
#include <linux/dmi.h>
#include <linux/dma-mapping.h>
#include <linux/usb/xhci-sideband.h>
+#include <linux/bitfield.h>
#include "xhci.h"
#include "xhci-trace.h"
@@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
goto free_priv;
}
- if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
+ if (xhci->devs[slot_id]->rhub_port->link_inactive) {
xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
ret = -ENODEV;
goto free_priv;
@@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
}
/* In this case no commands are pending but the endpoint is stopped */
- if (ep->ep_state & EP_CLEARING_TT) {
+ if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) {
/* and cancelled TDs can be given back right away */
xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n",
urb->dev->slot_id, ep_index, ep->ep_state);
- xhci_process_cancelled_tds(ep);
+ xhci_process_cancelled_tds(xhci, ep);
} else {
/* Otherwise, queue a new Stop Endpoint command */
command = xhci_alloc_command(xhci, false, GFP_ATOMIC);
@@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci,
* level page entries), but that's an optional feature for xHCI host
* controllers. xHCs must support at least 4 stream IDs.
*/
- max_streams = HCC_MAX_PSA(xhci->hcc_params);
+ max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params);
if (*num_stream_ctxs > max_streams) {
xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n",
max_streams);
@@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev,
/* MaxPSASize value 0 (2 streams) means streams are not supported */
if ((xhci->quirks & XHCI_BROKEN_STREAMS) ||
- HCC_MAX_PSA(xhci->hcc_params) < 4) {
+ GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) {
xhci_dbg(xhci, "xHCI controller does not support streams.\n");
return -ENOSYS;
}
@@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd,
xhci_get_slot_state(xhci, virt_dev->out_ctx));
xhci_dbg(xhci, "Not freeing device rings.\n");
/* Don't treat this as an error. May change my mind later. */
- virt_dev->flags = 0;
ret = 0;
goto command_cleanup;
case COMP_SUCCESS:
@@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd,
}
/* If necessary, update the number of active TTs on this root port */
xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps);
- virt_dev->flags = 0;
ret = 0;
command_cleanup:
+ for (i = 0; i < EP_CTX_PER_DEV; i++)
+ virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING;
+
xhci_free_command(xhci, reset_device_cmd);
return ret;
}
@@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci,
int besl_device = 0;
u32 field;
- u2del = HCS_U2_LATENCY(xhci->hcs_params3);
+ u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3);
field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
if (field & USB_BESL_SUPPORT) {
@@ -5433,6 +5435,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
struct device *dev = hcd->self.sysdev;
int retval;
u32 hcs_params1;
+ u32 hc_capbase;
/* Accept arbitrarily long scatter-gather lists */
hcd->self.sg_tablesize = ~0;
@@ -5453,27 +5456,33 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
mutex_init(&xhci->mutex);
xhci->main_hcd = hcd;
xhci->cap_regs = hcd->regs;
- xhci->op_regs = hcd->regs +
- HC_LENGTH(readl(&xhci->cap_regs->hc_capbase));
+ hc_capbase = readl(&xhci->cap_regs->hc_capbase);
+ if (hc_capbase == U32_MAX) {
+ xhci_warn(xhci, "Host controller not accessible, removed?\n");
+ return -ENODEV;
+ }
+ xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase);
+
xhci->run_regs = hcd->regs +
(readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK);
/* Cache read-only capability registers */
hcs_params1 = readl(&xhci->cap_regs->hcs_params1);
xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2);
xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3);
- xhci->hci_version = HC_VERSION(readl(&xhci->cap_regs->hc_capbase));
+ xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase);
xhci->hcc_params = readl(&xhci->cap_regs->hcc_params);
if (xhci->hci_version > 0x100)
xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2);
xhci->dma_mask_bits = 64;
- xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS);
- xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS);
+ xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS);
+ xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS);
+
/* xhci-plat or xhci-pci might have set max_interrupters already */
if (!xhci->max_interrupters)
- xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS);
- else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1))
- xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1);
+ xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS);
+ else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1))
+ xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1);
xhci->quirks |= quirks;
@@ -5514,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
* DMA_BIT_MASK(32)) in this xhci_gen_setup().
*/
if (xhci->quirks & XHCI_NO_64BIT_SUPPORT)
- xhci->hcc_params &= ~BIT(0);
+ xhci->hcc_params &= ~HCC_64BIT_ADDR;
/*
* Set dma_mask and coherent_dma_mask to 64-bits if xHC supports
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 708e3ccc5d87..c7bfa7f028d3 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -290,6 +290,11 @@ struct xhci_run_regs {
struct xhci_intr_reg ir_set[1024];
};
+/* Bits [13:3] of the microframe index equals the 1ms frame index */
+#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff)
+#define MAX_FRAMES 2048
+#define MAX_UFRAMES (MAX_FRAMES * 8)
+
/**
* struct doorbell_array
*
@@ -677,9 +682,9 @@ struct xhci_virt_ep {
#define EP_SOFT_CLEAR_TOGGLE BIT(7)
/* usb_hub_clear_tt_buffer is in progress */
#define EP_CLEARING_TT BIT(8)
+#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */
/* ---- Related to URB cancellation ---- */
struct list_head cancelled_td_list;
- struct xhci_hcd *xhci;
/* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue
* command. We'll need to update the ring's dequeue segment and dequeue
* pointer after the command completes.
@@ -699,7 +704,7 @@ struct xhci_virt_ep {
struct list_head bw_endpoint_list;
unsigned long stop_time;
/* Isoch Frame ID checking storage */
- int next_frame_id;
+ int next_uframe;
/* Use new Isoch TRB layout needed for extended TBC support */
bool use_extended_tbc;
/* set if this endpoint is controlled via sideband access*/
@@ -753,14 +758,6 @@ struct xhci_virt_device {
struct xhci_port *rhub_port;
struct xhci_interval_bw_table *bw_table;
struct xhci_tt_bw_info *tt_info;
- /*
- * flags for state tracking based on events and issued commands.
- * Software can not rely on states from output contexts because of
- * latency between events and xHC updating output context values.
- * See xhci 1.1 section 4.8.3 for more details
- */
- unsigned long flags;
-#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */
/* The current max exit latency for the enabled USB3 link states. */
u16 current_mel;
@@ -1480,6 +1477,8 @@ struct xhci_port {
int hcd_portnum;
struct xhci_hub *rhub;
struct xhci_port_cap *port_cap;
+ unsigned int link_inactive:1;
+ unsigned int connected:1;
unsigned int lpm_incapable:1;
unsigned long resume_timestamp;
bool rexit_active;
@@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len);
unsigned int xhci_num_trbs_free(struct xhci_ring *ring);
int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
int suspend, gfp_t gfp_flags);
-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep);
+void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep);
void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
struct xhci_interrupter *ir,
bool clear_ehb);
diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c
index 4a7f955ba85b..ecc81c93c6ce 100644
--- a/drivers/usb/misc/cypress_cy7c63.c
+++ b/drivers/usb/misc/cypress_cy7c63.c
@@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request,
address, data, iobuf, CYPRESS_MAX_REQSIZE,
USB_CTRL_GET_TIMEOUT);
/* we must not process garbage */
- if (retval < 2)
+ if (retval < 2) {
+ if (retval >= 0)
+ retval = -EIO;
goto err_buf;
+ }
/* store returned data (more READs to be added) */
switch (request) {
@@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr,
dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num);
result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0);
-
dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result);
+ if (result < 0)
+ return result;
return sprintf(buf, "%d", cyp->port[port_num]);
}
diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c
index 78e94dd89da5..fcb627b49eac 100644
--- a/drivers/usb/misc/usb-ljca.c
+++ b/drivers/usb/misc/usb-ljca.c
@@ -595,6 +595,9 @@ static int ljca_enumerate_gpio(struct ljca_adapter *adap)
if (gpio_num > LJCA_MAX_GPIO_NUM)
return -EINVAL;
+ if (desc->bank_num > ARRAY_SIZE(valid_pin))
+ return -EINVAL;
+
/* construct platform data */
gpio_info = kzalloc_obj(*gpio_info);
if (!gpio_info)
diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c
index 98071b25ac07..8759df49be28 100644
--- a/drivers/usb/misc/usbtest.c
+++ b/drivers/usb/misc/usbtest.c
@@ -3054,6 +3054,7 @@ static struct usb_driver usbtest_driver = {
.disconnect = usbtest_disconnect,
.suspend = usbtest_suspend,
.resume = usbtest_resume,
+ .no_dynamic_id = 1,
};
/*-------------------------------------------------------------------------*/
diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c
index 1ce48f5832d7..bd0ad7c3261d 100644
--- a/drivers/usb/misc/uss720.c
+++ b/drivers/usb/misc/uss720.c
@@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha
}
printk(KERN_WARNING "get_1284_register timeout\n");
kill_all_async_requests_priv(priv);
+ kref_put(&rq->ref_count, destroy_async);
return -EIO;
}
diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c
index 66dbfe1705d5..a40bf5bad2d5 100644
--- a/drivers/usb/mtu3/mtu3_core.c
+++ b/drivers/usb/mtu3/mtu3_core.c
@@ -1037,9 +1037,14 @@ int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
if (!mtu->gadget_driver)
return 0;
- if (mtu->connected)
+ /* Prevent runtime suspend when active connection exists */
+ if (mtu->connected && PMSG_IS_AUTO(msg))
return -EBUSY;
+ /* Perform soft disconnect for system suspend */
+ if (mtu->softconnect && !PMSG_IS_AUTO(msg))
+ mtu3_dev_on_off(mtu, 0);
+
mtu3_dev_suspend(mtu);
synchronize_irq(mtu->irq);
@@ -1055,5 +1060,9 @@ int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
mtu3_dev_resume(mtu);
+ /* Restore soft connect for system resume */
+ if (mtu->softconnect && !PMSG_IS_AUTO(msg))
+ mtu3_dev_on_off(mtu, 1);
+
return 0;
}
diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c
index 7c657ea2dabd..8138b3f3096a 100644
--- a/drivers/usb/mtu3/mtu3_host.c
+++ b/drivers/usb/mtu3/mtu3_host.c
@@ -46,6 +46,14 @@
#define WC1_IS_P_95 BIT(12)
#define WC1_IS_EN_P0_95 BIT(6)
+/* mt8196 */
+#define PERI_WK_CTRL0_8196 0x08
+#define WC0_IS_EN_P0_96 BIT(0)
+#define WC0_IS_EN_P1_96 BIT(7)
+
+#define PERI_WK_CTRL1_8196 0x10
+#define WC1_IS_EN_P2_96 BIT(0)
+
/* mt2712 etc */
#define PERI_SSUSB_SPM_CTRL 0x0
#define SSC_IP_SLEEP_EN BIT(4)
@@ -59,6 +67,9 @@ enum ssusb_uwk_vers {
SSUSB_UWK_V1_3, /* mt8195 IP0 */
SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */
SSUSB_UWK_V1_6, /* mt8195 IP3 */
+ SSUSB_UWK_V1_7, /* mt8196 IP0 */
+ SSUSB_UWK_V1_8, /* mt8196 IP1 */
+ SSUSB_UWK_V1_9, /* mt8196 IP2 */
};
/*
@@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable)
msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0;
break;
+ case SSUSB_UWK_V1_7:
+ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196;
+ msk = WC0_IS_EN_P0_96;
+ val = enable ? msk : 0;
+ break;
+ case SSUSB_UWK_V1_8:
+ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196;
+ msk = WC0_IS_EN_P1_96;
+ val = enable ? msk : 0;
+ break;
+ case SSUSB_UWK_V1_9:
+ reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196;
+ msk = WC1_IS_EN_P2_96;
+ val = enable ? msk : 0;
+ break;
case SSUSB_UWK_V2:
reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL;
msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN;
diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c
index cc8a864dbd63..b4f4c776adb9 100644
--- a/drivers/usb/mtu3/mtu3_plat.c
+++ b/drivers/usb/mtu3/mtu3_plat.c
@@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg)
ssusb_phy_power_off(ssusb);
clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks);
- ssusb_wakeup_set(ssusb, true);
+ if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg)))
+ ssusb_wakeup_set(ssusb, true);
return 0;
sleep_err:
diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig
index f56929267eaa..1b0d27d8e02f 100644
--- a/drivers/usb/musb/Kconfig
+++ b/drivers/usb/musb/Kconfig
@@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC
tristate "Microchip PolarFire SoC platforms"
depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST
depends on NOP_USB_XCEIV
- select USB_MUSB_DUAL_ROLE
help
Say Y here to enable support for USB on Microchip's PolarFire SoC.
diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c
index 8efd2fa472f9..31b061248991 100644
--- a/drivers/usb/musb/musb_host.c
+++ b/drivers/usb/musb/musb_host.c
@@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum)
status = -EPIPE;
} else if (rx_csr & MUSB_RXCSR_H_ERROR) {
- dev_err(musb->controller, "ep%d RX three-strikes error", epnum);
+ dev_err_ratelimited(musb->controller,
+ "ep%d RX three-strikes error\n", epnum);
/*
* The three-strikes error could only happen when the USB
@@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum)
rx_csr &= ~MUSB_RXCSR_H_ERROR;
musb_writew(epio, MUSB_RXCSR, rx_csr);
+ /*
+ * Unplugging a USB-Ethernet adapter while it is busy can make
+ * the controller keep re-asserting the three-strikes error for
+ * this endpoint before the disconnect is processed. That floods
+ * the log and can wedge the host port until reboot. Drop the
+ * stale pending RX interrupt on platforms that support it (e.g.
+ * AM335x/DSPS) to break the storm; the transfer is still
+ * aborted below via the fault path.
+ */
+ musb_platform_clear_ep_rxintr(musb, epnum);
+
} else if (rx_csr & MUSB_RXCSR_DATAERROR) {
if (USB_ENDPOINT_XFER_ISOC != qh->type) {
diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c
index 35d79f11b03d..1986f7e9d9be 100644
--- a/drivers/usb/phy/phy-fsl-usb.c
+++ b/drivers/usb/phy/phy-fsl-usb.c
@@ -46,7 +46,7 @@
static const char driver_name[] = "fsl-usb2-otg";
-const pm_message_t otg_suspend_state = {
+static const pm_message_t otg_suspend_state = {
.event = 1,
};
@@ -57,11 +57,11 @@ static struct fsl_otg *fsl_otg_dev;
static int srp_wait_done;
/* FSM timers */
-struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr,
+static struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr,
*b_ase0_brst_tmr, *b_se0_srp_tmr;
/* Driver specific timers */
-struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr,
+static struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr,
*b_srp_wait_tmr, *a_wait_enum_tmr;
static struct list_head active_timers;
@@ -102,7 +102,7 @@ static void (*_fsl_writel)(u32 v, unsigned __iomem *p);
#define fsl_writel(val, addr) writel(val, addr)
#endif /* CONFIG_PPC32 */
-int write_ulpi(u8 addr, u8 data)
+static int write_ulpi(u8 addr, u8 data)
{
u32 temp;
@@ -115,7 +115,7 @@ int write_ulpi(u8 addr, u8 data)
/* Operations that will be called from OTG Finite State Machine */
/* Charge vbus for vbus pulsing in SRP */
-void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on)
+static void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on)
{
u32 tmp;
@@ -133,7 +133,7 @@ void fsl_otg_chrg_vbus(struct otg_fsm *fsm, int on)
}
/* Discharge vbus through a resistor to ground */
-void fsl_otg_dischrg_vbus(int on)
+static void fsl_otg_dischrg_vbus(int on)
{
u32 tmp;
@@ -151,7 +151,7 @@ void fsl_otg_dischrg_vbus(int on)
}
/* A-device driver vbus, controlled through PP bit in PORTSC */
-void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on)
+static void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on)
{
u32 tmp;
@@ -169,7 +169,7 @@ void fsl_otg_drv_vbus(struct otg_fsm *fsm, int on)
* Pull-up D+, signalling connect by periperal. Also used in
* data-line pulsing in SRP
*/
-void fsl_otg_loc_conn(struct otg_fsm *fsm, int on)
+static void fsl_otg_loc_conn(struct otg_fsm *fsm, int on)
{
u32 tmp;
@@ -188,7 +188,7 @@ void fsl_otg_loc_conn(struct otg_fsm *fsm, int on)
* port. In host mode, controller will automatically send SOF.
* Suspend will block the data on the port.
*/
-void fsl_otg_loc_sof(struct otg_fsm *fsm, int on)
+static void fsl_otg_loc_sof(struct otg_fsm *fsm, int on)
{
u32 tmp;
@@ -203,7 +203,7 @@ void fsl_otg_loc_sof(struct otg_fsm *fsm, int on)
}
/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */
-void fsl_otg_start_pulse(struct otg_fsm *fsm)
+static void fsl_otg_start_pulse(struct otg_fsm *fsm)
{
u32 tmp;
@@ -219,7 +219,7 @@ void fsl_otg_start_pulse(struct otg_fsm *fsm)
fsl_otg_add_timer(fsm, b_data_pulse_tmr);
}
-void b_data_pulse_end(unsigned long foo)
+static void b_data_pulse_end(unsigned long foo)
{
#ifdef HA_DATA_PULSE
#else
@@ -230,7 +230,7 @@ void b_data_pulse_end(unsigned long foo)
fsl_otg_pulse_vbus();
}
-void fsl_otg_pulse_vbus(void)
+static void fsl_otg_pulse_vbus(void)
{
srp_wait_done = 0;
fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 1);
@@ -238,7 +238,7 @@ void fsl_otg_pulse_vbus(void)
fsl_otg_add_timer(&fsl_otg_dev->fsm, b_vbus_pulse_tmr);
}
-void b_vbus_pulse_end(unsigned long foo)
+static void b_vbus_pulse_end(unsigned long foo)
{
fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 0);
@@ -251,7 +251,7 @@ void b_vbus_pulse_end(unsigned long foo)
fsl_otg_add_timer(&fsl_otg_dev->fsm, b_srp_wait_tmr);
}
-void b_srp_end(unsigned long foo)
+static void b_srp_end(unsigned long foo)
{
fsl_otg_dischrg_vbus(0);
srp_wait_done = 1;
@@ -266,7 +266,7 @@ void b_srp_end(unsigned long foo)
* a_host will start by SRP. It needs to set b_hnp_enable before
* actually suspending to start HNP
*/
-void a_wait_enum(unsigned long foo)
+static void a_wait_enum(unsigned long foo)
{
VDBG("a_wait_enum timeout\n");
if (!fsl_otg_dev->phy.otg->host->b_hnp_enable)
@@ -276,13 +276,13 @@ void a_wait_enum(unsigned long foo)
}
/* The timeout callback function to set time out bit */
-void set_tmout(unsigned long indicator)
+static void set_tmout(unsigned long indicator)
{
*(int *)indicator = 1;
}
/* Initialize timers */
-int fsl_otg_init_timers(struct otg_fsm *fsm)
+static int fsl_otg_init_timers(struct otg_fsm *fsm)
{
/* FSM used timers */
a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE,
@@ -339,7 +339,7 @@ int fsl_otg_init_timers(struct otg_fsm *fsm)
}
/* Uninitialize timers */
-void fsl_otg_uninit_timers(void)
+static void fsl_otg_uninit_timers(void)
{
/* FSM used timers */
kfree(a_wait_vrise_tmr);
@@ -391,7 +391,7 @@ static struct fsl_otg_timer *fsl_otg_get_timer(enum otg_fsm_timer t)
}
/* Add timer to timer list */
-void fsl_otg_add_timer(struct otg_fsm *fsm, void *gtimer)
+static void fsl_otg_add_timer(struct otg_fsm *fsm, void *gtimer)
{
struct fsl_otg_timer *timer = gtimer;
struct fsl_otg_timer *tmp_timer;
@@ -421,7 +421,7 @@ static void fsl_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
}
/* Remove timer from the timer list; clear timeout status */
-void fsl_otg_del_timer(struct otg_fsm *fsm, void *gtimer)
+static void fsl_otg_del_timer(struct otg_fsm *fsm, void *gtimer)
{
struct fsl_otg_timer *timer = gtimer;
struct fsl_otg_timer *tmp_timer, *del_tmp;
@@ -443,7 +443,7 @@ static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
}
/* Reset controller, not reset the bus */
-void otg_reset_controller(void)
+static void otg_reset_controller(void)
{
u32 command;
@@ -455,7 +455,7 @@ void otg_reset_controller(void)
}
/* Call suspend/resume routines in host driver */
-int fsl_otg_start_host(struct otg_fsm *fsm, int on)
+static int fsl_otg_start_host(struct otg_fsm *fsm, int on)
{
struct usb_otg *otg = fsm->otg;
struct device *dev;
@@ -522,7 +522,7 @@ end:
* Call suspend and resume function in udc driver
* to stop and start udc driver.
*/
-int fsl_otg_start_gadget(struct otg_fsm *fsm, int on)
+static int fsl_otg_start_gadget(struct otg_fsm *fsm, int on)
{
struct usb_otg *otg = fsm->otg;
struct device *dev;
@@ -704,7 +704,7 @@ static int fsl_otg_start_hnp(struct usb_otg *otg)
* intact. It needs to have knowledge of some USB interrupts
* such as port change.
*/
-irqreturn_t fsl_otg_isr(int irq, void *dev_id)
+static irqreturn_t fsl_otg_isr(int irq, void *dev_id)
{
struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm;
struct usb_otg *otg = ((struct fsl_otg *)dev_id)->phy.otg;
@@ -830,7 +830,7 @@ err:
}
/* OTG Initialization */
-int usb_otg_start(struct platform_device *pdev)
+static int usb_otg_start(struct platform_device *pdev)
{
struct fsl_otg *p_otg;
struct usb_phy *otg_trans = usb_get_phy(USB_PHY_TYPE_USB2);
@@ -1002,7 +1002,7 @@ static void fsl_otg_remove(struct platform_device *pdev)
pdata->exit(pdev);
}
-struct platform_driver fsl_otg_driver = {
+static struct platform_driver fsl_otg_driver = {
.probe = fsl_otg_probe,
.remove = fsl_otg_remove,
.driver = {
diff --git a/drivers/usb/phy/phy-fsl-usb.h b/drivers/usb/phy/phy-fsl-usb.h
index 95bfe7f1b83a..b754077875c3 100644
--- a/drivers/usb/phy/phy-fsl-usb.h
+++ b/drivers/usb/phy/phy-fsl-usb.h
@@ -373,6 +373,6 @@ struct fsl_otg_config {
#define FSL_OTG_NAME "fsl-usb2-otg"
-void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer);
-void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer);
-void fsl_otg_pulse_vbus(void);
+static void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer);
+static void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer);
+static void fsl_otg_pulse_vbus(void);
diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c
index 8c93bde4b816..51d3035f82be 100644
--- a/drivers/usb/renesas_usbhs/common.c
+++ b/drivers/usb/renesas_usbhs/common.c
@@ -813,9 +813,6 @@ static void usbhs_remove(struct platform_device *pdev)
flush_delayed_work(&priv->notify_hotplug_work);
- usbhs_platform_call(priv, hardware_exit, pdev);
- reset_control_assert(priv->rsts);
-
/*
* Explicitly free the IRQ to ensure the interrupt handler is
* disabled and synchronized before freeing resources.
@@ -832,6 +829,9 @@ static void usbhs_remove(struct platform_device *pdev)
if (!usbhs_get_dparam(priv, runtime_pwctrl))
usbhsc_power_ctrl(priv, 0);
+ usbhs_platform_call(priv, hardware_exit, pdev);
+ reset_control_assert(priv->rsts);
+
usbhsc_clk_put(priv);
pm_runtime_disable(&pdev->dev);
}
diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c
index dea039163661..12db0589ee27 100644
--- a/drivers/usb/serial/digi_acceleport.c
+++ b/drivers/usb/serial/digi_acceleport.c
@@ -32,10 +32,6 @@
/* so we can be sure to send the full buffer in one urb */
#define DIGI_OUT_BUF_SIZE 8
-/* port input buffer length -- must be >= transfer buffer length - 3 */
-/* so we can be sure to hold at least one full buffer from one urb */
-#define DIGI_IN_BUF_SIZE 64
-
/* retry timeout while sleeping */
#define DIGI_RETRY_TIMEOUT (HZ/10)
@@ -176,10 +172,10 @@
/* Structures */
struct digi_serial {
- spinlock_t ds_serial_lock;
+ struct mutex open_mutex;
struct usb_serial_port *ds_oob_port; /* out-of-band port */
int ds_oob_port_num; /* index of out-of-band port */
- int ds_device_started;
+ int open_count;
};
struct digi_port {
@@ -194,7 +190,6 @@ struct digi_port {
int dp_throttled;
int dp_throttle_restart;
wait_queue_head_t dp_flush_wait;
- wait_queue_head_t dp_close_wait; /* wait queue for close */
wait_queue_head_t write_wait;
struct usb_serial_port *dp_port;
};
@@ -227,9 +222,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty);
static int digi_open(struct tty_struct *tty, struct usb_serial_port *port);
static void digi_close(struct usb_serial_port *port);
static void digi_dtr_rts(struct usb_serial_port *port, int on);
-static int digi_startup_device(struct usb_serial *serial);
static int digi_startup(struct usb_serial *serial);
-static void digi_disconnect(struct usb_serial *serial);
static void digi_release(struct usb_serial *serial);
static int digi_port_probe(struct usb_serial_port *port);
static void digi_port_remove(struct usb_serial_port *port);
@@ -272,7 +265,7 @@ static struct usb_serial_driver digi_acceleport_2_device = {
.dtr_rts = digi_dtr_rts,
.write = digi_write,
.write_room = digi_write_room,
- .write_bulk_callback = digi_write_bulk_callback,
+ .write_bulk_callback = digi_write_bulk_callback,
.read_bulk_callback = digi_read_bulk_callback,
.chars_in_buffer = digi_chars_in_buffer,
.throttle = digi_rx_throttle,
@@ -282,7 +275,6 @@ static struct usb_serial_driver digi_acceleport_2_device = {
.tiocmget = digi_tiocmget,
.tiocmset = digi_tiocmset,
.attach = digi_startup,
- .disconnect = digi_disconnect,
.release = digi_release,
.port_probe = digi_port_probe,
.port_remove = digi_port_remove,
@@ -301,7 +293,7 @@ static struct usb_serial_driver digi_acceleport_4_device = {
.close = digi_close,
.write = digi_write,
.write_room = digi_write_room,
- .write_bulk_callback = digi_write_bulk_callback,
+ .write_bulk_callback = digi_write_bulk_callback,
.read_bulk_callback = digi_read_bulk_callback,
.chars_in_buffer = digi_chars_in_buffer,
.throttle = digi_rx_throttle,
@@ -311,7 +303,6 @@ static struct usb_serial_driver digi_acceleport_4_device = {
.tiocmget = digi_tiocmget,
.tiocmset = digi_tiocmset,
.attach = digi_startup,
- .disconnect = digi_disconnect,
.release = digi_release,
.port_probe = digi_port_probe,
.port_remove = digi_port_remove,
@@ -335,7 +326,6 @@ static struct usb_serial_driver * const serial_drivers[] = {
* interruptible_sleep_on_timeout is deprecated and has been replaced
* with the equivalent code.
*/
-
static long cond_wait_interruptible_timeout_irqrestore(
wait_queue_head_t *q, long timeout,
spinlock_t *lock, unsigned long flags)
@@ -351,6 +341,13 @@ __releases(lock)
return timeout;
}
+static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial)
+{
+ struct digi_serial *serial_priv = usb_get_serial_data(serial);
+
+ return serial_priv->ds_oob_port;
+}
+
/*
* Digi Write OOB Command
*
@@ -361,15 +358,14 @@ __releases(lock)
* the interruptible flag is true, or a negative error
* returned by usb_submit_urb.
*/
-
static int digi_write_oob_command(struct usb_serial_port *port,
unsigned char *buf, int count, int interruptible)
{
- int ret = 0;
- int len;
- struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port;
+ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial);
struct digi_port *oob_priv = usb_get_serial_port_data(oob_port);
unsigned long flags;
+ int ret = 0;
+ int len;
dev_dbg(&port->dev,
"digi_write_oob_command: TOP: port=%d, count=%d\n",
@@ -406,10 +402,8 @@ static int digi_write_oob_command(struct usb_serial_port *port,
dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n",
__func__, ret);
return ret;
-
}
-
/*
* Digi Write In Band Command
*
@@ -421,16 +415,15 @@ static int digi_write_oob_command(struct usb_serial_port *port,
* timeout ticks. Returns 0 if successful, or a negative
* error returned by digi_write.
*/
-
static int digi_write_inb_command(struct usb_serial_port *port,
unsigned char *buf, int count, unsigned long timeout)
{
- int ret = 0;
- int len;
struct digi_port *priv = usb_get_serial_port_data(port);
unsigned char *data = port->write_urb->transfer_buffer;
unsigned long expire;
unsigned long flags;
+ int ret = 0;
+ int len;
dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n",
priv->dp_port_num, count);
@@ -459,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port,
/* len must be a multiple of 4 and small enough to */
/* guarantee the write will send buffered data first, */
/* so commands are in order with data and not split */
- len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len);
+ len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len);
if (len > 4)
len &= ~3;
@@ -467,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port,
if (priv->dp_out_buf_len > 0) {
data[0] = DIGI_CMD_SEND_DATA;
data[1] = priv->dp_out_buf_len;
- memcpy(data + 2, priv->dp_out_buf,
- priv->dp_out_buf_len);
+ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len);
memcpy(data + 2 + priv->dp_out_buf_len, buf, len);
- port->write_urb->transfer_buffer_length
- = priv->dp_out_buf_len + 2 + len;
+ port->write_urb->transfer_buffer_length =
+ priv->dp_out_buf_len + 2 + len;
} else {
memcpy(data, buf, len);
port->write_urb->transfer_buffer_length = len;
@@ -484,7 +476,6 @@ static int digi_write_inb_command(struct usb_serial_port *port,
count -= len;
buf += len;
}
-
}
spin_unlock_irqrestore(&priv->dp_port_lock, flags);
@@ -495,7 +486,6 @@ static int digi_write_inb_command(struct usb_serial_port *port,
return ret;
}
-
/*
* Digi Set Modem Signals
*
@@ -505,17 +495,15 @@ static int digi_write_inb_command(struct usb_serial_port *port,
* -EINTR if interrupted while sleeping, or a non-zero error
* returned by usb_submit_urb.
*/
-
static int digi_set_modem_signals(struct usb_serial_port *port,
unsigned int modem_signals, int interruptible)
{
-
- int ret;
struct digi_port *port_priv = usb_get_serial_port_data(port);
- struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port;
+ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial);
struct digi_port *oob_priv = usb_get_serial_port_data(oob_port);
unsigned char *data = oob_port->write_urb->transfer_buffer;
unsigned long flags;
+ int ret;
dev_dbg(&port->dev,
"digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n",
@@ -573,14 +561,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port,
* is only called from close, and only one process can be in close on a
* port at a time, so its ok.
*/
-
static int digi_transmit_idle(struct usb_serial_port *port,
unsigned long timeout)
{
- int ret;
- unsigned char buf[2];
struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned char buf[2];
unsigned long flags;
+ int ret;
spin_lock_irqsave(&priv->dp_port_lock, flags);
priv->dp_transmit_idle = 0;
@@ -607,16 +594,15 @@ static int digi_transmit_idle(struct usb_serial_port *port,
}
priv->dp_transmit_idle = 0;
spin_unlock_irqrestore(&priv->dp_port_lock, flags);
- return 0;
+ return 0;
}
-
static void digi_rx_throttle(struct tty_struct *tty)
{
- unsigned long flags;
struct usb_serial_port *port = tty->driver_data;
struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
/* stop receiving characters by not resubmitting the read urb */
spin_lock_irqsave(&priv->dp_port_lock, flags);
@@ -625,13 +611,12 @@ static void digi_rx_throttle(struct tty_struct *tty)
spin_unlock_irqrestore(&priv->dp_port_lock, flags);
}
-
static void digi_rx_unthrottle(struct tty_struct *tty)
{
- int ret = 0;
- unsigned long flags;
struct usb_serial_port *port = tty->driver_data;
struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+ int ret = 0;
spin_lock_irqsave(&priv->dp_port_lock, flags);
@@ -651,7 +636,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty)
__func__, ret, priv->dp_port_num);
}
-
static void digi_set_termios(struct tty_struct *tty,
struct usb_serial_port *port,
const struct ktermios *old_termios)
@@ -762,7 +746,6 @@ static void digi_set_termios(struct tty_struct *tty,
/* set stop bits */
if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
-
if ((cflag & CSTOPB))
arg = DIGI_STOP_BITS_2;
else
@@ -772,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty,
buf[i++] = priv->dp_port_num;
buf[i++] = arg;
buf[i++] = 0;
-
}
/* set input flow control */
@@ -841,7 +823,6 @@ static void digi_set_termios(struct tty_struct *tty,
tty_encode_baud_rate(tty, baud, baud);
}
-
static int digi_break_ctl(struct tty_struct *tty, int break_state)
{
struct usb_serial_port *port = tty->driver_data;
@@ -855,50 +836,48 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state)
return digi_write_inb_command(port, buf, 4, 0);
}
-
static int digi_tiocmget(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
struct digi_port *priv = usb_get_serial_port_data(port);
- unsigned int val;
unsigned long flags;
+ unsigned int val;
spin_lock_irqsave(&priv->dp_port_lock, flags);
val = priv->dp_modem_signals;
spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
return val;
}
-
static int digi_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear)
{
struct usb_serial_port *port = tty->driver_data;
struct digi_port *priv = usb_get_serial_port_data(port);
- unsigned int val;
unsigned long flags;
+ unsigned int val;
spin_lock_irqsave(&priv->dp_port_lock, flags);
val = (priv->dp_modem_signals & ~clear) | set;
spin_unlock_irqrestore(&priv->dp_port_lock, flags);
+
return digi_set_modem_signals(port, val, 1);
}
-
static int digi_write(struct tty_struct *tty, struct usb_serial_port *port,
const unsigned char *buf, int count)
{
-
- int ret, data_len, new_len;
struct digi_port *priv = usb_get_serial_port_data(port);
unsigned char *data = port->write_urb->transfer_buffer;
+ int ret, data_len, new_len;
unsigned long flags;
dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n",
priv->dp_port_num, count);
/* copy user data (which can sleep) before getting spin lock */
- count = min(count, port->bulk_out_size-2);
+ count = min(count, port->bulk_out_size - 2);
count = min(64, count);
/* be sure only one write proceeds at a time */
@@ -920,7 +899,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port,
/* allow space for any buffered data and for new data, up to */
/* transfer buffer size - 2 (for command and length bytes) */
- new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len);
+ new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len);
data_len = new_len + priv->dp_out_buf_len;
if (data_len == 0) {
@@ -928,7 +907,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port,
return 0;
}
- port->write_urb->transfer_buffer_length = data_len+2;
+ port->write_urb->transfer_buffer_length = data_len + 2;
*data++ = DIGI_CMD_SEND_DATA;
*data++ = data_len;
@@ -954,39 +933,22 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port,
"%s: usb_submit_urb failed, ret=%d, port=%d\n",
__func__, ret, priv->dp_port_num);
dev_dbg(&port->dev, "digi_write: returning %d\n", ret);
- return ret;
+ return ret;
}
static void digi_write_bulk_callback(struct urb *urb)
{
-
struct usb_serial_port *port = urb->context;
- struct usb_serial *serial;
- struct digi_port *priv;
- struct digi_serial *serial_priv;
+ struct digi_serial *serial_priv = usb_get_serial_data(port->serial);
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned char *data = urb->transfer_buffer;
unsigned long flags;
- int ret = 0;
- int status = urb->status;
bool wakeup;
-
- /* port and serial sanity check */
- if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) {
- pr_err("%s: port or port->private is NULL, status=%d\n",
- __func__, status);
- return;
- }
- serial = port->serial;
- if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) {
- dev_err(&port->dev,
- "%s: serial or serial->private is NULL, status=%d\n",
- __func__, status);
- return;
- }
+ int ret = 0;
/* handle oob callback */
if (priv->dp_port_num == serial_priv->ds_oob_port_num) {
- dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n");
spin_lock_irqsave(&priv->dp_port_lock, flags);
priv->dp_write_urb_in_use = 0;
wake_up_interruptible(&priv->write_wait);
@@ -999,15 +961,13 @@ static void digi_write_bulk_callback(struct urb *urb)
spin_lock_irqsave(&priv->dp_port_lock, flags);
priv->dp_write_urb_in_use = 0;
if (priv->dp_out_buf_len > 0) {
- *((unsigned char *)(port->write_urb->transfer_buffer))
- = (unsigned char)DIGI_CMD_SEND_DATA;
- *((unsigned char *)(port->write_urb->transfer_buffer) + 1)
- = (unsigned char)priv->dp_out_buf_len;
- port->write_urb->transfer_buffer_length =
- priv->dp_out_buf_len + 2;
- memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf,
- priv->dp_out_buf_len);
- ret = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ data[0] = DIGI_CMD_SEND_DATA;
+ data[1] = priv->dp_out_buf_len;
+ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len);
+
+ urb->transfer_buffer_length = priv->dp_out_buf_len + 2;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret == 0) {
priv->dp_write_urb_in_use = 1;
priv->dp_out_buf_len = 0;
@@ -1041,8 +1001,8 @@ static unsigned int digi_write_room(struct tty_struct *tty)
spin_unlock_irqrestore(&priv->dp_port_lock, flags);
dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room);
- return room;
+ return room;
}
static unsigned int digi_chars_in_buffer(struct tty_struct *tty)
@@ -1070,17 +1030,53 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on)
digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1);
}
+static int digi_open_oob_port(struct usb_serial *serial)
+{
+ struct digi_serial *serial_priv = usb_get_serial_data(serial);
+ struct usb_serial_port *oob_port = serial_priv->ds_oob_port;
+ int ret = 0;
+
+ mutex_lock(&serial_priv->open_mutex);
+
+ if (serial_priv->open_count++ == 0) {
+ ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n",
+ ret);
+ serial_priv->open_count--;
+ }
+ }
+
+ mutex_unlock(&serial_priv->open_mutex);
+
+ return ret;
+}
+
+static void digi_close_oob_port(struct usb_serial *serial)
+{
+ struct digi_serial *serial_priv = usb_get_serial_data(serial);
+ struct usb_serial_port *oob_port = serial_priv->ds_oob_port;
+
+ mutex_lock(&serial_priv->open_mutex);
+
+ if (serial_priv->open_count-- == 1) {
+ usb_kill_urb(oob_port->read_urb);
+ usb_kill_urb(oob_port->write_urb);
+ }
+
+ mutex_unlock(&serial_priv->open_mutex);
+}
+
static int digi_open(struct tty_struct *tty, struct usb_serial_port *port)
{
- int ret;
- unsigned char buf[32];
struct digi_port *priv = usb_get_serial_port_data(port);
struct ktermios not_termios;
- int throttled;
+ unsigned char buf[32];
+ int ret;
- /* be sure the device is started up */
- if (digi_startup_device(port->serial) != 0)
- return -ENXIO;
+ ret = digi_open_oob_port(port->serial);
+ if (ret)
+ return ret;
/* read modem signals automatically whenever they change */
buf[0] = DIGI_CMD_READ_INPUT_SIGNALS;
@@ -1106,32 +1102,34 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port)
}
spin_lock_irq(&priv->dp_port_lock);
- throttled = priv->dp_throttle_restart;
priv->dp_throttled = 0;
priv->dp_throttle_restart = 0;
spin_unlock_irq(&priv->dp_port_lock);
- if (throttled) {
- ret = usb_submit_urb(port->read_urb, GFP_KERNEL);
- if (ret) {
- dev_err(&port->dev, "failed to submit read urb: %d\n", ret);
- return ret;
- }
+ ret = usb_submit_urb(port->read_urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&port->dev, "failed to submit read urb: %d\n", ret);
+ goto err_close_oob;
}
return 0;
-}
+err_close_oob:
+ digi_close_oob_port(port->serial);
+
+ return ret;
+}
static void digi_close(struct usb_serial_port *port)
{
+ struct digi_port *priv = usb_get_serial_port_data(port);
+ unsigned char buf[32];
DEFINE_WAIT(wait);
int ret;
- unsigned char buf[32];
- struct digi_port *priv = usb_get_serial_port_data(port);
+
+ usb_kill_urb(port->read_urb);
mutex_lock(&port->serial->disc_mutex);
- /* if disconnected, just clear flags */
if (port->serial->disconnected)
goto exit;
@@ -1177,57 +1175,18 @@ static void digi_close(struct usb_serial_port *port)
TASK_INTERRUPTIBLE);
schedule_timeout(DIGI_CLOSE_TIMEOUT);
finish_wait(&priv->dp_flush_wait, &wait);
-
- /* shutdown any outstanding bulk writes */
- usb_kill_urb(port->write_urb);
exit:
- spin_lock_irq(&priv->dp_port_lock);
- priv->dp_write_urb_in_use = 0;
- wake_up_interruptible(&priv->dp_close_wait);
- spin_unlock_irq(&priv->dp_port_lock);
mutex_unlock(&port->serial->disc_mutex);
-}
-
-
-/*
- * Digi Startup Device
- *
- * Starts reads on all ports. Must be called AFTER startup, with
- * urbs initialized. Returns 0 if successful, non-zero error otherwise.
- */
-static int digi_startup_device(struct usb_serial *serial)
-{
- int i, ret = 0;
- struct digi_serial *serial_priv = usb_get_serial_data(serial);
- struct usb_serial_port *port;
+ /* shutdown any outstanding bulk writes */
+ usb_kill_urb(port->write_urb);
- /* be sure this happens exactly once */
- spin_lock(&serial_priv->ds_serial_lock);
- if (serial_priv->ds_device_started) {
- spin_unlock(&serial_priv->ds_serial_lock);
- return 0;
- }
- serial_priv->ds_device_started = 1;
- spin_unlock(&serial_priv->ds_serial_lock);
-
- /* start reading from each bulk in endpoint for the device */
- /* set USB_DISABLE_SPD flag for write bulk urbs */
- for (i = 0; i < serial->type->num_ports + 1; i++) {
- port = serial->port[i];
- ret = usb_submit_urb(port->read_urb, GFP_KERNEL);
- if (ret != 0) {
- dev_err(&port->dev,
- "%s: usb_submit_urb failed, ret=%d, port=%d\n",
- __func__, ret, i);
- break;
- }
- }
- return ret;
+ digi_close_oob_port(port->serial);
}
static int digi_port_init(struct usb_serial_port *port, unsigned port_num)
{
+ struct digi_serial *serial_priv = usb_get_serial_data(port->serial);
struct digi_port *priv;
priv = kzalloc_obj(*priv);
@@ -1235,10 +1194,13 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num)
return -ENOMEM;
spin_lock_init(&priv->dp_port_lock);
+
+ if (port == serial_priv->ds_oob_port)
+ lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING);
+
priv->dp_port_num = port_num;
init_waitqueue_head(&priv->dp_transmit_idle_wait);
init_waitqueue_head(&priv->dp_flush_wait);
- init_waitqueue_head(&priv->dp_close_wait);
init_waitqueue_head(&priv->write_wait);
priv->dp_port = port;
@@ -1275,10 +1237,13 @@ static int digi_startup(struct usb_serial *serial)
if (!serial_priv)
return -ENOMEM;
- spin_lock_init(&serial_priv->ds_serial_lock);
+ mutex_init(&serial_priv->open_mutex);
+
serial_priv->ds_oob_port_num = oob_port_num;
serial_priv->ds_oob_port = serial->port[oob_port_num];
+ usb_set_serial_data(serial, serial_priv);
+
ret = digi_port_init(serial_priv->ds_oob_port,
serial_priv->ds_oob_port_num);
if (ret) {
@@ -1286,24 +1251,9 @@ static int digi_startup(struct usb_serial *serial)
return ret;
}
- usb_set_serial_data(serial, serial_priv);
-
return 0;
}
-
-static void digi_disconnect(struct usb_serial *serial)
-{
- int i;
-
- /* stop reads and writes on all ports */
- for (i = 0; i < serial->type->num_ports + 1; i++) {
- usb_kill_urb(serial->port[i]->read_urb);
- usb_kill_urb(serial->port[i]->write_urb);
- }
-}
-
-
static void digi_release(struct usb_serial *serial)
{
struct digi_serial *serial_priv;
@@ -1333,30 +1283,25 @@ static void digi_port_remove(struct usb_serial_port *port)
static void digi_read_bulk_callback(struct urb *urb)
{
struct usb_serial_port *port = urb->context;
- struct digi_port *priv;
- struct digi_serial *serial_priv;
- int ret;
+ struct usb_serial *serial = port->serial;
+ struct digi_serial *serial_priv = usb_get_serial_data(serial);
+ struct digi_port *priv = usb_get_serial_port_data(port);
int status = urb->status;
-
- /* port sanity check, do not resubmit if port is not valid */
- if (port == NULL)
- return;
- priv = usb_get_serial_port_data(port);
- if (priv == NULL) {
- dev_err(&port->dev, "%s: port->private is NULL, status=%d\n",
- __func__, status);
- return;
- }
- if (port->serial == NULL ||
- (serial_priv = usb_get_serial_data(port->serial)) == NULL) {
- dev_err(&port->dev, "%s: serial is bad or serial->private "
- "is NULL, status=%d\n", __func__, status);
- return;
- }
+ int ret;
/* do not resubmit urb if it has any status error */
- if (status) {
- dev_err(&port->dev,
+ switch (status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ dev_err(&serial->interface->dev,
+ "%s: nonzero read bulk status: status=%d, port=%d\n",
+ __func__, status, priv->dp_port_num);
+ return;
+ default:
+ dev_err(&serial->interface->dev,
"%s: nonzero read bulk status: status=%d, port=%d\n",
__func__, status, priv->dp_port_num);
return;
@@ -1374,11 +1319,10 @@ static void digi_read_bulk_callback(struct urb *urb)
/* continue read */
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret != 0 && ret != -EPERM) {
- dev_err(&port->dev,
+ dev_err(&serial->interface->dev,
"%s: failed resubmitting urb, ret=%d, port=%d\n",
__func__, ret, priv->dp_port_num);
}
-
}
/*
@@ -1390,7 +1334,6 @@ static void digi_read_bulk_callback(struct urb *urb)
* It returns 0 if successful, 1 if successful but the port is
* throttled, and -1 if the sanity checks failed.
*/
-
static int digi_read_inb_callback(struct urb *urb)
{
struct usb_serial_port *port = urb->context;
@@ -1468,10 +1411,8 @@ static int digi_read_inb_callback(struct urb *urb)
dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode);
return throttled ? 1 : 0;
-
}
-
/*
* Digi Read OOB Callback
*
@@ -1480,10 +1421,8 @@ static int digi_read_inb_callback(struct urb *urb)
* the port->serial is valid. It returns 0 if successful, and
* -1 if the sanity checks failed.
*/
-
static int digi_read_oob_callback(struct urb *urb)
{
-
struct usb_serial_port *port = urb->context;
struct usb_serial *serial = port->serial;
struct tty_struct *tty;
@@ -1491,8 +1430,8 @@ static int digi_read_oob_callback(struct urb *urb)
unsigned char *buf = urb->transfer_buffer;
int opcode, line, status, val;
unsigned long flags;
- int i;
unsigned int rts;
+ int i;
if (urb->actual_length < 4)
return -1;
@@ -1504,7 +1443,8 @@ static int digi_read_oob_callback(struct urb *urb)
status = buf[i + 2];
val = buf[i + 3];
- dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n",
+ dev_dbg(&serial->interface->dev,
+ "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n",
opcode, line, status, val);
if (status != 0 || line >= serial->type->num_ports)
@@ -1562,8 +1502,8 @@ static int digi_read_oob_callback(struct urb *urb)
}
tty_kref_put(tty);
}
- return 0;
+ return 0;
}
module_usb_serial_driver(serial_drivers, id_table_combined);
diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c
index dd4cfd17f7ad..4ee0bdf98c0e 100644
--- a/drivers/usb/serial/keyspan_pda.c
+++ b/drivers/usb/serial/keyspan_pda.c
@@ -31,7 +31,6 @@
struct keyspan_pda_private {
int tx_room;
struct work_struct unthrottle_work;
- struct usb_serial *serial;
struct usb_serial_port *port;
bool throttled;
bool throttle_req;
diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c
index 35473544f1c8..22c5f071f116 100644
--- a/drivers/usb/serial/metro-usb.c
+++ b/drivers/usb/serial/metro-usb.c
@@ -36,6 +36,7 @@
struct metrousb_private {
spinlock_t lock;
int throttled;
+ int throttle_req;
unsigned long control_state;
};
@@ -108,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb)
unsigned char *data = urb->transfer_buffer;
unsigned long flags;
int throttled = 0;
- int result = 0;
+ int result;
dev_dbg(&port->dev, "%s\n", __func__);
@@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb)
/* Set any port variables. */
spin_lock_irqsave(&metro_priv->lock, flags);
- throttled = metro_priv->throttled;
+ if (metro_priv->throttle_req) {
+ metro_priv->throttled = 1;
+ throttled = 1;
+ }
spin_unlock_irqrestore(&metro_priv->lock, flags);
if (throttled)
@@ -169,12 +173,13 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port)
struct usb_serial *serial = port->serial;
struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
unsigned long flags;
- int result = 0;
+ int result;
/* Set the private data information for the port. */
spin_lock_irqsave(&metro_priv->lock, flags);
metro_priv->control_state = 0;
metro_priv->throttled = 0;
+ metro_priv->throttle_req = 0;
spin_unlock_irqrestore(&metro_priv->lock, flags);
/* Clear the urb pipe. */
@@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty)
/* Set the private information for the port to stop reading data. */
spin_lock_irqsave(&metro_priv->lock, flags);
- metro_priv->throttled = 1;
+ metro_priv->throttle_req = 1;
spin_unlock_irqrestore(&metro_priv->lock, flags);
}
@@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty)
struct usb_serial_port *port = tty->driver_data;
struct metrousb_private *metro_priv = usb_get_serial_port_data(port);
unsigned long flags;
- int result = 0;
+ int throttled;
+ int result;
/* Set the private information for the port to resume reading data. */
spin_lock_irqsave(&metro_priv->lock, flags);
+ throttled = metro_priv->throttled;
metro_priv->throttled = 0;
+ metro_priv->throttle_req = 0;
spin_unlock_irqrestore(&metro_priv->lock, flags);
- /* Submit the urb to read from the port. */
- result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
- if (result)
- dev_err(&port->dev,
- "failed submitting interrupt in urb error code=%d\n",
- result);
+ if (throttled) {
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+ if (result) {
+ dev_err(&port->dev, "failed to submit interrupt in urb: %d\n",
+ result);
+ }
+ }
}
static struct usb_serial_driver metrousb_device = {
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 580f06f5ce5e..e4ad14375d6a 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -2689,12 +2689,26 @@ static void option_instat_callback(struct urb *urb)
dev_dbg(dev, "%s: NULL req_pkt\n", __func__);
return;
}
+
+ if (urb->actual_length < sizeof(*req_pkt)) {
+ dev_err(dev, "%s: short packet: %u bytes\n", __func__,
+ urb->actual_length);
+ return;
+ }
+
if ((req_pkt->bRequestType == 0xA1) &&
(req_pkt->bRequest == 0x20)) {
+ unsigned char signals;
int old_dcd_state;
- unsigned char signals = *((unsigned char *)
- urb->transfer_buffer +
- sizeof(struct usb_ctrlrequest));
+
+ if (urb->actual_length < sizeof(*req_pkt) + 1) {
+ dev_err(dev, "%s: short interrupt transfer: %u bytes\n",
+ __func__, urb->actual_length);
+ return;
+ }
+
+ signals = *((unsigned char *)urb->transfer_buffer +
+ sizeof(*req_pkt));
dev_dbg(dev, "%s: signal x%x\n", __func__, signals);
diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c
index 0bcbdcea52af..bf42a545c20f 100644
--- a/drivers/usb/serial/pl2303.c
+++ b/drivers/usb/serial/pl2303.c
@@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = {
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) },
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) },
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) },
+ { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) },
{ USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) },
{ USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) },
{ USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID),
@@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table);
#define VENDOR_WRITE_REQUEST_TYPE 0x40
#define VENDOR_WRITE_REQUEST 0x01
#define VENDOR_WRITE_NREQUEST 0x80
+#define VENDOR_WRITE_MPREQUEST 0x80
#define VENDOR_READ_REQUEST_TYPE 0xc0
#define VENDOR_READ_REQUEST 0x01
#define VENDOR_READ_NREQUEST 0x81
+#define VENDOR_READ_MPREQUEST 0x80
+
+#define PL256X_RESET_REQUEST_TYPE 0x40
+#define PL256X_RESET_REQUEST 0x96
#define UART_STATE_INDEX 8
#define UART_STATE_MSR_MASK 0x8b
@@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table);
#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18
#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c
+#define PL256X_PORT_A_FLOWCTRL_REG 0xc005
+#define PL256X_PORT_B_FLOWCTRL_REG 0xd005
+#define PL256X_PORT_C_FLOWCTRL_REG 0xe005
+#define PL256X_PORT_D_FLOWCTRL_REG 0xf005
+
+#define PL256X_FLOWCTRL_MASK 0x43
+#define PL256X_FLOWCTRL_XON_XOFF 0x40
+#define PL256X_FLOWCTRL_RTS_CTS 0x03
+#define PL256X_FLOWCTRL_NONE 0x00
+
static int pl2303_set_break(struct usb_serial_port *port, bool enable);
enum pl2303_type {
@@ -181,6 +197,7 @@ enum pl2303_type {
TYPE_TB,
TYPE_HXD,
TYPE_HXN,
+ TYPE_MP,
TYPE_COUNT
};
@@ -196,6 +213,8 @@ struct pl2303_type_data {
struct pl2303_serial_private {
const struct pl2303_type_data *type;
unsigned long quirks;
+ u16 interface_num;
+ u16 flowctrl_reg;
};
struct pl2303_private {
@@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
.max_baud_rate = 12000000,
.no_divisors = true,
},
+ [TYPE_MP] = {
+ .name = "MP",
+ .max_baud_rate = 24000000,
+ .no_divisors = true,
+ },
};
+static int pl256x_uart_reset(struct usb_serial *serial)
+{
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+ struct device *dev = &serial->interface->dev;
+ int res;
+
+ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+ PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE,
+ 0, spriv->interface_num, NULL, 0, 100);
+ if (res) {
+ dev_err(dev, "failed to reset device: %d\n", res);
+ return res;
+ }
+
+ return 0;
+}
+
static int pl2303_vendor_read(struct usb_serial *serial, u16 value,
unsigned char buf[1])
{
@@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value,
if (spriv->type == &pl2303_type_data[TYPE_HXN])
request = VENDOR_READ_NREQUEST;
+ else if (spriv->type == &pl2303_type_data[TYPE_MP])
+ request = VENDOR_READ_MPREQUEST;
else
request = VENDOR_READ_REQUEST;
@@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index)
if (spriv->type == &pl2303_type_data[TYPE_HXN])
request = VENDOR_WRITE_NREQUEST;
+ else if (spriv->type == &pl2303_type_data[TYPE_MP])
+ request = VENDOR_WRITE_MPREQUEST;
else
request = VENDOR_WRITE_REQUEST;
@@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val)
if (!buf)
return -ENOMEM;
- if (spriv->type == &pl2303_type_data[TYPE_HXN])
+ if (spriv->type == &pl2303_type_data[TYPE_HXN] ||
+ spriv->type == &pl2303_type_data[TYPE_MP]) {
ret = pl2303_vendor_read(serial, reg, buf);
- else
+ } else {
ret = pl2303_vendor_read(serial, reg | 0x80, buf);
+ }
if (ret)
goto out_free;
@@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial)
case 0x905: /* GT-2AB */
case 0x1005: /* GC-Q20 */
return TYPE_HXN;
+ case 0x3302: /* PL2533 VC 2 Port */
+ case 0x3304: /* PL2533 VC 4 Port */
+ case 0x4302: /* PL2543 VC 2 Port */
+ case 0x4304: /* PL2543 VC 4 Port */
+ case 0x6502: /* PL2565 VC 2 Port */
+ case 0x6504: /* PL2565 VC 4 Port */
+ case 0x6506: /* PL2565 VC 4 Port QFN64 package */
+ return TYPE_MP;
}
break;
}
@@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial)
struct pl2303_serial_private *spriv;
enum pl2303_type type;
unsigned char *buf;
+ unsigned int ifnum;
int ret;
ret = pl2303_detect_type(serial);
@@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial)
type = ret;
dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name);
+ ifnum = serial->interface->altsetting->desc.bInterfaceNumber;
+
spriv = kzalloc_obj(*spriv);
if (!spriv)
return -ENOMEM;
+ if (type == TYPE_MP) {
+ switch (ifnum) {
+ case 0:
+ spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG;
+ break;
+ case 1:
+ spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG;
+ break;
+ case 2:
+ spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG;
+ break;
+ case 3:
+ spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG;
+ break;
+ default:
+ kfree(spriv);
+ return -ENODEV;
+ }
+ }
+
spriv->type = &pl2303_type_data[type];
spriv->quirks = (unsigned long)usb_get_serial_data(serial);
spriv->quirks |= spriv->type->quirks;
+ spriv->interface_num = ifnum;
if (type == TYPE_HXD && pl2303_is_hxd_clone(serial))
spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE;
usb_set_serial_data(serial, spriv);
- if (type != TYPE_HXN) {
+ if (type != TYPE_HXN && type != TYPE_MP) {
buf = kmalloc(1, GFP_KERNEL);
if (!buf) {
kfree(spriv);
@@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port)
static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value)
{
struct usb_device *dev = port->serial->dev;
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
int retval;
dev_dbg(&port->dev, "%s - %02x\n", __func__, value);
retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE,
- value, 0, NULL, 0, 100);
+ value, spriv->interface_num, NULL, 0, 100);
if (retval)
dev_err(&port->dev, "%s - failed: %d\n", __func__, retval);
@@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port,
ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE,
- 0, 0, buf, 7, 100);
+ 0, spriv->interface_num, buf, 7, 100);
if (ret != 7) {
dev_err(&port->dev, "%s - failed: %d\n", __func__, ret);
@@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port,
unsigned char buf[7])
{
struct usb_device *udev = port->serial->dev;
+ struct usb_serial *serial = port->serial;
+ struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
int ret;
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE,
- 0, 0, buf, 7, 100);
+ 0, spriv->interface_num, buf, 7, 100);
if (ret < 0) {
dev_err(&port->dev, "%s - failed: %d\n", __func__, ret);
return ret;
@@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty,
pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
PL2303_HXN_FLOWCTRL_MASK,
PL2303_HXN_FLOWCTRL_RTS_CTS);
+ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) {
+ pl2303_vendor_write(serial, spriv->flowctrl_reg,
+ PL256X_FLOWCTRL_RTS_CTS);
} else {
pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60);
}
@@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty,
pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
PL2303_HXN_FLOWCTRL_MASK,
PL2303_HXN_FLOWCTRL_XON_XOFF);
+ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) {
+ pl2303_vendor_write(serial, spriv->flowctrl_reg,
+ PL256X_FLOWCTRL_XON_XOFF);
} else {
pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0);
}
@@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty,
pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
PL2303_HXN_FLOWCTRL_MASK,
PL2303_HXN_FLOWCTRL_NONE);
+ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) {
+ pl2303_vendor_write(serial, spriv->flowctrl_reg,
+ PL256X_FLOWCTRL_NONE);
} else {
pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0);
}
@@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port)
pl2303_vendor_write(serial, PL2303_HXN_RESET_REG,
PL2303_HXN_RESET_UPSTREAM_PIPE |
PL2303_HXN_RESET_DOWNSTREAM_PIPE);
+ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) {
+ pl256x_uart_reset(serial);
} else {
pl2303_vendor_write(serial, 8, 0);
pl2303_vendor_write(serial, 9, 0);
@@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable)
result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
BREAK_REQUEST, BREAK_REQUEST_TYPE, state,
- 0, NULL, 0, 100);
+ spriv->interface_num, NULL, 0, 100);
if (result) {
dev_err(&port->dev, "error sending break = %d\n", result);
return result;
diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h
index d60eda7f6eda..8eb1e7b5d3ec 100644
--- a/drivers/usb/serial/pl2303.h
+++ b/drivers/usb/serial/pl2303.h
@@ -26,7 +26,7 @@
#define PL2303_PRODUCT_ID_HCR331 0x331a
#define PL2303_PRODUCT_ID_MOTOROLA 0x0307
#define PL2303_PRODUCT_ID_ZTEK 0xe1f1
-
+#define PL256X_PRODUCT_ID_4P 0x2533
#define ATEN_VENDOR_ID 0x0557
#define ATEN_VENDOR_ID2 0x0547
diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c
index c11d64bf08fb..3ef7ca2c0cf4 100644
--- a/drivers/usb/serial/spcp8x5.c
+++ b/drivers/usb/serial/spcp8x5.c
@@ -133,14 +133,14 @@ struct spcp8x5_private {
static int spcp8x5_probe(struct usb_serial *serial,
const struct usb_device_id *id)
{
- usb_set_serial_data(serial, (void *)id);
+ usb_set_serial_data(serial, (void *)id->driver_info);
return 0;
}
static int spcp8x5_port_probe(struct usb_serial_port *port)
{
- const struct usb_device_id *id = usb_get_serial_data(port->serial);
+ unsigned int quirks = (unsigned int)(unsigned long)usb_get_serial_data(port->serial);
struct spcp8x5_private *priv;
priv = kzalloc_obj(*priv);
@@ -148,7 +148,7 @@ static int spcp8x5_port_probe(struct usb_serial_port *port)
return -ENOMEM;
spin_lock_init(&priv->lock);
- priv->quirks = id->driver_info;
+ priv->quirks = quirks;
usb_set_serial_port_data(port, priv);
@@ -235,18 +235,6 @@ static void spcp8x5_set_work_mode(struct usb_serial_port *port, u16 value,
dev_err(&port->dev, "failed to set work mode: %d\n", ret);
}
-static int spcp8x5_carrier_raised(struct usb_serial_port *port)
-{
- u8 msr;
- int ret;
-
- ret = spcp8x5_get_msr(port, &msr);
- if (ret || msr & MSR_STATUS_LINE_DCD)
- return 1;
-
- return 0;
-}
-
static void spcp8x5_dtr_rts(struct usb_serial_port *port, int on)
{
struct spcp8x5_private *priv = usb_get_serial_port_data(port);
@@ -458,7 +446,6 @@ static struct usb_serial_driver spcp8x5_device = {
.num_bulk_out = 1,
.open = spcp8x5_open,
.dtr_rts = spcp8x5_dtr_rts,
- .carrier_raised = spcp8x5_carrier_raised,
.set_termios = spcp8x5_set_termios,
.init_termios = spcp8x5_init_termios,
.tiocmget = spcp8x5_tiocmget,
diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
index 7867fa7c405d..b054c0b6a8d4 100644
--- a/drivers/usb/typec/altmodes/Kconfig
+++ b/drivers/usb/typec/altmodes/Kconfig
@@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers"
config TYPEC_DP_ALTMODE
tristate "DisplayPort Alternate Mode driver"
depends on DRM
+ select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF
help
DisplayPort USB Type-C Alternate Mode allows DisplayPort
displays and adapters to be attached to the USB Type-C
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index 263a89c5f324..51c92dd887ab 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt)
struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P);
struct fwnode_handle *fwnode;
struct dp_altmode *dp;
+ u32 cap = DP_CAP_CAPABILITY(alt->vdo);
/* Port can only be DFP_U. */
if (typec_altmode_get_data_role(alt) != TYPEC_HOST)
@@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt)
return -ENODEV;
}
+ /*
+ * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section
+ * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a
+ * USB-C receptacle must advertise at least one pin assignment for the
+ * capability it claims, otherwise Alt Mode can never be configured.
+ */
+ if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) ||
+ (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) {
+ typec_altmode_put_plug(plug);
+ return -ENODEV;
+ }
+
dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
if (!dp) {
typec_altmode_put_plug(plug);
@@ -790,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt)
dp->alt = alt;
alt->desc = "DisplayPort";
- typec_altmode_set_ops(alt, &dp_altmode_ops);
if (plug) {
plug->desc = "Displayport";
@@ -811,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt)
if (plug)
typec_altmode_set_drvdata(plug, dp);
+ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt))
+ return 0;
+
+ typec_altmode_set_ops(alt, &dp_altmode_ops);
if (!alt->mode_selection) {
dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER;
schedule_work(&dp->work);
diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
index 32250b94262a..2e1b1a1da7d9 100644
--- a/drivers/usb/typec/altmodes/thunderbolt.c
+++ b/drivers/usb/typec/altmodes/thunderbolt.c
@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt)
alt->desc = "Thunderbolt3";
typec_altmode_set_drvdata(alt, tbt);
+
+ if (typec_cable_altmode_unsupported(alt))
+ return 0;
+
typec_altmode_set_ops(alt, &tbt_altmode_ops);
if (!alt->mode_selection && tbt_ready(alt)) {
@@ -303,6 +307,8 @@ static void tbt_altmode_remove(struct typec_altmode *alt)
{
struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+ disable_work_sync(&tbt->work);
+
for (int i = TYPEC_PLUG_SOP_PP; i >= 0; --i) {
if (tbt->plug[i])
typec_altmode_put_plug(tbt->plug[i]);
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 0595e8cb83aa..54c7810b563b 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable)
}
EXPORT_SYMBOL_GPL(typec_cable_is_active);
+enum typec_cable_altmode_support {
+ CABLE_SUPPORT_UNKNOWN,
+ CABLE_SUPPORTED,
+ CABLE_NOT_SUPPORTED,
+};
+
+static enum typec_cable_altmode_support
+typec_cable_check_altmode_support(struct typec_cable *cable,
+ struct typec_altmode *alt)
+{
+ struct typec_altmode *plug;
+ u32 speed;
+
+ /*
+ * Check if the cable has an e-marker, supports modal operation, and the
+ * SOP' altmode nodes are created.
+ */
+ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P);
+ if (plug) {
+ typec_altmode_put_plug(plug);
+ return CABLE_SUPPORTED;
+ }
+
+ /* The identity is not specified */
+ if (!cable->identity)
+ return CABLE_SUPPORT_UNKNOWN;
+
+ /* Non-e-marked cable */
+ if (!cable->identity->id_header)
+ return CABLE_NOT_SUPPORTED;
+
+ switch (PD_IDH_PTYPE(cable->identity->id_header)) {
+ case IDH_PTYPE_PCABLE:
+ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]);
+ if (speed == CABLE_USB2_ONLY)
+ return CABLE_NOT_SUPPORTED;
+ return CABLE_SUPPORTED;
+ case IDH_PTYPE_ACABLE:
+ /*
+ * Active cables must establish an SOP' communication
+ * node. Since that check failed at the beginning of
+ * this function, this active cable does not support
+ * this specific altmode.
+ */
+ return CABLE_NOT_SUPPORTED;
+ }
+
+ return CABLE_SUPPORT_UNKNOWN;
+}
+
+/**
+ * typec_cable_altmode_unsupported - Check if a cable restricts altmode
+ * @alt: The Alternate Mode to evaluate
+ *
+ * Returns true if the connected cable is incapable of handling the altmode.
+ */
+bool typec_cable_altmode_unsupported(struct typec_altmode *alt)
+{
+ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN;
+ struct typec_cable *cable;
+
+ cable = typec_cable_get(typec_altmode2port(alt));
+ if (cable) {
+ support = typec_cable_check_altmode_support(cable, alt);
+ typec_cable_put(cable);
+ }
+
+ return support == CABLE_NOT_SUPPORTED;
+}
+EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported);
+
/**
* typec_cable_set_identity - Report result from Discover Identity command
* @cable: The cable updated identity values
diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c
index 3e39b800e6b5..d0de5a2488f9 100644
--- a/drivers/usb/typec/hd3ss3220.c
+++ b/drivers/usb/typec/hd3ss3220.c
@@ -218,7 +218,7 @@ static void hd3ss3220_regulator_control(struct hd3ss3220 *hd3ss3220, bool on)
if (ret)
dev_err(hd3ss3220->dev,
- "vbus regulator %s failed: %d\n", on ? "disable" : "enable", ret);
+ "vbus regulator %s failed: %d\n", on ? "enable" : "disable", ret);
}
static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220)
diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c
index d6e1289a4945..34ef81f75693 100644
--- a/drivers/usb/typec/mux/tusb1046.c
+++ b/drivers/usb/typec/mux/tusb1046.c
@@ -178,6 +178,7 @@ static const struct of_device_id tusb1046_match_table[] = {
{.compatible = "ti,tusb1046"},
{},
};
+MODULE_DEVICE_TABLE(of, tusb1046_match_table);
static struct i2c_driver tusb1046_driver = {
.driver = {
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index 7ac7000b2d13..6717ac914c6a 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -38,6 +38,7 @@ struct tcpci {
struct regmap *regmap;
unsigned int alert_mask;
+ unsigned int rx_type_mask;
bool controls_vbus;
@@ -488,6 +489,8 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
if (tcpci->data->cable_comm_capable)
reg |= TCPC_RX_DETECT_SOP1;
}
+
+ tcpci->rx_type_mask = reg;
ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
if (ret < 0)
return ret;
@@ -749,6 +752,7 @@ process_status:
if (status & TCPC_ALERT_RX_STATUS) {
struct pd_message msg;
unsigned int cnt, payload_cnt;
+ enum tcpm_transmit_type rx_type;
u16 header;
regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt);
@@ -773,10 +777,16 @@ process_status:
regmap_raw_read(tcpci->regmap, TCPC_RX_DATA,
&msg.payload, payload_cnt);
+ ret = regmap_read(tcpci->regmap, TCPC_RX_BUF_FRAME_TYPE, &rx_type);
+ if (ret)
+ return ret;
+
/* Read complete, clear RX status alert bit */
tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
- tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP);
+ rx_type &= TCPC_RX_BUF_FRAME_TYPE_MASK;
+ if (tcpci->rx_type_mask & BIT(rx_type))
+ tcpm_pd_receive(tcpci->port, &msg, rx_type);
}
if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) {
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 89eec20a2064..a8cd1959c426 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -12,6 +12,7 @@
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
+#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -212,6 +213,24 @@ static const char * const tcpm_ams_str[] = {
FOREACH_AMS(GENERATE_STRING)
};
+#define FOREACH_VDM_DISCOVERY(S) \
+ S(VDM_DISCOVERY_UNKNOWN), \
+ S(VDM_DISCOVERY_PARTNER_IDENT), \
+ S(VDM_DISCOVERY_CABLE_IDENT), \
+ S(VDM_DISCOVERY_PARTNER_SVIDS), \
+ S(VDM_DISCOVERY_PARTNER_MODES), \
+ S(VDM_DISCOVERY_CABLE_SVIDS), \
+ S(VDM_DISCOVERY_CABLE_MODES), \
+ S(VDM_DISCOVERY_COMPLETE)
+
+enum vdm_discovery_states {
+ FOREACH_VDM_DISCOVERY(GENERATE_ENUM)
+};
+
+static const char * const vdm_discovery_state_strings[] = {
+ FOREACH_VDM_DISCOVERY(GENERATE_STRING)
+};
+
enum vdm_states {
VDM_STATE_ERR_BUSY = -3,
VDM_STATE_ERR_SEND = -2,
@@ -232,7 +251,9 @@ enum pd_msg_request {
PD_MSG_DATA_SINK_CAP,
PD_MSG_DATA_SOURCE_CAP,
PD_MSG_DATA_REV,
- PD_MSG_EXT_SINK_CAP_EXT
+ PD_MSG_EXT_SINK_CAP_EXT,
+ PD_MSG_DATA_BATT_STATUS,
+ PD_MSG_EXT_BATT_CAP,
};
enum adev_actions {
@@ -274,7 +295,7 @@ enum frs_typec_current {
#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)
#define GET_SINK_CAP_RETRY_MS 100
-#define SEND_DISCOVER_RETRY_MS 100
+#define SEND_DISCOVERY_VDM_RETRY_MS 100
struct pd_mode_data {
int svid_index; /* current SVID index */
@@ -387,7 +408,15 @@ struct pd_timings {
};
/* Convert microwatt to watt */
-#define UW_TO_W(pow) ((pow) / 1000000)
+#define UW_TO_W(pow) (div_u64((pow), 1000000))
+
+/*
+ * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries
+ * that a port can be queried is restricted to 4.
+ */
+#define MAX_NUM_FIXED_BATT 4
+
+#define BATTERY_PROPERTY_UNKNOWN 0xffff
/*
* struct pd_identifier - Contains info about PD identifiers
@@ -483,8 +512,6 @@ struct tcpm_port {
bool vbus_source;
bool vbus_charge;
- /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */
- bool send_discover;
bool op_vsafe5v;
int try_role;
@@ -510,8 +537,8 @@ struct tcpm_port {
struct kthread_work vdm_state_machine;
struct hrtimer enable_frs_timer;
struct kthread_work enable_frs;
- struct hrtimer send_discover_timer;
- struct kthread_work send_discover_work;
+ struct hrtimer vdm_discovery_timer;
+ struct kthread_work vdm_discovery_work;
bool state_machine_running;
/* Set to true when VDM State Machine has following actions. */
bool vdm_sm_running;
@@ -578,6 +605,9 @@ struct tcpm_port {
u32 bist_request;
+ /* VDM Discovery State to determine message sent */
+ enum vdm_discovery_states vdm_discovery_state;
+
/* PD state for Vendor Defined Messages */
enum vdm_states vdm_state;
u32 vdm_retries;
@@ -642,12 +672,6 @@ struct tcpm_port {
/* SOP* Related Fields */
/*
- * Flag to determine if SOP' Discover Identity is available. The flag
- * is set if Discover Identity on SOP' does not immediately follow
- * Discover Identity on SOP.
- */
- bool send_discover_prime;
- /*
* tx_sop_type determines which SOP* a message is being sent on.
* For messages that are queued and not sent immediately such as in
* tcpm_queue_message or messages that send after state changes,
@@ -683,6 +707,9 @@ struct tcpm_port {
struct pd_identifier pd_ident;
struct sink_caps_ext_data sink_caps_ext;
+ struct power_supply **fixed_batt;
+ u32 fixed_batt_cnt;
+ u32 batt_request_id;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct mutex logbuffer_lock; /* log buffer access lock */
@@ -771,6 +798,9 @@ static const char * const pd_rev[] = {
#define tcpm_wait_for_discharge(port) \
(((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0)
+#define tcpm_can_send_vdm(state) \
+ ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST))
+
static enum tcpm_state tcpm_default_state(struct tcpm_port *port)
{
if (port->port_type == TYPEC_PORT_DRP) {
@@ -1470,6 +1500,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
}
+static void tcpm_get_fixed_batt(struct tcpm_port *port)
+{
+ int ret;
+
+ if (!port->self_powered || port->fixed_batt_cnt > 0)
+ return;
+
+ ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt);
+ if (ret < 0)
+ tcpm_log(port, "Failed to get battery array, ret=%d", ret);
+ else
+ port->fixed_batt_cnt = ret;
+}
+
static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port)
{
u16 operating_snk_watt = port->operating_snk_mw / 1000;
@@ -1482,6 +1526,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port)
if (!port->self_powered)
data->spr_op_pdp = operating_snk_watt;
+ tcpm_get_fixed_batt(port);
+
/*
* SPR Sink Minimum PDP indicates the minimum power required to operate
* a sink device in its lowest level of functionality without requiring
@@ -1507,6 +1553,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port)
skedb.load_step = data->load_step;
skedb.load_char = cpu_to_le16(data->load_char);
skedb.compliance = data->compliance;
+ skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT);
skedb.modes = data->modes;
skedb.spr_min_pdp = data->spr_min_pdp;
skedb.spr_op_pdp = data->spr_op_pdp;
@@ -1525,6 +1572,153 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port)
port->message_id,
data_obj_cnt,
1 /* Denotes if ext header */));
+
+ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static u16 tcpm_charge_to_energy(int charge, int voltage)
+{
+ u64 energy = div_u64((u64)charge * voltage, 1000000);
+
+ /* Battery telemetry is reported in increments of 0.1Wh */
+ return (u16)UW_TO_W(energy * 10);
+}
+
+static int tcpm_pd_send_batt_status(struct tcpm_port *port)
+{
+ u16 present_charge = BATTERY_PROPERTY_UNKNOWN;
+ bool batt_present = false, invalid_ref = true;
+ u32 batt_id = port->batt_request_id;
+ union power_supply_propval val;
+ struct power_supply *batt;
+ u8 charging_status = 0;
+ struct pd_message msg;
+ int ret, charge_now;
+ u32 bsdo;
+
+ tcpm_get_fixed_batt(port);
+ memset(&msg, 0, sizeof(msg));
+
+ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT)
+ goto send_status;
+
+ invalid_ref = false;
+ batt = port->fixed_batt[batt_id];
+ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val);
+ if (ret)
+ tcpm_log(port,
+ "Failed to fetch power_supply_prop_present ret %d",
+ ret);
+ else
+ batt_present = val.intval > 0;
+
+ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW,
+ &val);
+ if (!ret) {
+ charge_now = val.intval;
+ ret = power_supply_get_property(batt,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ &val);
+ if (!ret)
+ present_charge = tcpm_charge_to_energy(charge_now,
+ val.intval);
+ }
+
+ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val);
+ if (!ret) {
+ switch (val.intval) {
+ case POWER_SUPPLY_STATUS_CHARGING:
+ charging_status = BSDO_BATTERY_INFO_CHARGING;
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ charging_status = BSDO_BATTERY_INFO_DISCHARGING;
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ case POWER_SUPPLY_STATUS_FULL:
+ charging_status = BSDO_BATTERY_INFO_IDLE;
+ break;
+ default:
+ charging_status = BSDO_BATTERY_INFO_RSVD;
+ break;
+ }
+ }
+
+send_status:
+
+ bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref);
+ msg.payload[0] = cpu_to_le32(bsdo);
+ msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS,
+ port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id,
+ 1);
+
+ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_pd_send_batt_cap(struct tcpm_port *port)
+{
+ u16 design_cap = BATTERY_PROPERTY_UNKNOWN;
+ u16 charge_cap = BATTERY_PROPERTY_UNKNOWN;
+ u32 batt_id = port->batt_request_id;
+ union power_supply_propval val;
+ struct batt_cap_ext_msg bcdb;
+ struct power_supply *batt;
+ bool invalid_ref = true;
+ struct pd_message msg;
+ u8 data_obj_cnt;
+ int ret, vol;
+
+ tcpm_get_fixed_batt(port);
+ memset(&msg, 0, sizeof(msg));
+
+ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT)
+ goto send_cap;
+
+ invalid_ref = false;
+ batt = port->fixed_batt[batt_id];
+ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ &val);
+ if (!ret) {
+ vol = val.intval;
+ ret = power_supply_get_property(batt,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ &val);
+ if (!ret)
+ design_cap = tcpm_charge_to_energy(val.intval, vol);
+
+ ret = power_supply_get_property(batt,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ &val);
+ if (!ret)
+ charge_cap = tcpm_charge_to_energy(val.intval, vol);
+ }
+
+send_cap:
+
+ /*
+ * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the
+ * USB-IF) does not exist or an invalid battery reference is made by the
+ * requestor, then set the VID field to 0xffff. If the VID field is
+ * 0xffff, set the PID field to 0.
+ */
+ bcdb.vid = BATTERY_PROPERTY_UNKNOWN;
+ bcdb.pid = 0;
+ bcdb.batt_design_cap = cpu_to_le16(design_cap);
+ bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap);
+ bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0;
+ memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb));
+ msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb),
+ 0, /* Denotes if request chunk */
+ 0, /* Chunk number */
+ 1 /* Chunked */);
+
+ data_obj_cnt = count_chunked_data_objs(sizeof(bcdb));
+ msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role,
+ port->data_role, port->negotiated_rev,
+ port->message_id, data_obj_cnt);
+
return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
}
@@ -1559,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del
}
}
-static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
+static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port)
+{
+ hrtimer_cancel(&port->vdm_discovery_timer);
+ kthread_cancel_work_sync(&port->vdm_discovery_work);
+}
+
+static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
{
if (delay_ms) {
- hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
+ hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
} else {
- hrtimer_cancel(&port->send_discover_timer);
- kthread_queue_work(port->wq, &port->send_discover_work);
+ hrtimer_cancel(&port->vdm_discovery_timer);
+ kthread_queue_work(port->wq, &port->vdm_discovery_work);
}
}
@@ -1773,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
WARN_ON(!mutex_is_locked(&port->lock));
/* If is sending discover_identity, handle received message first */
- if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) {
- if (tx_sop_type == TCPC_TX_SOP_PRIME)
- port->send_discover_prime = true;
- else
- port->send_discover = true;
- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS);
- } else {
+ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT)
+ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS);
+ else
/* Make sure we are not still processing a previous VDM packet */
WARN_ON(port->vdm_state > VDM_STATE_DONE);
- }
port->vdo_count = cnt + 1;
port->vdo_data[0] = header;
@@ -1805,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work)
struct tcpm_port *port = event->port;
mutex_lock(&port->lock);
- if (port->state != SRC_READY && port->state != SNK_READY &&
- port->state != SRC_VDM_IDENTITY_REQUEST) {
+ if (!tcpm_can_send_vdm(port->state)) {
tcpm_log_force(port, "dropping altmode_vdm_event");
goto port_unlock;
}
@@ -2034,7 +2228,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port)
if (!port->partner)
return;
- for (i = 0; i < modep->altmodes; i++) {
+ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) {
altmode = typec_partner_register_altmode(port->partner,
&modep->altmode_desc[i]);
if (IS_ERR(altmode)) {
@@ -2052,9 +2246,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port)
struct typec_altmode *altmode;
int i;
- typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes);
+ typec_plug_set_num_altmodes(port->plug_prime,
+ min(modep->altmodes, ALTMODE_DISCOVERY_MAX));
- for (i = 0; i < modep->altmodes; i++) {
+ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) {
altmode = typec_plug_register_altmode(port->plug_prime,
&modep->altmode_desc[i]);
if (IS_ERR(altmode)) {
@@ -2154,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port)
tcpm_can_communicate_sop_prime(port);
}
+static void tcpm_update_vdm_discovery_state(struct tcpm_port *port,
+ enum vdm_discovery_states new_state)
+{
+ enum vdm_discovery_states old_state = port->vdm_discovery_state;
+
+ if (old_state != new_state)
+ tcpm_log_force(port, "vdm discovery state changed: %s -> %s",
+ vdm_discovery_state_strings[old_state],
+ vdm_discovery_state_strings[new_state]);
+
+ port->vdm_discovery_state = new_state;
+}
+
static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response,
enum tcpm_transmit_type rx_sop_type,
enum tcpm_transmit_type *response_tx_sop_type)
@@ -2171,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response,
response[0] = VDO(svid, 1,
typec_get_negotiated_svdm_version(typec),
CMD_DISCOVER_MODES);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES);
return 1;
}
@@ -2179,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response,
response[0] = VDO(USB_SID_PD, 1,
typec_get_cable_svdm_version(typec),
CMD_DISCOVER_SVID);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES);
return 1;
}
tcpm_register_partner_altmodes(port);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
} else if (rx_sop_type == TCPC_TX_SOP_PRIME) {
modep = &port->mode_data_prime;
modep->svid_index++;
@@ -2193,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response,
response[0] = VDO(svid, 1,
typec_get_cable_svdm_version(typec),
CMD_DISCOVER_MODES);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES);
return 1;
}
tcpm_register_plug_altmodes(port);
tcpm_register_partner_altmodes(port);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
}
return 0;
@@ -2376,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
typec_cable_set_svdm_version(port->cable,
svdm_version);
}
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT);
+
/* 6.4.4.3.1 */
svdm_consume_identity(port, p, cnt);
/* Attempt Vconn swap, delay SOP' discovery if necessary */
if (tcpm_attempt_vconn_swap_discovery(port)) {
- port->send_discover_prime = true;
port->upcoming_state = VCONN_SWAP_SEND;
ret = tcpm_ams_start(port, VCONN_SWAP);
if (!ret)
return 0;
/* Cannot perform Vconn swap */
port->upcoming_state = INVALID_STATE;
- port->send_discover_prime = false;
}
/*
@@ -2398,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
if (IS_ERR_OR_NULL(port->cable) &&
tcpm_can_communicate_sop_prime(port)) {
*response_tx_sop_type = TCPC_TX_SOP_PRIME;
- port->send_discover_prime = true;
response[0] = VDO(USB_SID_PD, 1,
typec_get_negotiated_svdm_version(typec),
CMD_DISCOVER_IDENT);
@@ -2426,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
return 0;
}
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT);
*response_tx_sop_type = TCPC_TX_SOP;
response[0] = VDO(USB_SID_PD, 1,
@@ -2445,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
rlen = 1;
} else {
if (rx_sop_type == TCPC_TX_SOP) {
+ tcpm_update_vdm_discovery_state(port,
+ VDM_DISCOVERY_PARTNER_SVIDS);
if (modep->nsvids && supports_modal(port)) {
response[0] = VDO(modep->svids[0], 1, svdm_version,
CMD_DISCOVER_MODES);
rlen = 1;
+ } else {
+ tcpm_update_vdm_discovery_state(port,
+ VDM_DISCOVERY_COMPLETE);
}
} else if (rx_sop_type == TCPC_TX_SOP_PRIME) {
+ tcpm_update_vdm_discovery_state(port,
+ VDM_DISCOVERY_CABLE_SVIDS);
if (modep_prime->nsvids) {
response[0] = VDO(modep_prime->svids[0], 1,
svdm_version, CMD_DISCOVER_MODES);
rlen = 1;
+ } else {
+ tcpm_register_partner_altmodes(port);
+ tcpm_update_vdm_discovery_state(port,
+ VDM_DISCOVERY_COMPLETE);
}
}
}
@@ -2505,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
case CMDT_RSP_NAK:
tcpm_ams_finish(port);
switch (cmd) {
+ /*
+ * The cable is not allowed to respond with NAK so this must've happened over SOP
+ */
case CMD_DISCOVER_IDENT:
case CMD_DISCOVER_SVID:
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
+ break;
case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15):
break;
case CMD_DISCOVER_MODES:
@@ -2686,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
port->vdm_sm_running = false;
}
-static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd,
- const u32 *data, int count, enum tcpm_transmit_type tx_sop_type)
-{
- int svdm_version;
- u32 header;
-
- switch (tx_sop_type) {
- case TCPC_TX_SOP_PRIME:
- /*
- * If the port partner is discovered, then the port partner's
- * SVDM Version will be returned
- */
- svdm_version = typec_get_cable_svdm_version(port->typec_port);
- if (svdm_version < 0)
- svdm_version = SVDM_VER_MAX;
- break;
- case TCPC_TX_SOP:
- svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
- if (svdm_version < 0)
- return;
- break;
- default:
- svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
- if (svdm_version < 0)
- return;
- break;
- }
-
- if (WARN_ON(count > VDO_MAX_SIZE - 1))
- count = VDO_MAX_SIZE - 1;
-
- /* set VDM header with VID & CMD */
- header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ?
- 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION),
- svdm_version, cmd);
- tcpm_queue_vdm(port, header, data, count, tx_sop_type);
-}
-
static unsigned int vdm_ready_timeout(u32 vdm_hdr)
{
unsigned int timeout;
@@ -2769,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
* if there's traffic or we're not in PDO ready state don't send
* a VDM.
*/
- if (port->state != SRC_READY && port->state != SNK_READY &&
- port->state != SRC_VDM_IDENTITY_REQUEST) {
+ if (!tcpm_can_send_vdm(port->state)) {
port->vdm_sm_running = false;
break;
}
@@ -2780,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port)
switch (PD_VDO_CMD(vdo_hdr)) {
case CMD_DISCOVER_IDENT:
res = tcpm_ams_start(port, DISCOVER_IDENTITY);
- if (res == 0) {
- switch (port->tx_sop_type) {
- case TCPC_TX_SOP_PRIME:
- port->send_discover_prime = false;
- break;
- case TCPC_TX_SOP:
- port->send_discover = false;
- break;
- default:
- port->send_discover = false;
- break;
- }
- } else if (res == -EAGAIN) {
+ if (res == -EAGAIN) {
port->vdo_data[0] = 0;
- mod_send_discover_delayed_work(port,
- SEND_DISCOVER_RETRY_MS);
+ mod_vdm_discovery_delayed_work(port,
+ SEND_DISCOVERY_VDM_RETRY_MS);
}
break;
case CMD_DISCOVER_SVID:
@@ -2853,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
*/
if (port->state == SRC_VDM_IDENTITY_REQUEST) {
tcpm_ams_finish(port);
+ port->vdo_data[0] = 0;
port->vdm_state = VDM_STATE_DONE;
tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
/*
@@ -2869,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
tcpm_ams_finish(port);
} else {
tcpm_ams_finish(port);
+ port->vdo_data[0] = 0;
if (port->tx_sop_type == TCPC_TX_SOP)
break;
/* Handle SOP' Transmission Errors */
@@ -2878,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port)
* discovery process on SOP only.
*/
case CMD_DISCOVER_IDENT:
- port->vdo_data[0] = 0;
response[0] = VDO(USB_SID_PD, 1,
typec_get_negotiated_svdm_version(
port->typec_port),
CMD_DISCOVER_SVID);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT);
tcpm_queue_vdm(port, response[0], &response[1],
0, TCPC_TX_SOP);
break;
@@ -2892,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port)
*/
case CMD_DISCOVER_SVID:
tcpm_register_partner_altmodes(port);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
break;
case CMD_DISCOVER_MODES:
tcpm_register_partner_altmodes(port);
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
break;
default:
break;
@@ -3807,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
PD_MSG_CTRL_NOT_SUPP,
NONE_AMS);
} else {
- if (port->send_discover && port->negotiated_rev < PD_REV30) {
+ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN &&
+ port->negotiated_rev < PD_REV30) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
@@ -3823,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
PD_MSG_CTRL_NOT_SUPP,
NONE_AMS);
} else {
- if (port->send_discover && port->negotiated_rev < PD_REV30) {
+ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN &&
+ port->negotiated_rev < PD_REV30) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
@@ -3832,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
}
break;
case PD_CTRL_VCONN_SWAP:
- if (port->send_discover && port->negotiated_rev < PD_REV30) {
+ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN &&
+ port->negotiated_rev < PD_REV30) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
@@ -3891,6 +4076,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port,
{
enum pd_ext_msg_type type = pd_header_type_le(msg->header);
unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header);
+ const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg;
/* stopping VDM state machine if interrupted by other Messages */
if (tcpm_vdm_ams(port)) {
@@ -3899,7 +4085,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port,
mod_vdm_delayed_work(port, 0);
}
- if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) {
+ if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) {
tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS);
tcpm_log(port, "Unchunked extended messages unsupported");
return;
@@ -3924,9 +4110,25 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port,
NONE_AMS, 0);
}
break;
- case PD_EXT_SOURCE_CAP_EXT:
- case PD_EXT_GET_BATT_CAP:
case PD_EXT_GET_BATT_STATUS:
+ if (data_size >= 1) {
+ port->batt_request_id = ext_msg->data[0];
+ tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS,
+ GETTING_BATTERY_STATUS);
+ } else {
+ tcpm_set_state(port, SOFT_RESET_SEND, 0);
+ }
+ break;
+ case PD_EXT_GET_BATT_CAP:
+ if (data_size >= 1) {
+ port->batt_request_id = ext_msg->data[0];
+ tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP,
+ GETTING_BATTERY_CAPABILITIES);
+ } else {
+ tcpm_set_state(port, SOFT_RESET_SEND, 0);
+ }
+ break;
+ case PD_EXT_SOURCE_CAP_EXT:
case PD_EXT_BATT_CAP:
case PD_EXT_GET_MANUFACTURER_INFO:
case PD_EXT_MANUFACTURER_INFO:
@@ -4137,6 +4339,22 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
ret);
tcpm_ams_finish(port);
break;
+ case PD_MSG_DATA_BATT_STATUS:
+ ret = tcpm_pd_send_batt_status(port);
+ if (ret)
+ tcpm_log(port,
+ "Failed to send battery status ret=%d",
+ ret);
+ tcpm_ams_finish(port);
+ break;
+ case PD_MSG_EXT_BATT_CAP:
+ ret = tcpm_pd_send_batt_cap(port);
+ if (ret)
+ tcpm_log(port,
+ "Failed to send battery cap ret=%d",
+ ret);
+ tcpm_ams_finish(port);
+ break;
default:
break;
}
@@ -4853,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
port->partner = NULL;
port->attached = true;
- port->send_discover = true;
- port->send_discover_prime = false;
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN);
return 0;
@@ -4896,11 +5113,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port)
struct pd_mode_data *modep_prime = &port->mode_data_prime;
int i;
- for (i = 0; i < modep->altmodes; i++) {
+ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) {
typec_unregister_altmode(port->partner_altmode[i]);
port->partner_altmode[i] = NULL;
}
- for (i = 0; i < modep_prime->altmodes; i++) {
+ for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) {
typec_unregister_altmode(port->plug_prime_altmode[i]);
port->plug_prime_altmode[i] = NULL;
}
@@ -4931,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port)
port->in_ams = false;
port->ams = NONE_AMS;
port->vdm_sm_running = false;
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN);
+ mod_vdm_discovery_cancel_delayed_work(port);
tcpm_unregister_altmodes(port);
tcpm_typec_disconnect(port);
port->attached = false;
@@ -5014,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port)
port->partner = NULL;
port->attached = true;
- port->send_discover = true;
- port->send_discover_prime = false;
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN);
return 0;
}
@@ -5422,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port)
* as well.
*/
if (port->explicit_contract) {
- if (port->send_discover_prime) {
- port->tx_sop_type = TCPC_TX_SOP_PRIME;
- } else {
- port->tx_sop_type = TCPC_TX_SOP;
+ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN)
tcpm_set_initial_svdm_version(port);
- }
- mod_send_discover_delayed_work(port, 0);
+ mod_vdm_discovery_delayed_work(port, 0);
} else {
- port->send_discover = false;
- port->send_discover_prime = false;
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
}
/*
@@ -5814,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port)
* as well.
*/
if (port->explicit_contract) {
- if (port->send_discover_prime) {
- port->tx_sop_type = TCPC_TX_SOP_PRIME;
- } else {
- port->tx_sop_type = TCPC_TX_SOP;
+ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN)
tcpm_set_initial_svdm_version(port);
- }
- mod_send_discover_delayed_work(port, 0);
+ mod_vdm_discovery_delayed_work(port, 0);
} else {
- port->send_discover = false;
- port->send_discover_prime = false;
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
}
power_supply_changed(port->psy);
@@ -5869,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port)
port->tcpc->set_pd_rx(port->tcpc, false);
tcpm_unregister_altmodes(port);
port->nr_sink_caps = 0;
- port->send_discover = true;
- port->send_discover_prime = false;
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN);
+ mod_vdm_discovery_cancel_delayed_work(port);
if (port->pwr_role == TYPEC_SOURCE)
tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF,
PD_T_PS_HARD_RESET);
@@ -6017,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port)
/* DR_Swap states */
case DR_SWAP_SEND:
tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP);
- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) {
- port->send_discover = true;
- port->send_discover_prime = false;
- }
tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT,
PD_T_SENDER_RESPONSE);
break;
case DR_SWAP_ACCEPT:
tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) {
- port->send_discover = true;
- port->send_discover_prime = false;
- }
tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
break;
case DR_SWAP_SEND_TIMEOUT:
tcpm_swap_complete(port, -ETIMEDOUT);
- port->send_discover = false;
- port->send_discover_prime = false;
tcpm_ams_finish(port);
tcpm_set_state(port, ready_state(port), 0);
break;
@@ -6047,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port)
else
tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role,
TYPEC_HOST);
+ if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20)
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN);
tcpm_ams_finish(port);
tcpm_set_state(port, ready_state(port), 0);
break;
@@ -6330,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port)
/* Cable states */
case SRC_VDM_IDENTITY_REQUEST:
- port->send_discover_prime = true;
- port->tx_sop_type = TCPC_TX_SOP_PRIME;
- mod_send_discover_delayed_work(port, 0);
+ mod_vdm_discovery_delayed_work(port, 0);
port->upcoming_state = SRC_SEND_CAPABILITIES;
break;
@@ -7036,8 +7234,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work)
goto unlock;
/* Send when the state machine is idle */
- if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover ||
- port->send_discover_prime)
+ if (port->state != SNK_READY || port->vdm_sm_running ||
+ port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN)
goto resched;
port->upcoming_state = GET_SINK_CAP;
@@ -7054,29 +7252,160 @@ unlock:
mutex_unlock(&port->lock);
}
-static void tcpm_send_discover_work(struct kthread_work *work)
+static void tcpm_vdm_discovery_work(struct kthread_work *work)
{
- struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work);
+ struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work);
+ enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP;
+ struct typec_port *typec = port->typec_port;
+ struct pd_mode_data *modep, *modep_prime;
+ u32 msg[2] = { };
+ int svdm_version;
mutex_lock(&port->lock);
- /* No need to send DISCOVER_IDENTITY anymore */
- if (!port->send_discover && !port->send_discover_prime)
+
+ tcpm_log_force(port, "%s state [%s]", __func__,
+ vdm_discovery_state_strings[port->vdm_discovery_state]);
+
+ /* No need to perform work if Discovery process is complete */
+ if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE)
goto unlock;
- if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) {
- port->send_discover = false;
- port->send_discover_prime = false;
+ /* Retry if the port is not idle */
+ if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) {
+ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS);
goto unlock;
}
- /* Retry if the port is not idle */
- if ((port->state != SRC_READY && port->state != SNK_READY &&
- port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) {
- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS);
+ modep = &port->mode_data;
+ modep_prime = &port->mode_data_prime;
+
+ svdm_version = typec_get_negotiated_svdm_version(typec);
+
+ switch (port->vdm_discovery_state) {
+ /*
+ * The port has not received a Discover Identity response from the port partner.
+ *
+ * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and
+ * SNK_READY states if there is an explicit contract
+ * 2. The port will send Discover Identity to the cable over SOP' in the
+ * SRC_VDM_IDENTITY_REQUEST state if capable of doing so.
+ */
+ case VDM_DISCOVERY_UNKNOWN:
+ /* Can't send Discover Identity, VDM discovery is complete */
+ if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) {
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
+ goto unlock;
+ }
+
+ if (port->state == SRC_VDM_IDENTITY_REQUEST) {
+ tx_sop_type = TCPC_TX_SOP_PRIME;
+ svdm_version = SVDM_VER_MAX;
+ }
+
+ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT);
+ break;
+ /*
+ * The port has received a Discover Identity ACK from the port partner.
+ *
+ * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and
+ * SNK_READY states if it did not previously discover the cable but is capable of doing
+ * so.
+ * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and
+ * SNK_READY states otherwise.
+ */
+ case VDM_DISCOVERY_PARTNER_IDENT:
+ if (tcpm_can_communicate_sop_prime(port) && !port->cable) {
+ tx_sop_type = TCPC_TX_SOP_PRIME;
+ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT);
+ } else {
+ if (tcpm_can_communicate_sop_prime(port))
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT);
+ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID);
+ }
+ break;
+ /*
+ * The port has received a Discover Identity ACK from the cable.
+ *
+ * 1. The port will send Discover SVIDs to the partner over SOP.
+ */
+ case VDM_DISCOVERY_CABLE_IDENT:
+ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID);
+ break;
+ /*
+ * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported
+ * by the partner.
+ *
+ * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports
+ * modal operation and valid SVIDs were registered.
+ * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise.
+ */
+ case VDM_DISCOVERY_PARTNER_SVIDS:
+ if (modep->nsvids && supports_modal(port)) {
+ msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES);
+ } else {
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
+ goto unlock;
+ }
+ break;
+ /*
+ * The port has received a Discover Modes ACK from the partner for any mode.
+ *
+ * 1. The port will send Discover Modes for the next SVID that has not been discovered to
+ * the port partner over SOP.
+ * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP'
+ * and the cable supports VDMs.
+ * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise.
+ */
+ case VDM_DISCOVERY_PARTNER_MODES:
+ /* Not all modes have been discovered yet */
+ if (modep->svid_index < modep->nsvids) {
+ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version,
+ CMD_DISCOVER_MODES);
+ } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) {
+ tx_sop_type = TCPC_TX_SOP_PRIME;
+ svdm_version = typec_get_cable_svdm_version(typec);
+ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID);
+ } else {
+ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE);
+ goto unlock;
+ }
+ break;
+ /*
+ * The port has received a Discover SVIDs ACK from the cable over SOP'.
+ *
+ * 1. The port will send Discover Modes for the first SVID over SOP'.
+ */
+ case VDM_DISCOVERY_CABLE_SVIDS:
+ if (modep_prime->nsvids) {
+ tx_sop_type = TCPC_TX_SOP_PRIME;
+ svdm_version = typec_get_cable_svdm_version(typec);
+ msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES);
+ } else {
+ goto unlock;
+ }
+ break;
+ /*
+ * The port has received a Discover Modes ACK from the cable for any mode.
+ *
+ * 1. The port will send Discover Modes for the next SVID that has not been discovered to
+ * the cable over SOP'.
+ */
+ case VDM_DISCOVERY_CABLE_MODES:
+ if (modep_prime->svid_index < modep_prime->nsvids) {
+ tx_sop_type = TCPC_TX_SOP_PRIME;
+ svdm_version = typec_get_cable_svdm_version(typec);
+ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version,
+ CMD_DISCOVER_MODES);
+ } else {
+ goto unlock;
+ }
+ break;
+ default:
goto unlock;
}
- tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type);
+ if (svdm_version >= 0)
+ tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type);
unlock:
mutex_unlock(&port->lock);
@@ -8492,12 +8821,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer)
return HRTIMER_NORESTART;
}
-static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer)
+static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer)
{
- struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer);
+ struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer);
if (port->registered)
- kthread_queue_work(port->wq, &port->send_discover_work);
+ kthread_queue_work(port->wq, &port->vdm_discovery_work);
return HRTIMER_NORESTART;
}
@@ -8531,14 +8860,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work);
kthread_init_work(&port->event_work, tcpm_pd_event_handler);
kthread_init_work(&port->enable_frs, tcpm_enable_frs_work);
- kthread_init_work(&port->send_discover_work, tcpm_send_discover_work);
+ kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work);
hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler,
CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
- hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC,
+ hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
spin_lock_init(&port->pd_event_lock);
@@ -8633,11 +8962,12 @@ void tcpm_unregister_port(struct tcpm_port *port)
port->registered = false;
kthread_destroy_worker(port->wq);
- hrtimer_cancel(&port->send_discover_timer);
+ hrtimer_cancel(&port->vdm_discovery_timer);
hrtimer_cancel(&port->enable_frs_timer);
hrtimer_cancel(&port->vdm_state_machine_timer);
hrtimer_cancel(&port->state_machine_timer);
+ power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt);
tcpm_reset_port(port);
tcpm_port_unregister_pd(port);
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index d5ee0af9058b..522f56742aa9 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -130,6 +130,7 @@ enum {
TPS_MODE_BIST,
TPS_MODE_DISC,
TPS_MODE_PTCH,
+ TPS_MODE_APP1,
};
static const char *const modes[] = {
@@ -138,6 +139,7 @@ static const char *const modes[] = {
[TPS_MODE_BIST] = "BIST",
[TPS_MODE_DISC] = "DISC",
[TPS_MODE_PTCH] = "PTCH",
+ [TPS_MODE_APP1] = "APP1",
};
/* Unrecognized commands will be replaced with "!CMD" */
@@ -159,6 +161,7 @@ struct tipd_data {
int (*init)(struct tps6598x *tps);
int (*switch_power_state)(struct tps6598x *tps, u8 target_state);
bool (*read_data_status)(struct tps6598x *tps);
+ bool (*read_power_status)(struct tps6598x *tps);
int (*reset)(struct tps6598x *tps);
int (*connect)(struct tps6598x *tps, u32 status);
};
@@ -630,6 +633,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps)
return true;
}
+/*
+ * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported
+ * and the remaining bits are redundant with STATUS register (0x1A).
+ * Synthesize pwr_status from the already-read STATUS register.
+ */
+static bool tps66993_read_power_status(struct tps6598x *tps)
+{
+ u16 pwr_status = 0;
+
+ /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */
+ if (tps->status & TPS_STATUS_PLUG_PRESENT)
+ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1);
+
+ /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */
+ if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status))
+ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1);
+
+ if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD)
+ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK,
+ TPS_POWER_STATUS_TYPEC_CURRENT_PD);
+
+ tps->pwr_status = pwr_status;
+
+ tps->data->trace_power_status(pwr_status);
+
+ return true;
+}
+
static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status)
{
int ret;
@@ -897,7 +928,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data)
goto err_unlock;
if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) {
- if (!tps6598x_read_power_status(tps))
+ if (!tps->data->read_power_status(tps))
goto err_unlock;
if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) {
if (tps6598x_read_partner_identity(tps)) {
@@ -952,7 +983,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data)
goto err_clear_ints;
if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE)
- if (!tps6598x_read_power_status(tps))
+ if (!tps->data->read_power_status(tps))
goto err_clear_ints;
if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE)
@@ -1025,17 +1056,25 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data)
if (!tps6598x_read_status(tps, &status))
goto err_unlock;
+ tps->status = status;
+
if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE)
- if (!tps6598x_read_power_status(tps))
+ if (!tps->data->read_power_status(tps))
goto err_unlock;
if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE)
if (!tps->data->read_data_status(tps))
goto err_unlock;
- /* Handle plug insert or removal */
- if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT)
+ /*
+ * Refresh power status before connect - needed for TPS66993 which
+ * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE.
+ */
+ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) {
+ if (!tps->data->read_power_status(tps))
+ goto err_unlock;
tps6598x_handle_plug_event(tps, status);
+ }
err_unlock:
mutex_unlock(&tps->lock);
@@ -1071,6 +1110,7 @@ static int tps6598x_check_mode(struct tps6598x *tps)
switch (ret) {
case TPS_MODE_APP:
+ case TPS_MODE_APP1:
case TPS_MODE_PTCH:
return ret;
case TPS_MODE_BOOT:
@@ -1744,7 +1784,7 @@ static int tps6598x_probe(struct i2c_client *client)
struct tps6598x *tps;
struct fwnode_handle *fwnode;
u32 status;
- u32 vid;
+ u32 vid = 0;
int ret;
data = i2c_get_match_data(client);
@@ -1772,8 +1812,11 @@ static int tps6598x_probe(struct i2c_client *client)
if (!device_is_compatible(tps->dev, "ti,tps25750")) {
ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
- if (ret < 0 || !vid)
+ if (ret < 0 || !vid) {
+ dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n",
+ ret, vid);
return -ENODEV;
+ }
}
/*
@@ -1809,6 +1852,8 @@ static int tps6598x_probe(struct i2c_client *client)
goto err_clear_mask;
}
+ tps->status = status;
+
/*
* This fwnode has a "compatible" property, but is never populated as a
* struct device. Instead we simply parse it to read the properties.
@@ -1836,7 +1881,7 @@ static int tps6598x_probe(struct i2c_client *client)
if (status & TPS_STATUS_PLUG_PRESENT) {
ret = -EINVAL;
- if (!tps6598x_read_power_status(tps))
+ if (!tps->data->read_power_status(tps))
goto err_unregister_port;
if (!tps->data->read_data_status(tps))
goto err_unregister_port;
@@ -1851,7 +1896,7 @@ static int tps6598x_probe(struct i2c_client *client)
IRQF_SHARED | IRQF_ONESHOT,
dev_name(&client->dev), tps);
} else {
- dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n");
+ dev_dbg(tps->dev, "no IRQ specified, using polling mode\n");
INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work);
queue_delayed_work(system_power_efficient_wq, &tps->wq_poll,
msecs_to_jiffies(POLL_INTERVAL));
@@ -1978,6 +2023,7 @@ static const struct tipd_data cd321x_data = {
.trace_status = trace_tps6598x_status,
.init = cd321x_init,
.read_data_status = cd321x_read_data_status,
+ .read_power_status = tps6598x_read_power_status,
.reset = cd321x_reset,
.switch_power_state = cd321x_switch_power_state,
.connect = cd321x_connect,
@@ -1997,6 +2043,25 @@ static const struct tipd_data tps6598x_data = {
.apply_patch = tps6598x_apply_patch,
.init = tps6598x_init,
.read_data_status = tps6598x_read_data_status,
+ .read_power_status = tps6598x_read_power_status,
+ .reset = tps6598x_reset,
+ .connect = tps6598x_connect,
+};
+
+static const struct tipd_data tps66993_data = {
+ .irq_handler = tps6598x_interrupt,
+ .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE |
+ TPS_REG_INT_PLUG_EVENT,
+ .tps_struct_size = sizeof(struct tps6598x),
+ .register_port = tps6598x_register_port,
+ .unregister_port = tps6598x_unregister_port,
+ .trace_data_status = trace_tps6598x_data_status,
+ .trace_power_status = trace_tps6598x_power_status,
+ .trace_status = trace_tps6598x_status,
+ .apply_patch = tps6598x_apply_patch,
+ .init = tps6598x_init,
+ .read_data_status = tps6598x_read_data_status,
+ .read_power_status = tps66993_read_power_status,
.reset = tps6598x_reset,
.connect = tps6598x_connect,
};
@@ -2015,12 +2080,14 @@ static const struct tipd_data tps25750_data = {
.apply_patch = tps25750_apply_patch,
.init = tps25750_init,
.read_data_status = tps6598x_read_data_status,
+ .read_power_status = tps6598x_read_power_status,
.reset = tps25750_reset,
.connect = tps6598x_connect,
};
static const struct of_device_id tps6598x_of_match[] = {
{ .compatible = "ti,tps6598x", &tps6598x_data},
+ { .compatible = "ti,tps66993", &tps66993_data},
{ .compatible = "apple,cd321x", &cd321x_data},
{ .compatible = "ti,tps25750", &tps25750_data},
{}
diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h
index 03edbb77bbd6..d4140f4da5bb 100644
--- a/drivers/usb/typec/tipd/tps6598x.h
+++ b/drivers/usb/typec/tipd/tps6598x.h
@@ -142,9 +142,13 @@
#define TPS_SYSTEM_POWER_STATE_S4 0x04
#define TPS_SYSTEM_POWER_STATE_S5 0x05
-/* TPS_REG_POWER_STATUS bits */
-#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x))
-#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x))
+/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */
+#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0)
+#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1)
+#define TPS_POWER_STATUS_CONNECTION(x) \
+ TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x))
+#define TPS_POWER_STATUS_SOURCESINK(x) \
+ TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x))
#define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x))
#define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2)
diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c
index ff33a5e7c6b0..77a0dd75edd3 100644
--- a/drivers/usb/typec/ucsi/debugfs.c
+++ b/drivers/usb/typec/ucsi/debugfs.c
@@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used)
if (ucsi->debugfs->status)
return ucsi->debugfs->status;
- seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high,
- ucsi->debugfs->response.low);
+ seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext,
+ ucsi->debugfs->response.high, ucsi->debugfs->response.low);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ucsi_resp);
@@ -162,6 +162,7 @@ void ucsi_debugfs_unregister(struct ucsi *ucsi)
debugfs_remove_recursive(ucsi->debugfs->dentry);
kfree(ucsi->debugfs);
+ ucsi->debugfs = NULL;
}
void ucsi_debugfs_init(void)
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 49f1c53721bb..bef3f9b71d71 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -91,7 +91,8 @@ int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci,
if (ret)
goto out_clear_bit;
- if (!wait_for_completion_timeout(&ucsi->complete, 5 * HZ))
+ if (!wait_for_completion_timeout(&ucsi->complete,
+ msecs_to_jiffies(UCSI_TIMEOUT_MS)))
ret = -ETIMEDOUT;
out_clear_bit:
@@ -1101,8 +1102,8 @@ static int ucsi_register_plug(struct ucsi_connector *con)
plug = typec_register_plug(con->cable, &desc);
if (IS_ERR(plug)) {
dev_err(con->ucsi->dev,
- "con%d: failed to register plug (%ld)\n", con->num,
- PTR_ERR(plug));
+ "con%d: failed to register plug (%pe)\n", con->num,
+ plug);
return PTR_ERR(plug);
}
@@ -1160,8 +1161,8 @@ static int ucsi_register_cable(struct ucsi_connector *con)
cable = typec_register_cable(con->port, &desc);
if (IS_ERR(cable)) {
dev_err(con->ucsi->dev,
- "con%d: failed to register cable (%ld)\n", con->num,
- PTR_ERR(cable));
+ "con%d: failed to register cable (%pe)\n", con->num,
+ cable);
return PTR_ERR(cable);
}
@@ -1290,8 +1291,8 @@ static int ucsi_register_partner(struct ucsi_connector *con)
partner = typec_register_partner(con->port, &desc);
if (IS_ERR(partner)) {
dev_err(con->ucsi->dev,
- "con%d: failed to register partner (%ld)\n", con->num,
- PTR_ERR(partner));
+ "con%d: failed to register partner (%pe)\n", con->num,
+ partner);
return PTR_ERR(partner);
}
@@ -2220,7 +2221,7 @@ static void ucsi_init_work(struct work_struct *work)
return;
}
- queue_delayed_work(system_long_wq, &ucsi->work,
+ queue_delayed_work(system_dfl_long_wq, &ucsi->work,
UCSI_ROLE_SWITCH_INTERVAL);
}
}
@@ -2344,7 +2345,7 @@ int ucsi_register(struct ucsi *ucsi)
UCSI_BCD_GET_MINOR(ucsi->version),
UCSI_BCD_GET_SUBMINOR(ucsi->version));
- queue_delayed_work(system_long_wq, &ucsi->work, 0);
+ queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0);
ucsi_debugfs_register(ucsi);
return 0;
@@ -2366,6 +2367,8 @@ void ucsi_unregister(struct ucsi *ucsi)
cancel_delayed_work_sync(&ucsi->work);
cancel_work_sync(&ucsi->resume_work);
+ ucsi_debugfs_unregister(ucsi);
+
/* Disable notifications */
ucsi->ops->async_control(ucsi, cmd);
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index 6e1608d88ec3..dc594388dcd0 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -466,6 +466,7 @@ struct ucsi_debugfs_entry {
struct ucsi_data {
u64 low;
u64 high;
+ u64 ext;
} response;
int status;
u8 message_out[MESSAGE_OUT_MAX_LEN];
diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
index ca1b534cb183..57b37fca150c 100644
--- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
+++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
@@ -105,6 +105,7 @@ struct gaokun_ucsi {
struct notifier_block nb;
u16 version;
u8 num_ports;
+ bool registered;
};
/* -------------------------------------------------------------------------- */
@@ -482,8 +483,13 @@ static void gaokun_ucsi_register_worker(struct work_struct *work)
}
ret = ucsi_register(ucsi);
- if (ret)
+ if (ret) {
dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
+ gaokun_ec_unregister_notify(uec->ec, &uec->nb);
+ return;
+ }
+
+ uec->registered = true;
}
static int gaokun_ucsi_probe(struct auxiliary_device *adev,
@@ -528,8 +534,11 @@ static void gaokun_ucsi_remove(struct auxiliary_device *adev)
int i;
disable_delayed_work_sync(&uec->work);
- gaokun_ec_unregister_notify(uec->ec, &uec->nb);
- ucsi_unregister(uec->ucsi);
+ if (uec->registered) {
+ gaokun_ec_unregister_notify(uec->ec, &uec->nb);
+ ucsi_unregister(uec->ucsi);
+ }
+
for (i = 0; i < uec->num_ports; ++i)
typec_mux_put(uec->ports[i].typec_mux);
diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
index 557288c0274b..d48623fda79b 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev)
*/
struct tb_service_driver {
struct device_driver driver;
- int (*probe)(struct tb_service *svc, const struct tb_service_id *id);
+ int (*probe)(struct tb_service *svc);
void (*remove)(struct tb_service *svc);
void (*shutdown)(struct tb_service *svc);
const struct tb_service_id *id_table;
@@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc);
* @hop_count: Number of rings (end point hops) supported by NHI.
* @quirks: NHI specific quirks if any
* @domain_released: Completed when domain has been fully released
+ * @host_reset: Host router was reset on driver load, or forced on system
+ * shutdown/reboot. When set, tb_stop() asserts DPR on connected
+ * downstream ports to signal disconnect before tearing down the
+ * router tree. Only Thunderbolt 3 devices are reset; USB4
+ * routers are skipped.
*/
struct tb_nhi {
spinlock_t lock;
@@ -528,6 +533,7 @@ struct tb_nhi {
u32 hop_count;
unsigned long quirks;
struct completion domain_released;
+ bool host_reset;
};
/**
@@ -592,6 +598,8 @@ struct tb_ring {
#define RING_FLAG_FRAME BIT(1)
/* Enable end-to-end flow control */
#define RING_FLAG_E2E BIT(2)
+/* Do not enable interrupt for the ring */
+#define RING_FLAG_NO_INTERRUPT BIT(3)
struct ring_frame;
typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 1da4ad1610bc..49ab8dbb885f 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1185,7 +1185,8 @@ extern ssize_t usb_show_dynids(struct usb_dynids *dynids, char *buf);
* interface. It may also use usb_set_interface() to specify the
* appropriate altsetting. If unwilling to manage the interface,
* return -ENODEV, if genuine IO errors occurred, an appropriate
- * negative errno value.
+ * negative errno value. The usb_device_id parameter is only valid during
+ * probe.
* @disconnect: Called when the interface is no longer accessible, usually
* because its device has been (or is being) disconnected or the
* driver module is being unloaded.
diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index 337a5485af7c..914e78f8acfd 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -106,6 +106,9 @@ enum pd_ext_msg_type {
#define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \
cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0)))
+#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \
+ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1)))
+
static inline unsigned int pd_header_cnt(u16 header)
{
return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK;
@@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size)
return ((size / 4) + (size % 4 ? 1 : 0));
}
+/**
+ * batt_cap_ext_msg - Battery capability extended PD message
+ * @vid: Battery Vendor ID (assigned by USB-IF)
+ * @pid: Battery Product ID (assigned by battery or device vendor)
+ * @batt_design_cap: Battery design capacity in 0.1Wh
+ * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh
+ * @batt_type: Battery Type. bit0 when set indicates invalid battery reference.
+ * Rest of the bits are reserved.
+ */
+struct batt_cap_ext_msg {
+ __le16 vid;
+ __le16 pid;
+ __le16 batt_design_cap;
+ __le16 batt_last_chg_cap;
+ u8 batt_type;
+} __packed;
+
+#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0)
+
/* Sink Caps Extended Data Block Version */
#define SKEDB_VER_1_0 1
@@ -493,7 +515,7 @@ static inline unsigned int pdo_epr_avs_apdo_min_voltage_mv(u32 pdo)
static inline unsigned int pdo_epr_avs_apdo_max_voltage_mv(u32 pdo)
{
- return FIELD_GET(PDO_EPR_AVS_APDO_MIN_VOLT, pdo) * 100;
+ return FIELD_GET(PDO_EPR_AVS_APDO_MAX_VOLT, pdo) * 100;
}
static inline unsigned int pdo_epr_avs_apdo_pdp_w(u32 pdo)
@@ -724,4 +746,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi
#endif /* CONFIG_TYPEC */
+/* Battery Status Data Object */
+#define BSDO_PRESENT_CAPACITY GENMASK(31, 16)
+#define BSDO_CHG_STATUS GENMASK(11, 10)
+#define BSDO_BATTERY_PRESENT BIT(9)
+#define BSDO_INVALID_BATTERY_REFERENCE BIT(8)
+
+/*
+ * Battery Charge Status: Battery Charging Status Values as defined in
+ * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)".
+ */
+#define BSDO_BATTERY_INFO_CHARGING 0x0
+#define BSDO_BATTERY_INFO_DISCHARGING 0x1
+#define BSDO_BATTERY_INFO_IDLE 0x2
+#define BSDO_BATTERY_INFO_RSVD 0x3
+
+/**
+ * BSDO() - Pack data into Battery Status Data Object format.
+ * @batt_charge: Battery's present state of charge in 0.1WH increment.
+ * @chg_status: Battery charge status.
+ * @batt_present: Indicates that battery is present/attached when set else absent when unset.
+ * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status
+ * request.
+ */
+#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \
+ ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \
+ (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \
+ ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \
+ ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0))
+
#endif /* __LINUX_USB_PD_H */
diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h
index f7f5cfbdef12..9b46a6bc762c 100644
--- a/include/linux/usb/tcpci.h
+++ b/include/linux/usb/tcpci.h
@@ -144,6 +144,7 @@
#define TCPC_RX_BUF_FRAME_TYPE 0x31
#define TCPC_RX_BUF_FRAME_TYPE_SOP 0
#define TCPC_RX_BUF_FRAME_TYPE_SOP1 1
+#define TCPC_RX_BUF_FRAME_TYPE_MASK GENMASK(2, 0)
#define TCPC_RX_HDR 0x32
#define TCPC_RX_DATA 0x34 /* through 0x4f */
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index d61ec38216fa..10a783b738ef 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable);
struct typec_cable *typec_cable_get(struct typec_port *port);
void typec_cable_put(struct typec_cable *cable);
int typec_cable_is_active(struct typec_cable *cable);
+bool typec_cable_altmode_unsupported(struct typec_altmode *alt);
struct typec_plug *typec_register_plug(struct typec_cable *cable,
struct typec_plug_desc *desc);
diff --git a/include/target/target_core_fabric.h b/include/target/target_core_fabric.h
index e9039e73d058..390ace5bb252 100644
--- a/include/target/target_core_fabric.h
+++ b/include/target/target_core_fabric.h
@@ -95,6 +95,8 @@ struct target_core_fabric_ops {
struct se_lun *);
void (*fabric_pre_unlink)(struct se_portal_group *,
struct se_lun *);
+ void (*fabric_post_unlink)(struct se_portal_group *se_tpg,
+ struct se_lun *lun);
struct se_tpg_np *(*fabric_make_np)(struct se_portal_group *,
struct config_group *, const char *);
void (*fabric_drop_np)(struct se_tpg_np *);
diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h
index beef1752e36e..290308c9dd92 100644
--- a/include/uapi/linux/usb/functionfs.h
+++ b/include/uapi/linux/usb/functionfs.h
@@ -25,6 +25,7 @@ enum functionfs_flags {
FUNCTIONFS_EVENTFD = 32,
FUNCTIONFS_ALL_CTRL_RECIP = 64,
FUNCTIONFS_CONFIG0_SETUP = 128,
+ FUNCTIONFS_RW_PROXY_EPS = 256,
};
/* Descriptor of an non-audio endpoint */
@@ -414,4 +415,27 @@ struct usb_functionfs_event {
#define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \
struct usb_ffs_dmabuf_transfer_req)
+/*
+ * Enable or disable automatic zero-length packet (ZLP) appending for the
+ * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to
+ * enable.
+ *
+ * When enabled, the kernel will automatically append a ZLP at the end of
+ * a transfer if the payload length is an exact multiple of the endpoint's
+ * max packet size.
+ *
+ * This is useful for compatibility with legacy protocols which require
+ * automatic ZLP appending to data written from userspace.
+ *
+ * This ioctl can only be used on IN endpoints. It can be called at any time
+ * after the FunctionFS instance is active, even before the host has connected
+ * or enabled the endpoint.
+ *
+ * Returns zero on success, or a negative errno value on error:
+ * -ENODEV: The FunctionFS instance is not active.
+ * -EINVAL: The endpoint is not an IN endpoint.
+ * -EFAULT: Invalid user space pointer for the argument.
+ */
+#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32)
+
#endif /* _UAPI__LINUX_FUNCTIONFS_H__ */
diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 558e15a08478..993760ff332b 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -24,11 +24,8 @@ use crate::{
};
use core::{
marker::PhantomData,
- mem::{
- offset_of,
- MaybeUninit, //
- },
- ptr::NonNull,
+ mem::offset_of,
+ ptr::NonNull, //
};
/// An adapter for the registration of USB drivers.
@@ -133,8 +130,7 @@ impl DeviceId {
match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16,
idVendor: vendor,
idProduct: product,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -146,8 +142,7 @@ impl DeviceId {
idProduct: product,
bcdDevice_lo: bcd_lo,
bcdDevice_hi: bcd_hi,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -158,8 +153,7 @@ impl DeviceId {
bDeviceClass: class,
bDeviceSubClass: subclass,
bDeviceProtocol: protocol,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -170,8 +164,7 @@ impl DeviceId {
bInterfaceClass: class,
bInterfaceSubClass: subclass,
bInterfaceProtocol: protocol,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -183,8 +176,7 @@ impl DeviceId {
idVendor: vendor,
idProduct: product,
bInterfaceClass: class,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -196,8 +188,7 @@ impl DeviceId {
idVendor: vendor,
idProduct: product,
bInterfaceProtocol: protocol,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -209,8 +200,7 @@ impl DeviceId {
idVendor: vendor,
idProduct: product,
bInterfaceNumber: number,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
@@ -230,8 +220,7 @@ impl DeviceId {
bInterfaceClass: class,
bInterfaceSubClass: subclass,
bInterfaceProtocol: protocol,
- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`.
- ..unsafe { MaybeUninit::zeroed().assume_init() }
+ ..pin_init::zeroed()
})
}
}
@@ -385,6 +374,7 @@ impl<Ctx: device::DeviceContext> AsRef<Device> for Interface<Ctx> {
// SAFETY: Instances of `Interface` are always reference-counted.
unsafe impl AlwaysRefCounted for Interface {
+ #[inline]
fn inc_ref(&self) {
// SAFETY: The invariants of `Interface` guarantee that `self.as_raw()`
// returns a valid `struct usb_interface` pointer, for which we will
@@ -392,6 +382,7 @@ unsafe impl AlwaysRefCounted for Interface {
unsafe { bindings::usb_get_intf(self.as_raw()) };
}
+ #[inline]
unsafe fn dec_ref(obj: NonNull<Self>) {
// SAFETY: The safety requirements guarantee that the refcount is non-zero.
unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) }
@@ -436,6 +427,7 @@ kernel::impl_device_context_into_aref!(Device);
// SAFETY: Instances of `Device` are always reference-counted.
unsafe impl AlwaysRefCounted for Device {
+ #[inline]
fn inc_ref(&self) {
// SAFETY: The invariants of `Device` guarantee that `self.as_raw()`
// returns a valid `struct usb_device` pointer, for which we will
@@ -443,6 +435,7 @@ unsafe impl AlwaysRefCounted for Device {
unsafe { bindings::usb_get_dev(self.as_raw()) };
}
+ #[inline]
unsafe fn dec_ref(obj: NonNull<Self>) {
// SAFETY: The safety requirements guarantee that the refcount is non-zero.
unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) }