summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/configfs-thunderbolt_stream83
-rw-r--r--Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd2
-rw-r--r--Documentation/admin-guide/thunderbolt.rst61
-rw-r--r--Documentation/devicetree/bindings/usb/cdns,usb3.yaml73
-rw-r--r--Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml25
-rw-r--r--Documentation/devicetree/bindings/usb/genesys,gl850g.yaml18
-rw-r--r--Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml9
-rw-r--r--Documentation/devicetree/bindings/usb/richtek,rt1711h.txt44
-rw-r--r--Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml3
-rw-r--r--Documentation/hwmon/index.rst1
-rw-r--r--Documentation/hwmon/prom21-xhci.rst101
-rw-r--r--Documentation/translations/zh_CN/subsystem-apis.rst2
-rw-r--r--Documentation/translations/zh_CN/usb/CREDITS163
-rw-r--r--Documentation/translations/zh_CN/usb/acm.rst147
-rw-r--r--Documentation/translations/zh_CN/usb/authorization.rst139
-rw-r--r--Documentation/translations/zh_CN/usb/chipidea.rst150
-rw-r--r--Documentation/translations/zh_CN/usb/dwc3.rst63
-rw-r--r--Documentation/translations/zh_CN/usb/ehci.rst261
-rw-r--r--Documentation/translations/zh_CN/usb/index.rst54
-rw-r--r--Documentation/translations/zh_CN/usb/usbmon.rst427
-rw-r--r--arch/arm/mach-pxa/devices.c7
-rw-r--r--arch/arm/mach-pxa/gumstix.c1
-rw-r--r--arch/arm/mach-pxa/udc.h8
-rw-r--r--arch/arm64/boot/dts/qcom/pm4125.dtsi1
-rw-r--r--arch/arm64/boot/dts/qcom/pm7250b.dtsi1
-rw-r--r--arch/arm64/boot/dts/qcom/pm8150b.dtsi1
-rw-r--r--arch/arm64/boot/dts/qcom/pmi632.dtsi1
-rw-r--r--arch/arm64/boot/dts/qcom/qrb2210-rb1.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/qrb4210-rb2.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/qrb5165-rb5.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/sm8150-hdk.dts2
-rw-r--r--arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi2
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/prom21-xhci.c239
-rw-r--r--drivers/net/thunderbolt/main.c20
-rw-r--r--drivers/thunderbolt/Kconfig15
-rw-r--r--drivers/thunderbolt/Makefile6
-rw-r--r--drivers/thunderbolt/acpi.c14
-rw-r--r--drivers/thunderbolt/configfs.c61
-rw-r--r--drivers/thunderbolt/ctl.c16
-rw-r--r--drivers/thunderbolt/debugfs.c18
-rw-r--r--drivers/thunderbolt/dma_test.c25
-rw-r--r--drivers/thunderbolt/domain.c37
-rw-r--r--drivers/thunderbolt/eeprom.c2
-rw-r--r--drivers/thunderbolt/icm.c39
-rw-r--r--drivers/thunderbolt/nhi.c606
-rw-r--r--drivers/thunderbolt/nhi.h33
-rw-r--r--drivers/thunderbolt/nhi_ops.c185
-rw-r--r--drivers/thunderbolt/nhi_regs.h3
-rw-r--r--drivers/thunderbolt/path.c37
-rw-r--r--drivers/thunderbolt/pci.c622
-rw-r--r--drivers/thunderbolt/property.c154
-rw-r--r--drivers/thunderbolt/stream.c1698
-rw-r--r--drivers/thunderbolt/switch.c77
-rw-r--r--drivers/thunderbolt/tb.c86
-rw-r--r--drivers/thunderbolt/tb.h21
-rw-r--r--drivers/thunderbolt/tb_regs.h19
-rw-r--r--drivers/thunderbolt/test.c249
-rw-r--r--drivers/thunderbolt/tunnel.c35
-rw-r--r--drivers/thunderbolt/usb4.c35
-rw-r--r--drivers/thunderbolt/usb4_port.c2
-rw-r--r--drivers/thunderbolt/xdomain.c311
-rw-r--r--drivers/usb/Makefile2
-rw-r--r--drivers/usb/atm/ueagle-atm.c58
-rw-r--r--drivers/usb/cdns3/Kconfig112
-rw-r--r--drivers/usb/cdns3/Makefile44
-rw-r--r--drivers/usb/cdns3/cdns3-gadget.c1
-rw-r--r--drivers/usb/cdns3/cdns3-plat.c60
-rw-r--r--drivers/usb/cdns3/cdnsp-gadget.c50
-rw-r--r--drivers/usb/cdns3/cdnsp-gadget.h1
-rw-r--r--drivers/usb/cdns3/cdnsp-mem.c73
-rw-r--r--drivers/usb/cdns3/cdnsp-pci.c253
-rw-r--r--drivers/usb/cdns3/cdnsp-ring.c9
-rw-r--r--drivers/usb/cdns3/core.c48
-rw-r--r--drivers/usb/cdns3/core.h9
-rw-r--r--drivers/usb/cdns3/drd.c45
-rw-r--r--drivers/usb/cdns3/gadget-export.h10
-rw-r--r--drivers/usb/cdns3/host-export.h4
-rw-r--r--drivers/usb/chipidea/core.c11
-rw-r--r--drivers/usb/chipidea/udc.c93
-rw-r--r--drivers/usb/class/cdc-acm.c6
-rw-r--r--drivers/usb/core/hcd.c14
-rw-r--r--drivers/usb/dwc2/hcd_intr.c10
-rw-r--r--drivers/usb/dwc3/core.c14
-rw-r--r--drivers/usb/dwc3/dwc3-google.c5
-rw-r--r--drivers/usb/dwc3/dwc3-octeon.c12
-rw-r--r--drivers/usb/gadget/udc/aspeed_udc.c7
-rw-r--r--drivers/usb/gadget/udc/core.c7
-rw-r--r--drivers/usb/gadget/udc/goku_udc.c3
-rw-r--r--drivers/usb/gadget/udc/pxa25x_udc.c41
-rw-r--r--drivers/usb/gadget/udc/pxa25x_udc.h2
-rw-r--r--drivers/usb/gadget/udc/pxa27x_udc.c37
-rw-r--r--drivers/usb/gadget/udc/pxa27x_udc.h2
-rw-r--r--drivers/usb/host/Kconfig8
-rw-r--r--drivers/usb/host/Makefile1
-rw-r--r--drivers/usb/host/max3421-hcd.c8
-rw-r--r--drivers/usb/host/xhci-dbgcap.c144
-rw-r--r--drivers/usb/host/xhci-dbgcap.h3
-rw-r--r--drivers/usb/host/xhci-hub.c7
-rw-r--r--drivers/usb/host/xhci-mem.c60
-rw-r--r--drivers/usb/host/xhci-mtk.c6
-rw-r--r--drivers/usb/host/xhci-pci-prom21.c137
-rw-r--r--drivers/usb/host/xhci-pci.c11
-rw-r--r--drivers/usb/host/xhci-pci.h3
-rw-r--r--drivers/usb/host/xhci-rcar.c70
-rw-r--r--drivers/usb/host/xhci-ring.c53
-rw-r--r--drivers/usb/host/xhci.c23
-rw-r--r--drivers/usb/host/xhci.h29
-rw-r--r--drivers/usb/misc/Kconfig2
-rw-r--r--drivers/usb/misc/onboard_usb_dev.c1
-rw-r--r--drivers/usb/misc/onboard_usb_dev.h8
-rw-r--r--drivers/usb/misc/usb251xb.c18
-rw-r--r--drivers/usb/misc/usb3503.c2
-rw-r--r--drivers/usb/misc/usb4604.c2
-rw-r--r--drivers/usb/phy/phy-isp1301.c2
-rw-r--r--drivers/usb/serial/ark3116.c1
-rw-r--r--drivers/usb/serial/belkin_sa.c2
-rw-r--r--drivers/usb/serial/cyberjack.c2
-rw-r--r--drivers/usb/serial/cypress_m8.c2
-rw-r--r--drivers/usb/serial/digi_acceleport.c2
-rw-r--r--drivers/usb/serial/empeg.c3
-rw-r--r--drivers/usb/serial/f81232.c3
-rw-r--r--drivers/usb/serial/f81534.c1
-rw-r--r--drivers/usb/serial/ftdi_sio.c1
-rw-r--r--drivers/usb/serial/garmin_gps.c3
-rw-r--r--drivers/usb/serial/generic.c1
-rw-r--r--drivers/usb/serial/io_edgeport.c2
-rw-r--r--drivers/usb/serial/io_ti.c2
-rw-r--r--drivers/usb/serial/ipaq.c3
-rw-r--r--drivers/usb/serial/ipw.c2
-rw-r--r--drivers/usb/serial/ir-usb.c2
-rw-r--r--drivers/usb/serial/iuu_phoenix.c2
-rw-r--r--drivers/usb/serial/keyspan.c2
-rw-r--r--drivers/usb/serial/keyspan_pda.c2
-rw-r--r--drivers/usb/serial/kl5kusb105.c2
-rw-r--r--drivers/usb/serial/kobil_sct.c2
-rw-r--r--drivers/usb/serial/mct_u232.c2
-rw-r--r--drivers/usb/serial/metro-usb.c3
-rw-r--r--drivers/usb/serial/mos7720.c3
-rw-r--r--drivers/usb/serial/mos7840.c1
-rw-r--r--drivers/usb/serial/mxuport.c40
-rw-r--r--drivers/usb/serial/omninet.c2
-rw-r--r--drivers/usb/serial/opticon.c2
-rw-r--r--drivers/usb/serial/option.c1
-rw-r--r--drivers/usb/serial/oti6858.c3
-rw-r--r--drivers/usb/serial/pl2303.c3
-rw-r--r--drivers/usb/serial/qcserial.c1
-rw-r--r--drivers/usb/serial/quatech2.c2
-rw-r--r--drivers/usb/serial/safe_serial.c2
-rw-r--r--drivers/usb/serial/spcp8x5.c2
-rw-r--r--drivers/usb/serial/ssu100.c2
-rw-r--r--drivers/usb/serial/symbolserial.c2
-rw-r--r--drivers/usb/serial/ti_usb_3410_5052.c2
-rw-r--r--drivers/usb/serial/usb-serial.c3
-rw-r--r--drivers/usb/serial/usb_wwan.c1
-rw-r--r--drivers/usb/serial/visor.c4
-rw-r--r--drivers/usb/serial/whiteheat.c4
-rw-r--r--drivers/usb/serial/wishbone-serial.c1
-rw-r--r--drivers/usb/serial/xr_serial.c1
-rw-r--r--drivers/usb/serial/xsens_mt.c1
-rw-r--r--drivers/usb/typec/anx7411.c4
-rw-r--r--drivers/usb/typec/mux/fsa4480.c2
-rw-r--r--drivers/usb/typec/mux/intel_pmc_mux.c19
-rw-r--r--drivers/usb/typec/mux/it5205.c2
-rw-r--r--drivers/usb/typec/mux/nb7vpq904m.c2
-rw-r--r--drivers/usb/typec/mux/pi3usb30532.c2
-rw-r--r--drivers/usb/typec/mux/ps883x.c196
-rw-r--r--drivers/usb/typec/mux/ptn36502.c2
-rw-r--r--drivers/usb/typec/mux/wcd939x-usbss.c2
-rw-r--r--drivers/usb/typec/tcpm/fusb302.c4
-rw-r--r--drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c5
-rw-r--r--drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c12
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c13
-rw-r--r--drivers/usb/typec/tcpm/tcpci_maxim_core.c2
-rw-r--r--drivers/usb/typec/tcpm/tcpci_rt1711h.c8
-rw-r--r--drivers/usb/typec/tipd/core.c2
-rw-r--r--drivers/usb/typec/ucsi/cros_ec_ucsi.c6
-rw-r--r--drivers/usb/typec/ucsi/debugfs.c31
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c60
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h21
-rw-r--r--drivers/usb/typec/ucsi/ucsi_acpi.c22
-rw-r--r--drivers/usb/typec/ucsi/ucsi_ccg.c10
-rw-r--r--drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c55
-rw-r--r--drivers/usb/typec/ucsi/ucsi_stm32g0.c4
-rw-r--r--drivers/usb/typec/ucsi/ucsi_yoga_c630.c6
-rw-r--r--drivers/usb/usbip/Kconfig2
-rw-r--r--drivers/usb/usbip/vhci_sysfs.c52
-rw-r--r--include/linux/platform_data/pxa2xx_udc.h15
-rw-r--r--include/linux/platform_data/usb-xhci-prom21.h22
-rw-r--r--include/linux/thunderbolt.h59
194 files changed, 6914 insertions, 2037 deletions
diff --git a/Documentation/ABI/testing/configfs-thunderbolt_stream b/Documentation/ABI/testing/configfs-thunderbolt_stream
new file mode 100644
index 000000000000..7abc6b73a1e4
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-thunderbolt_stream
@@ -0,0 +1,83 @@
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ Configuration group for a stream Thunderbolt/USB4
+ service. It is possible to create groups even if there
+ is no connection yet to the other host. Once a
+ connection established and there is stream service on
+ the remote side that matches, this configuration is
+ applied to it.
+
+ To find the service name you can run tblist from tbtools [1]:
+
+ # tblist -A
+ ...
+ Domain 0 Route 3 Index 0: stream
+
+ [1] https://github.com/intel/tbtools
+
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ Creates new stream with $name and fills it with the
+ default values. If there is an advertised remote stream
+ with the same name, uses its values as the default.
+
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/index
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ This matches the X in /dev/tbstreamX and allows userspace
+ to map the configfs directory to the corresponding character
+ device.
+
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/in_hopid
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ In HopID used with the read path of the tunnel. Available HopIDs
+ for tunneling start from 8. You can pass also -1 for automatic
+ allocation. The allocated value can be read here. Writing 0 will
+ de-allocate if the stream is not in use.
+
+ To figure out the maximum HopID you can run tbget from
+ tbtools for the lane adapter. For example below we check
+ for lane adapter number 1 (first USB4 port):
+
+ # tbget -r 0 -a 1 -D ADP_CS_5.Max\ Input\ HopID
+ 19
+
+ This allows to use anything between 8 and 19 inclusive.
+
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/out_hopid
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ Out HopID used with the write path of the tunnel. Available HopIDs
+ for tunneling start from 8. You can pass also -1 for automatic
+ allocation. The allocated value can be read here. Writing 0 will
+ de-allocate if the stream is not in use. See @in_hopid
+ for how to figure out the maximum HopID.
+
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/ring_size
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ Size of the TX/RX rings. Can be adjusted between 32 and
+ 4096. The default is 256.
+
+What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/throttling
+Date: Sep 2026
+KernelVersion: v7.2
+Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
+Description:
+ Interrupt throttling rate in ns. Lower values can give
+ better latency. The default is 8192 ns.
diff --git a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
index d10e6de3adb2..98a8376a83d2 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
+++ b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
@@ -22,7 +22,7 @@ Description:
Reading this attribute gives the state of the DbC. It
can be one of the following states: disabled, enabled,
- initialized, connected, configured and stalled.
+ initialized, connected or configured.
What: /sys/bus/pci/drivers/xhci_hcd/.../dbc_idVendor
Date: March 2023
diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst
index 89df26553aa0..91a6cb109988 100644
--- a/Documentation/admin-guide/thunderbolt.rst
+++ b/Documentation/admin-guide/thunderbolt.rst
@@ -373,6 +373,67 @@ port which are named like ``thunderbolt0`` and so on. From this point
you can either use standard userspace tools like ``ip`` to
configure the interface or let your GUI handle it automatically.
+Streaming data directly over Thunderbolt cable
+----------------------------------------------
+In addition to Thunderbolt networking (aka. USB4NET) Linux supports
+streaming data directly over a cable as well (aka. USB4STREAM). This is
+possible through ``thunderbolt-stream`` driver.
+
+Similarly to ``thunderbolt-net`` you load the driver first on one end::
+
+ host1 # modprobe thunderbolt-stream
+
+Then you configure it via ``ConfigFS``::
+
+ host1 # cd /sys/kernel/config/thunderbolt/stream
+ host1 # mkdir -p 0-1.0/data
+ host1 # cd 0-1.0
+ host1 # echo -1 > data/in_hopid
+ host1 # echo -1 > data/out_hopid
+
+This information is automatically announced to the other side via
+XDomain properties so if you have cable connected the other side knows
+that there is a stream named ``data`` available and can configure it for
+you automatically::
+
+ host2 # cd /sys/kernel/config/thunderbolt/stream
+ host2 # mkdir -p 0-3.0/data
+
+Here we used auto-configuration but you can configure it manually too.
+In that case you need to fill ``in_hopid`` and ``out_hopid`` accordingly.
+If you set them to ``-1`` the next available HopID is used which is
+typically what we want.
+
+Once they are configured you can use ``/dev/tbstreamX`` on both sides to
+transfer data::
+
+ host2 # cat /dev/tbstream0
+ host1 # dmesg > /dev/tbstream0
+
+Once you are done with the stream you can remove them::
+
+ host2 # cd /sys/kernel/config/thunderbolt/stream
+ host2 # rmdir -p 0-1.0/data
+ host1 # cd /sys/kernel/config/thunderbolt/stream
+ host1 # rmdir -p 0-3.0/data
+
+Since streams are essentially files you can use any existing application
+that supports ``read(2)`` and ``write(2)`` in some form.
+
+It is possible to have more than one stream and you can have both stream
+and ``thunderbolt-net`` in use simultaneously. For example we can create
+two streams with name ``control`` and ``data`` like this::
+
+ host1 # cd /sys/kernel/config/thunderbolt/stream
+ host1 # mkdir 0-1.0
+ host1 # cd 0-1.0
+ host1 # mkdir control
+ host1 # mkdir data
+
+Then you have ``/dev/tbstream0`` for ``control`` and ``/dev/tbstream1``
+for ``data``. Before you can use them you need to configure them as
+shown above for the one stream case.
+
Forcing power
-------------
Many OEMs include a method that can be used to force the power of a
diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml
index a199e5ba6416..e8082c5c05a2 100644
--- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml
+++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml
@@ -4,29 +4,37 @@
$id: http://devicetree.org/schemas/usb/cdns,usb3.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Cadence USBSS-DRD controller
+title: Cadence USBSS and USBSSP DRD controller
maintainers:
- Pawel Laszczak <pawell@cadence.com>
+description:
+ Cadence USB dual-role controller. Covers USBSS (SuperSpeed, USB 3.0) and
+ USBSSP (SuperSpeed Plus, USB 3.1 gen2x1). Both variants share the same
+ DRD/OTG register interface, so the driver auto-detects the controller
+ version at runtime.
+
properties:
compatible:
- const: cdns,usb3
+ oneOf:
+ - const: cdns,usb3
+ - items:
+ - {}
+ - const: cdns,cdnsp
reg:
- items:
- - description: OTG controller registers
- - description: XHCI Host controller registers
- - description: DEVICE controller registers
+ minItems: 2
+ maxItems: 3
reg-names:
+ minItems: 2
+ maxItems: 3
items:
- - const: otg
- - const: xhci
- - const: dev
+ enum: [ otg, xhci, dev ]
interrupts:
- minItems: 3
+ minItems: 2
items:
- description: XHCI host controller interrupt
- description: Device controller interrupt
@@ -35,7 +43,7 @@ properties:
cleared by xhci core, this interrupt is optional
interrupt-names:
- minItems: 3
+ minItems: 2
items:
- const: host
- const: peripheral
@@ -49,7 +57,7 @@ properties:
cdns3 to type C connector.
maximum-speed:
- enum: [super-speed, high-speed, full-speed]
+ enum: [super-speed-plus, super-speed, high-speed, full-speed]
phys:
minItems: 1
@@ -87,6 +95,47 @@ allOf:
- $ref: usb-drd.yaml#
- $ref: usb-xhci.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: cdns,cdnsp
+ then:
+ properties:
+ reg:
+ items:
+ - description: XHCI Host controller registers
+ - description: DEVICE controller registers
+ reg-names:
+ items:
+ - const: xhci
+ - const: dev
+ interrupts:
+ maxItems: 2
+ interrupt-names:
+ items:
+ - const: host
+ - const: peripheral
+ dr_mode:
+ enum: [host, peripheral]
+ else:
+ properties:
+ reg:
+ items:
+ - description: OTG controller registers
+ - description: XHCI Host controller registers
+ - description: DEVICE controller registers
+ reg-names:
+ items:
+ - const: otg
+ - const: xhci
+ - const: dev
+ interrupts:
+ minItems: 3
+ maxItems: 4
+ interrupt-names:
+ minItems: 3
+
unevaluatedProperties: false
examples:
diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml
index fec04702f530..620b564914d4 100644
--- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml
+++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.yaml
@@ -45,11 +45,11 @@ properties:
clocks:
minItems: 1
- maxItems: 2
+ maxItems: 3
clock-names:
minItems: 1
- maxItems: 2
+ maxItems: 3
operating-points-v2:
description: A phandle to the OPP table containing the performance states.
@@ -92,6 +92,27 @@ allOf:
- $ref: chipidea,usb2-common.yaml#
- $ref: usb-hcd.yaml#
- $ref: usb-drd.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,ci-hdrc
+ then:
+ properties:
+ clocks:
+ minItems: 2
+ clock-names:
+ minItems: 2
+ items:
+ - const: iface
+ - const: core
+ - const: fs
+ else:
+ properties:
+ clocks:
+ maxItems: 2
+ clock-names:
+ maxItems: 2
unevaluatedProperties: false
diff --git a/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml b/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml
index 9a94b2a74a1e..e8b8c03f87a0 100644
--- a/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml
+++ b/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml
@@ -15,6 +15,7 @@ properties:
- usb5e3,608
- usb5e3,610
- usb5e3,620
+ - usb5e3,625
- usb5e3,626
reg: true
@@ -26,6 +27,10 @@ properties:
description:
The regulator that provides 3.3V or 5.0V core power to the hub.
+ vdd12-supply:
+ description:
+ The regulator that provides 1.2V power to the hub.
+
peer-hub: true
ports:
@@ -56,6 +61,7 @@ allOf:
properties:
peer-hub: false
vdd-supply: false
+ vdd12-supply: false
- if:
properties:
@@ -68,6 +74,18 @@ allOf:
properties:
peer-hub: true
vdd-supply: true
+ vdd12-supply: false
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - usb5e3,625
+ then:
+ properties:
+ peer-hub: true
+ vdd-supply: true
unevaluatedProperties: false
diff --git a/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml
index 6d3fa2bc9cee..ba790c4488b7 100644
--- a/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml
+++ b/Documentation/devicetree/bindings/usb/qcom,pmic-typec.yaml
@@ -79,22 +79,17 @@ properties:
- const: fr-swap
vdd-vbus-supply:
- description: VBUS power supply.
+ deprecated: true
+ description: use connector/vbus-supply instead.
vdd-pdphy-supply:
description: VDD regulator supply to the PDPHY.
- port:
- $ref: /schemas/graph.yaml#/properties/port
- description:
- Contains a port which produces data-role switching messages.
-
required:
- compatible
- reg
- interrupts
- interrupt-names
- - vdd-vbus-supply
allOf:
- if:
diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt b/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt
deleted file mode 100644
index 6f8115db2ea9..000000000000
--- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-Richtek RT1711H TypeC PD Controller.
-
-Required properties:
- - compatible : Must be "richtek,rt1711h".
- - reg : Must be 0x4e, it's slave address of RT1711H.
- - interrupts : <a b> where a is the interrupt number and b represents an
- encoding of the sense and level information for the interrupt.
-
-Required sub-node:
-- connector: The "usb-c-connector" attached to the tcpci chip, the bindings
- of connector node are specified in
- Documentation/devicetree/bindings/connector/usb-connector.yaml
-
-Example :
-rt1711h@4e {
- compatible = "richtek,rt1711h";
- reg = <0x4e>;
- interrupt-parent = <&gpio26>;
- interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
-
- usb_con: connector {
- compatible = "usb-c-connector";
- label = "USB-C";
- data-role = "dual";
- power-role = "dual";
- try-power-role = "sink";
- source-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)>;
- sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)
- PDO_VAR(5000, 12000, 2000)>;
- op-sink-microwatt = <10000000>;
-
- ports {
- #address-cells = <1>;
- #size-cells = <0>;
-
- port@1 {
- reg = <1>;
- usb_con_ss: endpoint {
- remote-endpoint = <&usb3_data_ss>;
- };
- };
- };
- };
-};
diff --git a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml
index fd1b13c0ed6b..0554dbc4b854 100644
--- a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml
+++ b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml
@@ -26,6 +26,7 @@ select:
contains:
enum:
- rockchip,rk3328-dwc3
+ - rockchip,rk3528-dwc3
- rockchip,rk3562-dwc3
- rockchip,rk3568-dwc3
- rockchip,rk3576-dwc3
@@ -38,6 +39,7 @@ properties:
items:
- enum:
- rockchip,rk3328-dwc3
+ - rockchip,rk3528-dwc3
- rockchip,rk3562-dwc3
- rockchip,rk3568-dwc3
- rockchip,rk3576-dwc3
@@ -135,6 +137,7 @@ allOf:
compatible:
contains:
enum:
+ - rockchip,rk3528-dwc3
- rockchip,rk3568-dwc3
- rockchip,rk3576-dwc3
then:
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 4aa910569c31..29130df44d12 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -225,6 +225,7 @@ Hardware Monitoring Kernel Drivers
pmbus
powerz
powr1220
+ prom21-xhci
pt5161l
pxe1610
pwm-fan
diff --git a/Documentation/hwmon/prom21-xhci.rst b/Documentation/hwmon/prom21-xhci.rst
new file mode 100644
index 000000000000..7984fb187bd8
--- /dev/null
+++ b/Documentation/hwmon/prom21-xhci.rst
@@ -0,0 +1,101 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver prom21-xhci
+=========================
+
+Supported chips:
+
+ * AMD Promontory 21 (PROM21) xHCI USB host controller
+
+ Prefix: 'prom21_xhci'
+
+ PCI IDs: 1022:43fc, 1022:43fd
+
+Author:
+
+ - Jihong Min <hurryman2212@gmail.com>
+
+Description
+-----------
+
+This driver exposes the temperature sensor in AMD PROM21 xHCI controllers.
+
+The driver binds to an auxiliary device created by the xHCI PCI driver for
+supported controllers. The sensor value is accessed through a vendor-specific
+index/data register pair in the controller's PCI MMIO BAR.
+The auxiliary device is created by the ``xhci-pci-prom21`` PCI glue driver.
+USB host operation is otherwise delegated to the common ``xhci-pci`` code.
+
+PROM21 is an AMD chipset IP used in single-chip or daisy-chained configurations
+to build AMD 6xx/8xx series chipsets. Since the xHCI controllers are
+integrated in PROM21, this temperature can also be used as a monitor for a
+temperature close to the AMD chipset temperature.
+
+Register access
+---------------
+
+The temperature value is read through a vendor-specific index/data register
+pair in the xHCI PCI MMIO BAR. The driver uses the following byte offsets from
+the MMIO BAR base:
+
+======================= =====================================================
+0x3000 Vendor index register
+0x3008 Vendor data register
+======================= =====================================================
+
+The driver saves the current vendor index register value, writes the
+temperature selector ``0x0001e520`` to the vendor index register, reads the
+vendor data register, and restores the previous vendor index value before
+returning. The raw temperature value is the low 8 bits of the vendor data
+register value.
+
+The hwmon core serializes this driver's callbacks, and the driver restores the
+previous index value after each read. This does not provide synchronization
+with firmware, SMM, ACPI AML, or any other user outside this driver.
+
+No public AMD reference is available for the register pair or the raw value.
+The register pair was identified on an X870E system with two PROM21 xHCI
+controllers. One controller was passed through to a Windows VM, and the same
+controller's PCI MMIO BAR was observed from the Linux host while HWiNFO64 was
+reporting the PROM21 xHCI temperature. In the test environment, the reported
+temperature was very stable at idle and the displayed sensor resolution was
+low, which made it possible to look for a consistently repeating MMIO response
+for the same reported temperature. During observation, offset 0x3000 repeatedly
+contained selector ``0x0001e520``. Writing the same selector to offset 0x3000
+from Linux and then reading offset 0x3008 reproduced the same raw value, so the
+offsets are treated as a vendor index/data register pair.
+
+The conversion formula was empirically inferred by matching observed raw
+8-bit values against HWiNFO64's reported PROM21 xHCI temperature for the same
+controller. The observed mapping is:
+
+ temp[C] = raw * 0.9066 - 78.624
+
+Runtime PM
+----------
+
+The driver does not wake the xHCI PCI device for hwmon reads. It reads the
+temperature only when the parent device is already active. A read from a
+suspended device returns ``-ENODATA``. After a successful read, the driver
+drops its active-only runtime PM reference and lets the PM core re-evaluate the
+idle state.
+
+Sysfs entries
+-------------
+
+======================= =====================================================
+temp1_input Temperature in millidegrees Celsius
+======================= =====================================================
+
+The hwmon device name is ``prom21_xhci``. The sysfs path depends on the hwmon
+device number assigned by the kernel. Userspace can locate the device by
+matching the ``name`` attribute:
+
+.. code-block:: sh
+
+ for hwmon in /sys/class/hwmon/hwmon*; do
+ [ "$(cat "$hwmon/name")" = "prom21_xhci" ] || continue
+ cat "$hwmon/temp1_input"
+ done
+
+If the raw register value is invalid, ``temp1_input`` returns ``-ENODATA``.
diff --git a/Documentation/translations/zh_CN/subsystem-apis.rst b/Documentation/translations/zh_CN/subsystem-apis.rst
index 830217140fb6..b52e1feb0167 100644
--- a/Documentation/translations/zh_CN/subsystem-apis.rst
+++ b/Documentation/translations/zh_CN/subsystem-apis.rst
@@ -90,6 +90,7 @@ TODOList:
security/index
PCI/index
peci/index
+ usb/index
TODOList:
@@ -104,6 +105,5 @@ TODOList:
* accel/index
* crypto/index
* bpf/index
-* usb/index
* misc-devices/index
* wmi/index
diff --git a/Documentation/translations/zh_CN/usb/CREDITS b/Documentation/translations/zh_CN/usb/CREDITS
new file mode 100644
index 000000000000..c133b1a5daff
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/CREDITS
@@ -0,0 +1,163 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/CREDITS
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+简易 Linux USB 驱动的致谢名单:
+
+以下人员都为 Linux USB 驱动代码作出了贡献
+(按姓氏字母顺序排列)。我相信这份名单本应
+更长一些,但确实不容易维护。
+如需将自己加入名单,请提交补丁。
+
+ Georg Acher <acher@informatik.tu-muenchen.de>
+ David Brownell <dbrownell@users.sourceforge.net>
+ Alan Cox <alan@lxorguk.ukuu.org.uk>
+ Randy Dunlap <randy.dunlap@intel.com>
+ Johannes Erdfelt <johannes@erdfelt.com>
+ Deti Fliegl <deti@fliegl.de>
+ ham <ham@unsuave.com>
+ Bradley M Keryan <keryan@andrew.cmu.edu>
+ Greg Kroah-Hartman <greg@kroah.com>
+ Pavel Machek <pavel@suse.cz>
+ Paul Mackerras <paulus@cs.anu.edu.au>
+ Petko Manlolov <petkan@dce.bg>
+ David E. Nelson <dnelson@jump.net>
+ Vojtech Pavlik <vojtech@suse.cz>
+ Bill Ryder <bryder@sgi.com>
+ Thomas Sailer <sailer@ife.ee.ethz.ch>
+ Gregory P. Smith <greg@electricrain.com>
+ Linus Torvalds <torvalds@linux-foundation.org>
+ Roman Weissgaerber <weissg@vienna.at>
+ <Kazuki.Yasumatsu@fujixerox.co.jp>
+
+特别感谢:
+
+ Inaky Perez Gonzalez <inaky@peloncho.fis.ucm.es>
+ 感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd
+ 驱动中的大部分代码。我们从那项工作中学到了很多。
+
+ NetBSD 和 FreeBSD 的 USB 开发者们
+ 感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。
+
+附加感谢:
+ 还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助
+ (摘自 Inaky 驱动原始的 THANKS 文件):
+
+ 以下公司曾帮助我们开发 Linux USB / UUSBD:
+
+ - 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面为我
+ 提供支持。没想到能得到这么大的帮助。
+
+ - USAR Systems 向我们提供了他们出色的 USB 评估套件,
+ 使我们能够测试 Linux USB 驱动对最新 USB 规范的符合性。
+ USAR Systems 认识到保持开放操作系统与时俱进的重要性,
+ 并以硬件支持这个项目。感谢!
+
+ - 感谢英特尔提供的宝贵帮助。
+
+ - 我们与 Cherry 合作,使 Linux 成为首个内置 USB 支持的操作系统。
+ Cherry 是全球最大的键盘制造商之一。
+
+ - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI-to-USB
+ 控制卡,用于测试 OHCI 实现。
+
+ - 由于他们对我们的支持,Keytronic 可以放心,
+ 他们的键盘能卖给至少 300 万 Linux 用户中的一部分。
+
+ - ing büro h doran [http://www.ibhdoran.com]!
+ 在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的
+ (我自己做的那个相当糟糕 :))。现在我知道该去哪里买漂亮的 USB
+ 配件了!
+
+ - Genius Germany 捐赠了一只 USB 鼠标,用于测试鼠标启动协议;
+ 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢!
+
+ - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB 驱动。
+ AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux 在内的
+ 所有操作系统平台开放。
+
+ - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,
+ 使我们能够测试批量传输代码。
+
+ - 感谢 Logitech 捐赠了一只三轴 USB 鼠标。
+
+ Logitech 负责设计、制造并销售各种人机接口设备,
+ 在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的
+ 控制设备方面拥有悠久历史和丰富经验。
+
+ 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、
+ 摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户
+ 能在自己喜欢的操作系统上获得支持,并让所有 Linux 用户都能使用
+ Logitech 以及其他 USB 硬件。
+
+ Logitech 也是 1999 年 2 月 11 日维也纳 Linux 大会的官方赞助商,
+ 我们将在会上展示 Linux USB 工作的最新进展。
+
+ - 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI 内部实现中
+ 那些不为人知的角落。
+
+ - 感谢 Entrega 为开发工作提供 PCI 转 USB 卡、集线器和转换器产品。
+
+ - 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,
+ 让这个驱动得以写成。
+
+ - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,
+ 以及驱动开发过程中的宝贵建议。
+
+ 另外还要感谢以下个人(嘿,顺序不分先后 :))
+
+ - Oren Tirosh <orenti@hishome.net>,
+ 他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。
+
+ - Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>,
+ 指出了致命 bug,并给出了宝贵建议。
+
+ - Edmund Humemberger <ed@atnet.at>,他在公共关系与项目管理方面
+ 为 Linux-USB 项目付出了巨大的努力。
+
+ - Alberto Menegazzi <flash@flash.iol.it> 正在着手编写 UUSBD 文档,加油!
+
+ - Ric Klaren <ia_ric@cs.utwente.nl> 编写了很好的入门文档,
+ 与 Alberto 的作品形成良性竞争:)。
+
+ - Christian Groessler <cpg@aladdin.de>,感谢他在那些棘手细节上的帮助。
+
+ - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,
+ 并提供了大量的改进意见。
+
+ - Fernando Herrera <fherrera@eurielec.etsit.upm.es>
+ 负责撰写、维护并不断补充那份期待已久、独一无二又精彩的
+ UUSBD FAQ!太棒了!
+
+ - Rasca Gmelch <thron@gmx.de> 重新启用了 raw 驱动,
+ 指出了一些错误,并启动了 uusbd-utils 软件包。
+
+ - Peter Dettori <dettori@ozy.dec.com>,像疯了一样挖掘 bug,
+ 还提出了很多很酷的建议,太棒了!
+
+ - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、
+ MIT X 联盟、TeX 社区等等,谢谢你们!
+
+ - 特别感谢 Richard Stallman 创造了 Emacs!
+
+ - 感谢 linux-usb 邮件列表的所有成员,读了那么多邮件——不开玩笑了,
+ 感谢你们提出的所有建议!
+
+ - 感谢 USB Implementers Forum 成员们的帮助与支持。
+
+ - Nathan Myers <ncm@cantrip.org>,感谢他的建议!
+ (希望你喜欢 Cibeles 的派对。)
+
+ - 感谢 Linus Torvalds 创建、开发并管理 Linux。
+
+ - Mike Smith、Craig Keithley、Thierry Giron 和 Janet Schank
+ 感谢他们让我认识到标准 USB 集线器其实也没那么“标准”,
+ 这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。
diff --git a/Documentation/translations/zh_CN/usb/acm.rst b/Documentation/translations/zh_CN/usb/acm.rst
new file mode 100644
index 000000000000..51d6eb8f5660
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/acm.rst
@@ -0,0 +1,147 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/acm.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+====================
+Linux ACM 驱动 v0.16
+====================
+
+版权所有 (c) 1999 Vojtech Pavlik <vojtech@suse.cz>
+
+由 SuSE 赞助
+
+0. 免责声明
+~~~~~~~~~~~
+本程序是自由软件;你可以在自由软件基金会发布的
+GNU 通用公共许可证第 2 版,或者(按你的选择)
+任何后续版本的条款下重新发布和/或修改它。
+
+发布本程序是希望它能发挥作用,但它不附带任何担保;
+甚至不包括对适销性或特定用途适用性的默示担保。
+详情见 GNU 通用公共许可证。
+
+你应该已经随本程序收到了 GNU 通用公共许可证的副本;
+如果没有,请致信:Free Software Foundation, Inc., 59
+Temple Place, Suite 330, Boston, MA 02111-1307 USA。
+
+如需联系作者,可发送电子邮件至 vojtech@suse.cz,
+或邮寄至:
+Vojtech Pavlik, Ucitelska 1576, Prague 8,
+182 00, Czech Republic。
+
+为方便起见,软件包中已附带 GNU 通用公共许可证
+第 2 版:见 COPYING 文件。
+
+1. 使用方法
+~~~~~~~~~~~
+``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB
+通信设备类抽象控制模型(USB CDC ACM)规范的
+USB 调制解调器和 USB ISDN 终端适配器。
+
+许多调制解调器支持此驱动,以下是我所知道的一些型号:
+
+ - 3Com OfficeConnect 56k
+ - 3Com Voice FaxModem Pro
+ - 3Com Sportster
+ - MultiTech MultiModem 56k
+ - Zoom 2986L FaxModem
+ - Compaq 56k FaxModem
+ - ELSA Microlink 56k
+
+我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用:
+
+ - 3Com USR ISDN Pro TA
+
+一些手机也可以通过 USB 连接。
+我知道以下机型可以正常工作:
+
+ - SonyEricsson K800i
+
+遗憾的是,许多调制解调器和大多数 ISDN TA
+都使用专有接口,因此无法与此驱动配合工作。
+购买前请先确认设备是否符合 ACM 规范。
+
+要使用这些调制解调器,需要加载以下模块::
+
+ usbcore.ko
+ uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko
+ cdc-acm.ko
+
+之后就应该可以访问这些调制解调器了。
+应当可以使用 ``minicom``、``ppp`` 和 ``mgetty``
+与它们通信。
+
+2. 验证驱动是否正常工作
+~~~~~~~~~~~~~~~~~~~~~~~
+
+第一步是检查 ``/sys/kernel/debug/usb/devices``,
+其内容应该类似如下::
+
+ T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
+ B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0
+ D: Ver= 1.00 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
+ P: Vendor=0000 ProdID=0000 Rev= 0.00
+ S: Product=USB UHCI Root Hub
+ S: SerialNumber=6800
+ C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr= 0mA
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
+ E: Ad=81(I) Atr=03(Int.) MxPS= 8 Ivl=255ms
+ T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
+ D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
+ P: Vendor=04c1 ProdID=008f Rev= 2.07
+ S: Manufacturer=3Com Inc.
+ S: Product=3Com U.S. Robotics Pro ISDN TA
+ S: SerialNumber=UFT53A49BVT7
+ C: #Ifs= 1 Cfg#= 1 Atr=60 MxPwr= 0mA
+ I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=acm
+ E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+ E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+ E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms
+ C:* #Ifs= 2 Cfg#= 2 Atr=60 MxPwr= 0mA
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
+ E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms
+ I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
+ E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+ E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+
+这三行的存在很关键(以及 ``Cls=`` 字段里出现的
+``comm`` 和 ``data`` 类);它说明这是一个 ACM
+设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。
+如果只看到 ``Cls=ff(vend.)``,那就无能为力了:
+这说明你手上的设备使用的是厂商专有接口::
+
+ D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
+ I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
+
+在系统日志中应该可以看到::
+
+ usb.c: USB new device connect, assigned device number 2
+ usb.c: kmalloc IF c7691fa0, numif 1
+ usb.c: kmalloc IF c7b5f3e0, numif 2
+ usb.c: skipped 4 class/vendor specific interface descriptors
+ usb.c: new device strings: Mfr=1, Product=2, SerialNumber=3
+ usb.c: USB device number 2 default language ID 0x409
+ Manufacturer: 3Com Inc.
+ Product: 3Com U.S. Robotics Pro ISDN TA
+ SerialNumber: UFT53A49BVT7
+ acm.c: probing config 1
+ acm.c: probing config 2
+ ttyACM0: USB ACM device
+ acm.c: acm_control_msg: rq: 0x22 val: 0x0 len: 0x0 result: 0
+ acm.c: acm_control_msg: rq: 0x20 val: 0x0 len: 0x7 result: 7
+ usb.c: acm driver claimed interface c7b5f3e0
+ usb.c: acm driver claimed interface c7b5f3f8
+ usb.c: acm driver claimed interface c7691fa0
+
+如果以上都正常,请启动 ``minicom``,
+把它配置为连接 ``ttyACM`` 设备,然后
+尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。
diff --git a/Documentation/translations/zh_CN/usb/authorization.rst b/Documentation/translations/zh_CN/usb/authorization.rst
new file mode 100644
index 000000000000..2aa311f6b967
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/authorization.rst
@@ -0,0 +1,139 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/authorization.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+=============================
+授权或禁止 USB 设备连接到系统
+=============================
+
+版权 (C) 2007 Inaky Perez-Gonzalez
+<inaky@linux.intel.com> 英特尔公司
+
+此功能允许你控制 USB 设备是否可以在系统中使用。
+借助它,你可以完全通过用户空间实现对 USB 设备的锁定。
+
+目前,当插入一个 USB 设备时,系统会对其进行配置,
+其接口会立即向用户开放。
+有了这项改动,只有在 root 授权设备完成配置后,
+设备才可被使用。
+
+
+用法
+====
+
+授权设备接入::
+
+ $ echo 1 > /sys/bus/usb/devices/DEVICE/authorized
+
+取消对设备的授权::
+
+ $ echo 0 > /sys/bus/usb/devices/DEVICE/authorized
+
+将新连接到 ``hostX`` 的设备默认设为未授权(即锁定)::
+
+ $ echo 0 > /sys/bus/usb/devices/usbX/authorized_default
+
+解除锁定::
+
+ $ echo 1 > /sys/bus/usb/devices/usbX/authorized_default
+
+默认情况下,所有 USB 设备都是授权的。
+向 ``authorized_default`` 属性写入 ``2`` 会使内核
+默认只授权连接到内部 USB 端口的设备。
+
+系统锁定示例(比较粗糙)
+------------------------
+
+假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入
+(例如某台带有外露 USB 端口的自助服务终端)::
+
+ 启动系统
+ rc.local ->
+
+ for host in /sys/bus/usb/devices/usb*
+ do
+ echo 0 > $host/authorized_default
+ done
+
+给 udev 挂一个脚本,用于处理新插入的 USB 设备::
+
+ if device_is_my_type $DEV
+ then
+ echo 1 > $device_path/authorized
+ done
+
+
+``device_is_my_type()`` 才是锁定方案真正见功夫的
+地方。仅仅检查 class、type 和 protocol 是否匹配
+某个值,是你能做出的最糟糕的安全验证之一;
+对想绕过它的人来说,这反而是最容易利用的方案。
+如果你需要真正安全的办法,那就该使用加密、
+证书认证之类的机制。把 USB 存储设备当作
+“钥匙”的一个简单例子可以是::
+
+ function device_is_my_type()
+ {
+ echo 1 > authorized # 暂时授权它
+ # FIXME: 确保没人能挂载它
+ mount DEVICENODE /mntpoint
+ sum=$(md5sum /mntpoint/.signature)
+ if [ $sum = $(cat /etc/lockdown/keysum) ]
+ then
+ echo "We are good, connected"
+ umount /mntpoint
+ # 再做一些额外处理,让其他人也能使用它
+ else
+ echo 0 > authorized
+ fi
+ }
+
+
+当然,这个例子很粗糙;真正要做的话,
+你会想用基于 PKI 的证书校验,这样就不必依赖
+共享密钥之类的东西。不过你应该已经明白意思了。
+任何拿到设备仿真工具包的人都能伪造描述符和设备信息。
+别信这个。
+
+接口授权
+--------
+
+也有类似的方法用于允许或拒绝特定 USB 接口。
+这使得你可以只阻止某个 USB 设备中的部分接口。
+
+授权接口::
+
+ $ echo 1 > /sys/bus/usb/devices/INTERFACE/authorized
+
+取消接口授权::
+
+ $ echo 0 > /sys/bus/usb/devices/INTERFACE/authorized
+
+也可以更改特定 USB 总线上新接口的默认授权值。
+
+默认允许接口::
+
+ $ echo 1 > /sys/bus/usb/devices/usbX/interface_authorized_default
+
+默认拒绝接口::
+
+ $ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default
+
+默认情况下,
+``interface_authorized_default`` 位为 ``1``,
+因此所有接口默认都处于已授权状态。
+
+注意:
+ 如果把一个先前未授权的接口改为已授权,
+ 则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe``
+ 来手动触发驱动探测。
+
+对于需要多个接口的驱动程序,应先授权所有必需接口,
+然后再触发驱动探测。这样做可以避免副作用。
diff --git a/Documentation/translations/zh_CN/usb/chipidea.rst b/Documentation/translations/zh_CN/usb/chipidea.rst
new file mode 100644
index 000000000000..ea0dc3043189
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/chipidea.rst
@@ -0,0 +1,150 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/chipidea.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+=============================
+ChipIdea 高速双角色控制器驱动
+=============================
+
+1. 如何测试 OTG FSM(HNP 和 SRP)
+---------------------------------
+
+下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,
+说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。
+
+1.1 如何使能 OTG FSM
+--------------------
+
+1.1.1 在 ``menuconfig`` 中选择 ``CONFIG_USB_OTG_FSM``,并重新编译内核
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+重新构建内核镜像和模块。如果想查看 OTG FSM 的
+一些内部变量,可以挂载 ``debugfs``;其中有两个文件
+可以显示 OTG FSM 变量以及部分控制器寄存器的值::
+
+ cat /sys/kernel/debug/ci_hdrc.0/otg
+ cat /sys/kernel/debug/ci_hdrc.0/registers
+
+1.1.2 在控制器节点对应的 ``dts`` 文件中添加以下条目
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+ otg-rev = <0x0200>;
+ adp-disable;
+
+1.2 测试步骤
+------------
+
+1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,
+ 并加载 gadget 类驱动(例如 ``g_mass_storage``)。
+
+2) 用 USB 线连接两块开发板:
+ 一端是 micro A 插头,另一端是 micro B 插头。
+
+ 插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。
+
+3) 角色切换
+
+ 在 B 设备上执行::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ B 设备应接管主机角色并枚举 A 设备。
+
+4) A 设备切回主机角色
+
+ 在 B 设备上执行::
+
+ echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ 或者,通过引入 HNP 轮询,B 端主机可以知道
+ A 端外设希望切换为主机角色,因此这次角色切换
+ 也可以通过 A 端外设响应 B 端主机的轮询,
+ 在 A 侧触发。
+ 这可以通过在 A 设备上执行下面的命令来完成::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
+
+ A 设备应切回主机角色并枚举 B 设备。
+
+5) 拔掉 B 设备(拔掉 micro B 插头),
+ 并在 10 秒内重新插入;
+ A 设备应重新枚举 B 设备。
+
+6) 拔掉 B 设备(拔掉 micro B 插头),
+ 并在 10 秒后重新插入;
+ A 设备不应重新枚举 B 设备。
+
+ 如果 A 设备希望使用总线:
+
+ 在 A 设备上执行::
+
+ echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
+
+ 如果 B 设备希望使用总线:
+
+ 在 B 设备上执行::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+7) A 设备关闭总线供电
+
+ 在 A 设备上执行::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
+
+ A 设备应断开与 B 设备的连接,并关闭总线供电。
+
+8) B 设备发出 SRP 数据脉冲
+
+ 在 B 设备上执行::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ A 设备应恢复 USB 总线并枚举 B 设备。
+
+1.3 参考文档
+------------
+《On-The-Go and Embedded Host Supplement
+to the USB Revision 2.0 Specification
+July 27, 2012 Revision 2.0 version 1.1a》
+
+2. 如何将 USB 用作系统唤醒源
+----------------------------
+下面是在 i.MX6 平台上把 USB 用作系统唤醒源的示例。
+
+2.1 使能核心控制器的唤醒功能::
+
+ echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup
+
+2.2 使能 glue 层的唤醒功能::
+
+ echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup
+
+2.3 使能 PHY 的唤醒功能(可选)::
+
+ echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup
+
+2.4 使能根集线器的唤醒功能::
+
+ echo enabled > /sys/bus/usb/devices/usb1/power/wakeup
+
+2.5 使能相关设备的唤醒功能::
+
+ echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
+
+如果系统只有一个 USB 端口,
+而你希望在该端口上启用 USB 唤醒功能,
+可以使用下面的脚本::
+
+ for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done;
diff --git a/Documentation/translations/zh_CN/usb/dwc3.rst b/Documentation/translations/zh_CN/usb/dwc3.rst
new file mode 100644
index 000000000000..3468ce50c5ba
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/dwc3.rst
@@ -0,0 +1,63 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/dwc3.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+=========
+DWC3 驱动
+=========
+
+
+待办
+~~~~
+
+阅读时如果想顺手认领点任务,可以从下面挑一项 :)
+
+- 将中断处理程序改为每个端点各自使用线程化 IRQ
+
+ 事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。
+ 当前代码会一直自旋等待命令完成,这种设计并不好。
+
+ 实现思路:
+
+ - DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。
+ 中断号在探测(``probe``)阶段分配,并归属于该设备。
+ 如果硬件通过 ``MSI`` 为每个端点提供独立中断,
+ 那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。
+
+ - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,
+ 在调用 ``usb_ep_disable()`` 时释放中断资源。
+ 最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。
+ - ``dwc3_send_gadget_ep_cmd()`` 将在 ``wait_for_completion_timeout()``
+ 中休眠,直到命令完成。
+ - 中断处理程序分为以下几个部分:
+
+ - 设备级主中断处理程序
+ 遍历每个事件,并对其调用 ``generic_handle_irq()``。
+ 从 ``generic_handle_irq()`` 返回后,确认事件计数器,使中断最终消失。
+
+ - 设备级线程化处理程序
+ 无。
+
+ - 端点中断的主处理程序
+ 读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。
+ 事件保存在每个端点的数据结构中。
+ 还要注意,一旦把某项工作交给线程处理,
+ 就不要再在主处理程序里处理它,
+ 以免出现优先级反转之类的问题。
+
+ - 端点中断的线程化处理程序
+ 处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。
+
+ 延迟:
+
+ 不应增加延迟,因为中断线程具有较高优先级,
+ 会在普通用户态任务之前运行
+ (除非用户更改了调度优先级)。
diff --git a/Documentation/translations/zh_CN/usb/ehci.rst b/Documentation/translations/zh_CN/usb/ehci.rst
new file mode 100644
index 000000000000..e05e493a30d3
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/ehci.rst
@@ -0,0 +1,261 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/ehci.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+=========
+EHCI 驱动
+=========
+
+2002年12月27日
+
+EHCI 驱动用于通过支持 USB 2.0 的主机控制器
+硬件与高速 USB 2.0 设备通信。USB 2.0 兼容
+USB 1.1 标准,它定义了三种传输速率:
+
+ - “高速”(High Speed)480 Mbit/sec(60 MByte/sec)
+ - “全速”(Full Speed)12 Mbit/sec(1.5 MByte/sec)
+ - “低速”(Low Speed)1.5 Mbit/sec
+
+USB 1.1 仅支持全速与低速。
+高速设备可以在 USB 1.1 系统上使用,
+但速度会降到 USB 1.1 的速率。
+
+USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
+插入 EHCI 控制器时,会被交由 USB 1.1 的伴随
+(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
+
+当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过
+集线器中的事务转换器(Transaction Translator,TT)
+与 EHCI 控制器交互,该转换器将低速或全速事务转换为
+高速分割事务,从而避免浪费传输带宽。
+
+截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行
+(按字母顺序):Intel、NEC、Philips 和 VIA。
+其他供应商的 EHCI 实现正在陆续问世;
+预计该驱动在这些实现上也可正常运行。
+
+自 2001 年年中起,usb-storage 设备就已可用
+(在 2.4 版该驱动上速度相当不错),
+集线器则直到 2001 年底才开始可用,而其他类型的高速设备
+似乎要等到更多系统内置 USB 2.0 后才会出现。
+这类新系统从 2002 年初开始上市,
+并在 2002 年下半年变得更加常见。
+
+注意,USB 2.0 支持并不只是 EHCI 本身。
+它还需要对 Linux-USB 核心 API 作出其他修改,
+包括 hub 驱动;不过这些修改并不需要真正改变
+暴露给 USB 设备驱动的基本 ``usbcore`` API。
+
+- David Brownell
+ <dbrownell@users.sourceforge.net>
+
+
+功能
+====
+
+该驱动会定期在 x86 硬件上进行测试,
+也已在 PPC 硬件上使用,因此大小端问题应当已经解决。
+因此可以认为,它已经处理好了所有必要的 PCI 细节,
+所以即便在 DMA 映射有些特殊的系统上,
+I/O 也应能正常运行。
+
+传输类型
+--------
+
+截至本文撰写时,该驱动应当已经能够很好地处理
+所有控制传输、批量传输和中断传输,
+包括通过 USB 2.0 集线器中的事务转换器
+与 USB 1.1 设备通信;但仍可能存在 bug。
+
+高速等时(ISO)传输支持也已可用,但截至本文撰写时,
+还没有 Linux 驱动使用这项支持。
+
+目前尚不支持通过事务转换器实现全速等时传输。
+需要注意,ISO 传输的 split transaction 支持
+与高速 ISO 传输几乎无法共用代码,
+因为 EHCI 用不同的数据结构表示它们。
+因此,目前大多数 USB 音频和视频设备
+还不能通过高速总线连接使用。
+
+驱动行为
+--------
+
+所有类型的传输都可以排队。
+这意味着来自一个接口驱动的控制传输
+(或通过 usbfs 发出的控制传输)不会干扰
+另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期,
+而不必担心中断处理开销导致的数据丢失。
+
+
+EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
+该驱动不需要了解那些驱动的任何细节;
+一个原本就能正常工作的 OHCI 或 UHCI 驱动,
+并不会因为 EHCI 驱动也存在而需要更改。
+
+电源管理方面还有一些问题;
+当前挂起/恢复的行为还不完全正确。
+
+此外,在调度周期性事务
+(中断和等时传输)时还采取了一些简化处理。
+这些简化会限制可调度的周期性事务数量,
+并且无法使用小于一帧的轮询间隔。
+
+使用方式
+========
+
+假设有一个 EHCI 控制器(位于 PCI 卡或主板上),
+并且已将此驱动编译为模块,可这样加载::
+
+ # modprobe ehci-hcd
+
+卸载方式::
+
+ # rmmod ehci-hcd
+
+还应加载一个伴随控制器驱动,
+例如 ``ohci-hcd`` 或 ``uhci-hcd``。
+如果 EHCI 驱动出现任何问题,只需卸载它的模块,
+随后该伴随控制器驱动就会接手
+此前由 EHCI 驱动处理的所有设备
+(但速度会降低)。
+
+模块参数(传给 ``modprobe``)包括:
+
+ log2_irq_thresh(默认值 0):
+ 默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧
+ (125 微秒)。最大值 6 表示 2^6 = 64 个微帧。
+ 该值控制 EHCI 控制器发出中断的频率。
+
+如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,
+则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件:
+
+ ``async``
+ 转储异步调度,用于控制传输和批量传输。它会显示每个活动的 ``qh``
+ 以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。
+ (可以在 ``usb-storage`` 做磁盘 I/O 时看它;顺便观察请求队列!)
+
+ ``periodic``
+ 转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。
+
+ ``registers``
+ 显示控制器寄存器状态。
+
+这些文件的内容有助于定位驱动问题。
+
+
+设备驱动通常不需要关心自己是否运行在 EHCI 之上,
+但它们可能想检查
+``usb_device->speed == USB_SPEED_HIGH``。
+高速设备能做到全速(或低速)设备做不到的事,
+例如高带宽的周期性传输(中断或 ISO 传输)。
+另外,设备描述符中的某些值
+(例如周期性传输的轮询间隔)
+在高速模式下使用不同的编码方式。
+
+不过,一定要让设备驱动经过 USB 2.0 集线器的测试。
+当使用事务转换器时,这些集线器报告某些故障
+(例如断开连接)的方式会不同;
+已经见过一些驱动在遇到与 OHCI 或 UHCI
+所报告的不同故障时表现不佳。
+
+性能
+====
+
+USB 2.0 吞吐量主要受两个因素制约:
+主机控制器处理请求的速度,以及设备响应这些请求的速度。
+480 Mbit/sec 的“原始传输率”对所有设备都成立,
+但总吞吐量还会受到诸如单个高速包之间的延迟、
+驱动是否足够聪明,以及系统整体负载等因素的影响。
+延迟也是性能考量因素。
+
+批量传输最常用于关注吞吐量的场景。
+需要记住的是,批量传输总是以 512 字节包为单位,
+而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。
+8 个 USB 2.0 微帧构成一个 USB 1.1 帧;
+一个微帧的时长是 1 毫秒 / 8 = 125 微秒。
+
+因此,只要硬件和设备驱动软件都允许,
+批量传输可提供超过 50 MByte/sec 的带宽。
+周期性传输模式(等时和中断)允许使用更大的包大小,
+从而可以逼近所宣称的 480 Mbit/sec 传输率。
+
+硬件性能
+--------
+
+截至本文撰写时,单个 USB 2.0 设备的最大传输速率
+通常约为 20 MByte/sec。
+这当然会随着时间改变:一些设备现在更快,一些更慢。
+
+第一代 NEC EHCI 实现似乎存在
+大约 28 MByte/sec 的硬件瓶颈。
+虽然这对单个 20 MByte/sec 的设备显然已经够用,
+但把三个这样的设备挂到同一总线上,
+并不能得到 60 MByte/sec。
+问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
+因此它每个微帧只会尝试 6 次(也许是 7 次)
+USB 事务,而不是 13 次。
+(对一个比其他产品早上市一年的芯片来说,
+这是个合理的妥协!)
+
+
+预计较新的实现会在这方面做得更好,
+通过投入更多芯片面积来解决这个问题,
+使新的主板芯片组更接近 60 MByte/sec 的目标。
+这既包括 NEC 的更新实现,也包括其他厂商的芯片。
+
+
+主机从 EHCI 控制器收到“请求已完成”中断的最小延迟
+为一个微帧(125 微秒)。该延迟可以调节;
+驱动提供了一个模块选项。默认情况下,
+``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制
+或批量请求时,通常可以在不到 250 微秒内得知它已完成
+(具体取决于传输大小)。
+
+软件性能
+--------
+
+即便只是要达到 20 MByte/sec 的传输速率,
+Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。
+这意味着要发出较大的请求,
+或者在需要发出一连串小请求时使用批量请求排队。
+如果驱动未做到这一点,那么会直接从性能结果上表现出来。
+
+
+在典型情况下,使用 ``usb_bulk_msg()``
+以 4 KB 块循环写出,
+会浪费超过一半的 USB 2.0 带宽。
+I/O 完成与驱动发出下一次请求之间的延迟,
+通常会比一次 I/O 本身耗时更长。
+如果同样的循环改用 16 KB 块,会好一些;
+若使用一连串 128 KB 块,则浪费会少得多。
+
+
+但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效,
+不如直接向主机控制器排入多个(批量)请求,
+然后等待它们全部完成(或在出错时取消)。
+这种 URB 排队方式对所有 USB 1.1
+主机控制器驱动也同样适用。
+
+
+在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;
+它们会把 scatterlist 中的所有缓冲区都排入队列。
+它们还使用 scatterlist 的 DMA 映射
+(其中可能应用 IOMMU)并减少中断次数,
+这些都有助于让高速传输尽可能快地运行。
+
+待办:
+ 中断传输和等时(ISO)传输的性能问题。
+ 这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。
+
+待办:
+ 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,
+ 可以分配超过标准 80% 的周期性带宽。
+ 后续将对此进行说明。
diff --git a/Documentation/translations/zh_CN/usb/index.rst b/Documentation/translations/zh_CN/usb/index.rst
new file mode 100644
index 000000000000..eb5aca0c13ec
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/index.rst
@@ -0,0 +1,54 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/index.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+========
+USB 支持
+========
+
+.. toctree::
+ :maxdepth: 1
+
+ acm
+ authorization
+ chipidea
+ dwc3
+ ehci
+ usbmon
+
+Todolist:
+
+* functionfs
+* functionfs-desc
+* gadget_configfs
+* gadget_hid
+* gadget_multi
+* gadget_printer
+* gadget_serial
+* gadget_uvc
+* gadget-testing
+* iuu_phoenix
+* mass-storage
+* misc_usbsevseg
+* mtouchusb
+* ohci
+* raw-gadget
+* usbip_protocol
+* usb-serial
+* usb-help
+* text_files
+
+.. only:: subproject and html
+
+ 索引
+ ====
+
+ * :ref:`genindex`
diff --git a/Documentation/translations/zh_CN/usb/usbmon.rst b/Documentation/translations/zh_CN/usb/usbmon.rst
new file mode 100644
index 000000000000..11b6d5b59dce
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/usbmon.rst
@@ -0,0 +1,427 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/usbmon.rst
+
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+======
+usbmon
+======
+
+简介
+====
+小写形式的 ``usbmon`` 指的是内核中的一项功能,
+用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具
+``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。
+类似地,人们希望使用 usbdump 或 USBMon
+(首字母大写)之类的工具来检查
+usbmon 生成的原始跟踪数据。
+
+usbmon 报告的是各个外设驱动
+向主机控制器驱动(HCD)发出的请求。
+因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息
+可能无法精确对应实际的总线事务。
+这和 tcpdump 的情况是一样的。
+
+目前实现了两种 API: ``text`` 和 ``binary``。
+二进制 API 通过 ``/dev`` 命名空间中的字符设备提供,
+并且属于 ABI。文本 API 自内核 2.6.35 起已废弃,
+但为了方便仍然可用。
+
+如何使用 usbmon 收集原始文本跟踪信息
+====================================
+
+与数据包套接字不同,usbmon 提供了一种接口,
+可以输出文本格式的跟踪信息。这样做有两个目的:
+第一,在更完善的格式最终确定之前,
+它作为工具间通用的跟踪交换格式;
+第二,在不使用工具的情况下,人们也可以直接阅读这些信息。
+
+要收集原始文本跟踪信息,请按以下步骤进行操作。
+
+1. 准备
+-------
+
+挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块
+(如果它是作为模块构建的)。如果 usbmon 已经编入内核,
+那么第二步可以省略。
+
+命令示例::
+
+ # mount -t debugfs none_debugs /sys/kernel/debug
+ # modprobe usbmon
+ #
+
+确认总线套接字是否存在::
+
+ # ls /sys/kernel/debug/usb/usbmon
+ 0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u
+ #
+
+现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包,
+并跳到第 3 步;
+也可以先按第 2 步找到目标设备所在的总线。
+这样可以过滤掉那些持续输出数据的烦人设备。
+
+2. 查找目标设备连接的是哪条总线
+-------------------------------
+
+运行 ``cat /sys/kernel/debug/usb/devices``,
+找到对应设备的 T 行。通常可以通过厂商字符串来查找。
+如果有许多类似设备,可以拔掉其中一个,
+再比较前后两次 ``/sys/kernel/debug/usb/devices``
+的输出。T 行里会包含总线编号。
+
+示例::
+
+ T: Bus=03 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
+ D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
+ P: Vendor=0557 ProdID=2004 Rev= 1.00
+ S: Manufacturer=ATEN
+ S: Product=UC100KM V2.00
+
+``Bus=03`` 表示它位于 3 号总线上。或者,
+也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。
+
+示例如下::
+
+ Bus 003 Device 002: ID 0557:2004 ATEN UC100KM V2.00
+
+
+3. 启动 cat 命令
+----------------
+
+如果只监听单条总线,可执行::
+
+ # cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out
+
+否则,如果要监听所有总线,则执行::
+
+ # cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out
+
+此进程会一直读取,直到被终止。
+由于输出通常会很长,因此更推荐将输出重定向到某个位置。
+
+
+4. 在 USB 总线上执行期望的操作
+------------------------------
+
+此处需要执行一些会产生 USB 流量的动作,
+比如插入 U 盘、拷贝文件、操作摄像头等。
+
+
+5. 停止 cat
+-----------
+
+这一步通常通过键盘中断(Control-C)完成。
+
+此时输出文件(本例中为 ``/tmp/1.mon.out``)
+可以保存、通过电子邮件发送,或使用文本编辑器查看。
+如果使用最后一种方式,请确保文件不会大到编辑器无法打开。
+
+
+原始文本数据格式
+================
+
+目前支持两种格式:原始格式,也就是 ``1t`` 格式,
+以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。
+``1u`` 格式增加了一些字段,例如 ISO 帧描述符、
+``interval`` 等。它生成的行会稍长一些,
+但在其他方面是 ``1t`` 格式的完整超集。
+
+如果程序需要区分上述两种格式,
+可以查看 ``address`` 字段(见下文)。
+如果其中有两个冒号,就是 ``1t`` 格式;
+否则是 ``1u`` 格式。
+
+任何文本格式的数据由一系列事件组成,
+如 URB 提交、URB 回调、提交错误等。
+每个事件对应单独的一行文本,
+由使用空白符间隔的若干字段组成。
+字段的数量与位置可能取决于事件类型,
+但以下字段对所有类型都通用:
+
+下面按从左到右的顺序列出这些共有字段:
+
+- URB Tag。用于标识 URB,通常是 URB 结构体在内核中的地址
+ (以十六进制表示),
+ 但也可能是序号或其他合理的唯一字符串。
+
+- 时间戳(微秒),十进制数字。
+ 时间戳的精度取决于可用时钟,
+ 因此可能远差于
+ 1 微秒(例如实现使用的是 jiffies)。
+
+- 事件类型。它表示的是事件的格式,而不是 URB 的类型。
+ 可用值为:``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。
+
+- ``Address`` 字段(以前称作 ``pipe``)。
+ 它包含四个由冒号分隔的字段:
+ URB 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下:
+
+ == == ==========================
+ Ci Co 控制输入和输出
+ Zi Zo 等时输入和输出
+ Ii Io 中断输入和输出
+ Bi Bo 批量输入和输出
+ == == ==========================
+
+ 总线号、设备地址和端点号使用十进制,但可能有前导零。
+
+- URB 状态字段。这个字段要么是一个字母,
+ 要么是几个由冒号分隔的数字:
+ URB 状态、``interval``、``start frame`` 和 ``error count``。
+ 与 ``address`` 字段不同,除了状态外,其余字段都是可选的。
+ ``interval`` 只会为中断和等时 URB 打印;``start frame`` 只会为
+ 等时 URB 打印;错误计数只会在等时回调事件中打印。
+
+ 状态字段是一个十进制数字,有时为负数,
+ 对应 URB 的 ``status`` 字段。
+ 对于提交事件,这个字段本身没有实际意义,
+ 但为了便于脚本解析,它仍然存在。
+ 当发生错误时,该字段包含错误码。
+
+ 在提交控制包时,这个字段包含的是 ``Setup Tag``,
+ 而不是一组数字。
+ 判断 ``Setup Tag`` 是否存在很容易,因为它从来不是数字。
+ 因此,如果脚本在这个字段里发现的是一组数字,
+ 就会继续读取数据长度(等时 URB 除外)。
+ 如果发现的是其他内容,比如一个字母,
+ 那么脚本会先读取 ``Setup`` 包,再读取数据长度或等时描述符。
+
+- ``Setup`` 包由 5 个字段组成:
+ ``bmRequestType``、``bRequest``、``wValue``、
+ ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。
+ 如果 ``Setup Tag`` 为 ``s``,就可以安全地解码这些字段。
+ 否则,说明 Setup 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。
+
+- 等时传输帧描述符的数量及其内容:
+ 如果一个等时传输事件带有一组描述符,首先打印该 URB 中描述符的总数,
+ 然后为每个描述符打印一个字段,最多打印 5 个字段。
+ 每个字段由三个用冒号分隔的十进制数字组成,
+ 分别表示状态(status)、偏移(offset)和长度(length)。
+ 对于提交(submission),报告的是初始长度;
+ 对于回调(callback),报告的是实际长度。
+
+- 数据长度:
+ 对于提交,表示请求的长度;对于回调,表示实际传输的长度。
+
+- 数据标签:
+ 即使数据长度非零,usbmon 也不一定会捕获数据。
+ 仅当标签为 ``=`` 时,才会有数据字段。
+
+- 数据字段:
+ 以大端十六进制格式显示。注意,这些并不是真正的机器字,
+ 而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含
+ 1 到 4 个字节。
+ 收集的数据长度是有限的,可能小于数据长度字段中报告的值。
+ 因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区,
+ 所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下,
+ 收集的数据长度可能大于数据长度字段的值。
+
+
+
+示例:
+
+获取端口状态的输入控制传输::
+
+ d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 <
+ d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000
+
+向地址为 5 的存储设备发送
+31 字节 Bulk 包装的 SCSI 命令 ``0x28``
+(``READ_10``)的输出批量传输::
+
+ dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000
+ dd65f0e8 4128379808 C Bo:1:005:2 0 31 >
+
+原始二进制格式与 API
+====================
+API 的整体架构与前文大体相同,只是事件以二进制格式传递。
+每个事件都通过下面的结构发送
+(这个名字是为了叙述方便而虚构的)::
+
+
+ struct usbmon_packet {
+ u64 id; /* 0: URB ID - 从提交到回调 */
+ unsigned char type; /* 8: 与文本相同;可扩展 */
+ unsigned char xfer_type; /* ISO (0)、中断、控制、批量 (3) */
+ unsigned char epnum; /* 端点号和传输方向 */
+ unsigned char devnum; /* 设备地址 */
+ u16 busnum; /* 12: 总线号 */
+ char flag_setup; /* 14: 与文本相同 */
+ char flag_data; /* 15: 与文本相同;二进制零也可 */
+ s64 ts_sec; /* 16: gettimeofday */
+ s32 ts_usec; /* 24: gettimeofday */
+ int status; /* 28: */
+ unsigned int length; /* 32: 数据长度(提交或实际) */
+ unsigned int len_cap; /* 36: 已捕获的数据长度 */
+ union { /* 40: */
+ unsigned char setup[SETUP_LEN]; /* 仅用于控制类 S 事件 */
+ struct iso_rec { /* 仅用于 ISO */
+ int error_count;
+ int numdesc;
+ } iso;
+ } s;
+ int interval; /* 48: 仅用于中断和 ISO */
+ int start_frame; /* 52: 仅用于 ISO */
+ unsigned int xfer_flags; /* 56: URB 的 transfer_flags 副本 */
+ unsigned int ndesc; /* 60: 实际 ISO 描述符数量 */
+ }; /* 64 总长度 */
+
+可以用 ``read(2)``、``ioctl(2)``,
+或者通过 ``mmap`` 访问缓冲区,
+从字符设备接收这些事件。
+不过,出于兼容性原因,``read(2)``
+只返回前 48 个字节。
+
+字符设备通常命名为 ``/dev/usbmonN``,
+其中 ``N`` 是 USB 总线号。
+编号为零的设备(``/dev/usbmon0``)比较特殊,
+表示“所有总线”。
+请注意,具体命名策略由 Linux 发行版决定。
+
+如果你手动创建 ``/dev/usbmon0``,
+请确保它归 root 所有,并且权限为 ``0600``。
+否则,非特权用户将能够窃听键盘流量。
+
+以下 ``MON_IOC_MAGIC`` 为 ``0x92`` 的 ioctl 调用可用:
+
+``MON_IOCQ_URB_LEN``,定义为 ``_IO(MON_IOC_MAGIC, 1)``
+
+该调用返回下一个事件的数据长度。
+注意大多数事件不包含数据,
+因此如果该调用返回零,并不意味着没有事件。
+
+``MON_IOCG_STATS``,定义为
+``_IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)``
+
+参数是指向以下结构的指针::
+
+ struct mon_bin_stats {
+ u32 queued;
+ u32 dropped;
+ };
+
+成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,
+而不是自上次重置以来处理过的事件数量。
+
+成员 ``dropped`` 表示自上次调用
+``MON_IOCG_STATS`` 以来丢失的事件数量。
+
+``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)``
+
+此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。
+大小可能会向下取整到下一个块(或页)。
+如果请求的大小超出该内核的 [未指定] 范围,
+则调用会失败并返回 ``-EINVAL``。
+
+``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)``
+
+该调用返回缓冲区当前大小(以字节为单位)。
+
+``MON_IOCX_GET``,定义为
+``_IOW(MON_IOC_MAGIC, 6, struct mon_get_arg)``
+``MON_IOCX_GETX``,定义为
+``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)``
+
+如果内核缓冲区中没有事件,
+这些调用就会一直等待,直到有事件到达,
+然后返回第一个事件。
+参数是指向以下结构的指针::
+
+ struct mon_get_arg {
+ struct usbmon_packet *hdr;
+ void *data;
+ size_t alloc; /* 数据长度可以为零 */
+ };
+
+
+调用前,应填好 ``hdr``、``data`` 和 ``alloc``。
+调用返回后,``hdr`` 指向的区域中包含下一个事件的结构;
+如果存在数据,那么数据缓冲区中也会包含相应数据。
+该事件会从内核缓冲区中移除。
+
+``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,
+``MON_IOCX_GETX`` 会复制 64 字节。
+
+``MON_IOCX_MFETCH``,定义为
+``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)``
+
+当应用程序通过 ``mmap(2)`` 访问缓冲区时,
+主要使用这个 ioctl。
+其参数是指向以下结构的指针::
+
+ struct mon_mfetch_arg {
+ uint32_t *offvec; /* 获取的事件偏移向量 */
+ uint32_t nfetch; /* 要获取的事件数量(输出:已获取) */
+ uint32_t nflush; /* 要刷新的事件数量 */
+ };
+
+
+该 ioctl 的操作分为三个阶段:
+
+首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。
+实际丢弃的事件数量会写回 ``nflush``。
+
+其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待,
+直到缓冲区中出现事件。
+
+第三,将最多 ``nfetch`` 个偏移量提取到 mmap
+缓冲区,并存入 ``offvec`` 中。
+实际提取到的事件偏移数量会存回 ``nfetch``。
+
+``MON_IOCH_MFLUSH``,定义为 ``_IO(MON_IOC_MAGIC, 8)``
+
+此调用从内核缓冲区移除若干事件。
+其参数为要移除的事件数量。
+如果缓冲区中的事件少于请求数量,
+则移除所有事件,且不报告错误。
+当没有事件时也可使用。
+
+``FIONBIO``
+
+如果有需要,将来可能会实现 ``FIONBIO`` ioctl。
+
+除了 ``ioctl(2)`` 和 ``read(2)`` 之外,
+二进制 API 的特殊文件也可以用 ``select(2)`` 和
+``poll(2)`` 轮询。
+但 ``lseek(2)`` 不起作用。
+
+* 二进制 API 的内核缓冲区内存映射访问
+
+基本思想很简单:
+
+准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。
+然后执行类似下面伪代码的循环::
+
+ struct mon_mfetch_arg fetch;
+ struct usbmon_packet *hdr;
+ int nflush = 0;
+ for (;;) {
+ fetch.offvec = vec; // 有 N 个 32 位字
+ fetch.nfetch = N; // 或者少于 N
+ fetch.nflush = nflush;
+ ioctl(fd, MON_IOCX_MFETCH, &fetch); // 同时处理错误
+ nflush = fetch.nfetch; // 完成后要刷新这么多包
+ for (i = 0; i < nflush; i++) {
+ hdr = (struct ubsmon_packet *) &mmap_area[vec[i]];
+ if (hdr->type == '@') // 填充包
+ continue;
+ caddr_t data = &mmap_area[vec[i]] + 64;
+ process_packet(hdr, data);
+ }
+ }
+
+
+
+因此,主要思想是每 N 个事件只执行一次 ioctl。
+
+虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端,
+因此上面的伪代码无需任何合并操作。
diff --git a/arch/arm/mach-pxa/devices.c b/arch/arm/mach-pxa/devices.c
index 7695cfce01a1..edad956a1483 100644
--- a/arch/arm/mach-pxa/devices.c
+++ b/arch/arm/mach-pxa/devices.c
@@ -11,7 +11,6 @@
#include <linux/platform_data/i2c-pxa.h>
#include <linux/soc/pxa/cpu.h>
-#include "udc.h"
#include <linux/platform_data/video-pxafb.h>
#include <linux/platform_data/mmc-pxamci.h>
#include "irqs.h"
@@ -83,10 +82,6 @@ void __init pxa_set_mci_info(const struct pxamci_platform_data *info,
pr_err("Unable to create mci device: %d\n", err);
}
-static struct pxa2xx_udc_mach_info pxa_udc_info = {
- .gpio_pullup = -1,
-};
-
static struct resource pxa2xx_udc_resources[] = {
[0] = {
.start = 0x40600000,
@@ -108,7 +103,6 @@ struct platform_device pxa25x_device_udc = {
.resource = pxa2xx_udc_resources,
.num_resources = ARRAY_SIZE(pxa2xx_udc_resources),
.dev = {
- .platform_data = &pxa_udc_info,
.dma_mask = &udc_dma_mask,
}
};
@@ -119,7 +113,6 @@ struct platform_device pxa27x_device_udc = {
.resource = pxa2xx_udc_resources,
.num_resources = ARRAY_SIZE(pxa2xx_udc_resources),
.dev = {
- .platform_data = &pxa_udc_info,
.dma_mask = &udc_dma_mask,
}
};
diff --git a/arch/arm/mach-pxa/gumstix.c b/arch/arm/mach-pxa/gumstix.c
index 1713bdf3b71e..6074815a4bca 100644
--- a/arch/arm/mach-pxa/gumstix.c
+++ b/arch/arm/mach-pxa/gumstix.c
@@ -39,7 +39,6 @@
#include "pxa25x.h"
#include <linux/platform_data/mmc-pxamci.h>
-#include "udc.h"
#include "gumstix.h"
#include "devices.h"
diff --git a/arch/arm/mach-pxa/udc.h b/arch/arm/mach-pxa/udc.h
deleted file mode 100644
index 9a827e32db98..000000000000
--- a/arch/arm/mach-pxa/udc.h
+++ /dev/null
@@ -1,8 +0,0 @@
-/*
- * arch/arm/mach-pxa/include/mach/udc.h
- *
- */
-#include <linux/platform_data/pxa2xx_udc.h>
-
-extern void pxa_set_udc_info(struct pxa2xx_udc_mach_info *info);
-
diff --git a/arch/arm64/boot/dts/qcom/pm4125.dtsi b/arch/arm64/boot/dts/qcom/pm4125.dtsi
index cf8c822e80ce..542e8fe030da 100644
--- a/arch/arm64/boot/dts/qcom/pm4125.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm4125.dtsi
@@ -61,7 +61,6 @@
"attach-detach",
"legacy-cable-detect",
"try-snk-src-detect";
- vdd-vbus-supply = <&pm4125_vbus>;
status = "disabled";
};
diff --git a/arch/arm64/boot/dts/qcom/pm7250b.dtsi b/arch/arm64/boot/dts/qcom/pm7250b.dtsi
index 0761e6b5fd8d..43cab07126c5 100644
--- a/arch/arm64/boot/dts/qcom/pm7250b.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm7250b.dtsi
@@ -86,7 +86,6 @@
"msg-tx-discarded",
"msg-rx-discarded",
"fr-swap";
- vdd-vbus-supply = <&pm7250b_vbus>;
status = "disabled";
};
diff --git a/arch/arm64/boot/dts/qcom/pm8150b.dtsi b/arch/arm64/boot/dts/qcom/pm8150b.dtsi
index 3f7b0b6a1d10..b83be4b6cb1c 100644
--- a/arch/arm64/boot/dts/qcom/pm8150b.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8150b.dtsi
@@ -95,7 +95,6 @@
"msg-tx-discarded",
"msg-rx-discarded",
"fr-swap";
- vdd-vbus-supply = <&pm8150b_vbus>;
};
pm8150b_temp: temp-alarm@2400 {
diff --git a/arch/arm64/boot/dts/qcom/pmi632.dtsi b/arch/arm64/boot/dts/qcom/pmi632.dtsi
index 8c899d148e46..b0ed35094a98 100644
--- a/arch/arm64/boot/dts/qcom/pmi632.dtsi
+++ b/arch/arm64/boot/dts/qcom/pmi632.dtsi
@@ -69,7 +69,6 @@
"attach-detach",
"legacy-cable-detect",
"try-snk-src-detect";
- vdd-vbus-supply = <&pmi632_vbus>;
status = "disabled";
};
diff --git a/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts b/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts
index da46e9d65528..4ace2d6c06ce 100644
--- a/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts
+++ b/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts
@@ -409,6 +409,8 @@
typec-power-opmode = "default";
pd-disable;
+ vbus-supply = <&pm4125_vbus>;
+
ports {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts b/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts
index 1203172729fa..22baee407fbe 100644
--- a/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts
+++ b/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts
@@ -323,6 +323,8 @@
typec-power-opmode = "default";
pd-disable;
+ vbus-supply = <&pmi632_vbus>;
+
ports {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
index 54da0d759a67..690b484352ed 100644
--- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
+++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
@@ -1499,6 +1499,8 @@
data-role = "dual";
self-powered;
+ vbus-supply = <&pm8150b_vbus>;
+
source-pdos = <PDO_FIXED(5000, 3000,
PDO_FIXED_DUAL_ROLE |
PDO_FIXED_USB_COMM |
diff --git a/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts b/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts
index 0edb2992b902..3223884f9cd6 100644
--- a/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts
+++ b/arch/arm64/boot/dts/qcom/sdm632-fairphone-fp3.dts
@@ -252,6 +252,8 @@
typec-power-opmode = "default";
pd-disable;
+ vbus-supply = <&pmi632_vbus>;
+
port {
pmi632_hs_in: endpoint {
remote-endpoint = <&usb_dwc3_hs>;
diff --git a/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts b/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts
index 466ad409e924..0f23eaef01f2 100644
--- a/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts
+++ b/arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts
@@ -251,6 +251,8 @@
typec-power-opmode = "default";
pd-disable;
+ vbus-supply = <&pmi632_vbus>;
+
ports {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts b/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts
index 3964aae47fd4..23f950067a08 100644
--- a/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts
+++ b/arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts
@@ -953,6 +953,8 @@
typec-power-opmode = "default";
pd-disable;
+ vbus-supply = <&pm7250b_vbus>;
+
ports {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/qcom/sm8150-hdk.dts b/arch/arm64/boot/dts/qcom/sm8150-hdk.dts
index 6ae6e07c37df..8fb04ce72f91 100644
--- a/arch/arm64/boot/dts/qcom/sm8150-hdk.dts
+++ b/arch/arm64/boot/dts/qcom/sm8150-hdk.dts
@@ -572,6 +572,8 @@
data-role = "dual";
self-powered;
+ vbus-supply = <&pm8150b_vbus>;
+
source-pdos = <PDO_FIXED(5000, 3000,
PDO_FIXED_DUAL_ROLE |
PDO_FIXED_USB_COMM |
diff --git a/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi b/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi
index c017399297b9..51b57c697a75 100644
--- a/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8250-xiaomi-elish-common.dtsi
@@ -725,6 +725,8 @@
data-role = "dual";
self-powered;
+ vbus-supply = <&pm8150b_vbus>;
+
source-pdos = <PDO_FIXED(5000, 3000,
PDO_FIXED_DUAL_ROLE |
PDO_FIXED_USB_COMM |
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index e4c4f2b09732..5c2d3ff5fce8 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -963,6 +963,16 @@ config SENSORS_POWR1220
This driver can also be built as a module. If so, the module
will be called powr1220.
+config SENSORS_PROM21_XHCI
+ tristate "AMD Promontory 21 xHCI temperature sensor"
+ depends on USB_XHCI_PCI
+ help
+ If you say yes here you get support for the AMD Promontory 21
+ (PROM21) xHCI temperature sensor.
+
+ This driver can also be built as a module. If so, the module
+ will be called prom21-xhci.
+
config SENSORS_LAN966X
tristate "Microchip LAN966x Hardware Monitoring"
depends on SOC_LAN966 || COMPILE_TEST
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d5c8bc05123a..63effc0ab8d1 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -199,6 +199,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
+obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o
obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
diff --git a/drivers/hwmon/prom21-xhci.c b/drivers/hwmon/prom21-xhci.c
new file mode 100644
index 000000000000..d40d0c53ce45
--- /dev/null
+++ b/drivers/hwmon/prom21-xhci.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Promontory 21 xHCI Hwmon Implementation
+ * (only temperature monitoring is supported)
+ *
+ * This can be effectively used as the alternative chipset temperature monitor.
+ *
+ * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_data/usb-xhci-prom21.h>
+#include <linux/pm_runtime.h>
+
+#define PROM21_XHCI_INDEX_OFFSET 0x3000
+#define PROM21_XHCI_DATA_OFFSET 0x3008
+#define PROM21_XHCI_TEMP_SELECTOR 0x0001e520
+
+struct prom21_xhci {
+ struct pci_dev *pdev;
+ struct device *hwmon_dev;
+ void __iomem *regs;
+};
+
+static int prom21_xhci_pm_get(struct prom21_xhci *hwmon)
+{
+ struct device *dev = &hwmon->pdev->dev;
+ int ret;
+
+ /*
+ * PROM21 temperature register access does not return a valid value while
+ * the parent xHCI PCI function is suspended. Do not wake the device from
+ * a hwmon read. On success, hold a usage reference without changing the
+ * runtime PM state; if runtime PM is disabled, allow the read unless the
+ * device is still marked suspended.
+ */
+ ret = pm_runtime_get_if_active(dev);
+ if (ret > 0)
+ return 0;
+
+ if (ret == -EINVAL) {
+ if (pm_runtime_status_suspended(dev))
+ return -ENODATA;
+
+ pm_runtime_get_noresume(dev);
+ return 0;
+ }
+
+ if (!ret)
+ return -ENODATA;
+
+ return ret;
+}
+
+/*
+ * This is not a pure MMIO read. The PROM21 vendor data register is selected
+ * by temporarily writing PROM21_XHCI_TEMP_SELECTOR to the vendor index
+ * register.
+ * The hwmon core already serializes this driver's callbacks, so this driver
+ * does not need an additional private lock. That does not synchronize with
+ * firmware, SMM, ACPI, or other possible users. Keep the sequence short and
+ * restore the previous index before returning.
+ */
+static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon,
+ u8 *raw)
+{
+ struct device *dev = &hwmon->pdev->dev;
+ u32 index;
+ u8 data;
+ int ret;
+
+ ret = prom21_xhci_pm_get(hwmon);
+ if (ret)
+ return ret;
+
+ index = readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+ /* Select the PROM21 temperature register through the vendor index. */
+ writel(PROM21_XHCI_TEMP_SELECTOR,
+ hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+ /* Use a 32-bit read for PCI MMIO register access. */
+ data = readl(hwmon->regs + PROM21_XHCI_DATA_OFFSET) & 0xff;
+ /* Restore the previous vendor index register value. */
+ writel(index, hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+ readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
+
+ /*
+ * Drop the usage reference taken by prom21_xhci_pm_get(). This is
+ * enough because the read path never resumes the device; use the normal
+ * put path so the PM core can re-evaluate idle state after the read.
+ * Otherwise, a racing xHCI autosuspend attempt can see a nonzero
+ * runtime PM usage count and skip autosuspend, and a later
+ * pm_runtime_put_noidle(), which does not check for an idle device,
+ * would leave the device active.
+ */
+ pm_runtime_put(dev);
+
+ if (!data)
+ return -ENODATA;
+
+ *raw = data;
+ return 0;
+}
+
+static long prom21_xhci_raw_to_millicelsius(u8 raw)
+{
+ /*
+ * No public AMD reference is available for this value.
+ * The scale was derived from observed PROM21 xHCI temperature readings:
+ * temp[C] = raw * 0.9066 - 78.624
+ */
+ return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
+}
+
+static umode_t prom21_xhci_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ if (type != hwmon_temp)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static int prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct prom21_xhci *hwmon = dev_get_drvdata(dev);
+ u8 raw;
+ int ret;
+
+ if (type != hwmon_temp || attr != hwmon_temp_input)
+ return -EOPNOTSUPP;
+
+ ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw);
+ if (ret)
+ return ret;
+
+ *val = prom21_xhci_raw_to_millicelsius(raw);
+ return 0;
+}
+
+static const struct hwmon_ops prom21_xhci_ops = {
+ .is_visible = prom21_xhci_is_visible,
+ .read = prom21_xhci_read,
+};
+
+static const struct hwmon_channel_info *const prom21_xhci_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ NULL,
+};
+
+static const struct hwmon_chip_info prom21_xhci_chip_info = {
+ .ops = &prom21_xhci_ops,
+ .info = prom21_xhci_info,
+};
+
+static int prom21_xhci_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &auxdev->dev;
+ const struct prom21_xhci_pdata *pdata = dev_get_platdata(dev);
+ struct prom21_xhci *hwmon;
+
+ if (!pdata)
+ return dev_err_probe(dev, -ENODEV,
+ "platform data unavailable\n");
+
+ if (!pdata->regs ||
+ pdata->rsrc_len < PROM21_XHCI_DATA_OFFSET + sizeof(u32))
+ return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n");
+
+ hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ hwmon->pdev = pdata->pdev;
+ hwmon->regs = pdata->regs;
+ auxiliary_set_drvdata(auxdev, hwmon);
+
+ /*
+ * Parent the hwmon device to the PCI function because the temperature
+ * value is read from that function's MMIO BAR, and systems may contain
+ * multiple PROM21 xHCI functions. This lets userspace identify the PCI
+ * endpoint for each reading. The auxiliary driver still owns the hwmon
+ * lifetime and unregisters it before HCD teardown.
+ */
+ hwmon->hwmon_dev =
+ hwmon_device_register_with_info(&pdata->pdev->dev, "prom21_xhci",
+ hwmon, &prom21_xhci_chip_info,
+ NULL);
+ if (IS_ERR(hwmon->hwmon_dev))
+ return PTR_ERR(hwmon->hwmon_dev);
+
+ return 0;
+}
+
+static void prom21_xhci_remove(struct auxiliary_device *auxdev)
+{
+ struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev);
+
+ /*
+ * The PROM21 PCI glue destroys the auxiliary device before HCD teardown.
+ * Unregister the hwmon device here so sysfs removes the attributes,
+ * stops new reads, and drains active hwmon callbacks before the xHCI
+ * MMIO mapping is released.
+ */
+ hwmon_device_unregister(hwmon->hwmon_dev);
+}
+
+static const struct auxiliary_device_id prom21_xhci_id_table[] = {
+ { .name = "xhci_pci_prom21.hwmon" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table);
+
+static struct auxiliary_driver prom21_xhci_driver = {
+ .name = "prom21-xhci",
+ .probe = prom21_xhci_probe,
+ .remove = prom21_xhci_remove,
+ .id_table = prom21_xhci_id_table,
+};
+module_auxiliary_driver(prom21_xhci_driver);
+
+MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
+MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c
index 7aae5d915a1e..f8f97e8e2226 100644
--- a/drivers/net/thunderbolt/main.c
+++ b/drivers/net/thunderbolt/main.c
@@ -34,11 +34,12 @@
#define TBNET_RING_SIZE 256
#define TBNET_LOGIN_RETRIES 60
#define TBNET_LOGOUT_RETRIES 10
+#define TBNET_THROTTLING 128000
#define TBNET_E2E BIT(0)
#define TBNET_MATCH_FRAGS_ID BIT(1)
#define TBNET_64K_FRAMES BIT(2)
#define TBNET_MAX_MTU SZ_64K
-#define TBNET_FRAME_SIZE SZ_4K
+#define TBNET_FRAME_SIZE TB_MAX_FRAME_SIZE
#define TBNET_MAX_PAYLOAD_SIZE \
(TBNET_FRAME_SIZE - sizeof(struct thunderbolt_ip_frame_header))
/* Rx packets need to hold space for skb_shared_info */
@@ -327,11 +328,6 @@ static void stop_login(struct tbnet *net)
netdev_dbg(net->dev, "login stopped\n");
}
-static inline unsigned int tbnet_frame_size(const struct tbnet_frame *tf)
-{
- return tf->frame.size ? : TBNET_FRAME_SIZE;
-}
-
static void tbnet_free_buffers(struct tbnet_ring *ring)
{
unsigned int i;
@@ -562,7 +558,7 @@ static struct tbnet_frame *tbnet_get_tx_buffer(struct tbnet *net)
tf->frame.size = 0;
dma_sync_single_for_cpu(dma_dev, tf->frame.buffer_phy,
- tbnet_frame_size(tf), DMA_TO_DEVICE);
+ tb_ring_frame_size(&tf->frame), DMA_TO_DEVICE);
return tf;
}
@@ -744,7 +740,7 @@ static bool tbnet_check_frame(struct tbnet *net, const struct tbnet_frame *tf,
}
/* Should be greater than just header i.e. contains data */
- size = tbnet_frame_size(tf);
+ size = tb_ring_frame_size(&tf->frame);
if (size <= sizeof(*hdr)) {
net->stats.rx_length_errors++;
return false;
@@ -961,6 +957,9 @@ static int tbnet_open(struct net_device *dev)
}
net->rx_ring.ring = ring;
+ tb_ring_throttling(net->tx_ring.ring, TBNET_THROTTLING);
+ tb_ring_throttling(net->rx_ring.ring, TBNET_THROTTLING);
+
napi_enable(&net->napi);
start_login(net);
@@ -1011,7 +1010,8 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb,
hdr->frame_index, hdr->frame_count);
dma_sync_single_for_device(dma_dev,
frames[i]->frame.buffer_phy,
- tbnet_frame_size(frames[i]), DMA_TO_DEVICE);
+ tb_ring_frame_size(&frames[i]->frame),
+ DMA_TO_DEVICE);
}
return true;
@@ -1085,7 +1085,7 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb,
*/
for (i = 0; i < frame_count; i++) {
dma_sync_single_for_device(dma_dev, frames[i]->frame.buffer_phy,
- tbnet_frame_size(frames[i]), DMA_TO_DEVICE);
+ tb_ring_frame_size(&frames[i]->frame), DMA_TO_DEVICE);
}
return true;
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index db3b0bef48f4..294b3227a545 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -18,6 +18,10 @@ menuconfig USB4
if USB4
+config USB4_CONFIGFS
+ def_tristate USB4
+ depends on CONFIGFS_FS && !(USB4=y && CONFIGFS_FS=m)
+
config USB4_DEBUGFS_WRITE
bool "Enable write by debugfs to configuration spaces (DANGEROUS)"
help
@@ -60,4 +64,15 @@ config USB4_DMA_TEST
To compile this driver a module, choose M here. The module will be
called thunderbolt_dma_test.
+config USB4_STREAM
+ tristate "Stream data over Thunderbolt/USB4 cable"
+ depends on USB4_CONFIGFS
+ help
+ This adds support for USB4STREAM protocol that allows two
+ hosts to stream data directly over Thunderbolt/USB4 cable
+ through /dev/tbstreamX devices.
+
+ To compile this driver a module, choose M here. The module will be
+ called thunderbolt_stream.
+
endif # USB4
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index b44b32dcb832..beb054c3126b 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,14 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y := -I$(src)
obj-${CONFIG_USB4} := thunderbolt.o
-thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o
+thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o pci.o path.o tunnel.o eeprom.o
thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o
thunderbolt-${CONFIG_ACPI} += acpi.o
thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
+thunderbolt-$(CONFIG_USB4_CONFIGFS) += configfs.o
thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o
CFLAGS_test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
thunderbolt_dma_test-${CONFIG_USB4_DMA_TEST} += dma_test.o
obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o
+
+thunderbolt_stream-${CONFIG_USB4_STREAM} += stream.o
+obj-$(CONFIG_USB4_STREAM) += thunderbolt_stream.o
diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c
index 45d1415871b4..53546bc477a5 100644
--- a/drivers/thunderbolt/acpi.c
+++ b/drivers/thunderbolt/acpi.c
@@ -28,7 +28,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
return AE_OK;
/* It needs to reference this NHI */
- if (dev_fwnode(&nhi->pdev->dev) != fwnode)
+ if (dev_fwnode(nhi->dev) != fwnode)
goto out_put;
/*
@@ -57,16 +57,16 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
*/
pm_runtime_get_sync(&pdev->dev);
- link = device_link_add(&pdev->dev, &nhi->pdev->dev,
+ link = device_link_add(&pdev->dev, nhi->dev,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_RPM_ACTIVE |
DL_FLAG_PM_RUNTIME);
if (link) {
- dev_dbg(&nhi->pdev->dev, "created link from %s\n",
+ dev_dbg(nhi->dev, "created link from %s\n",
dev_name(&pdev->dev));
*(bool *)ret = true;
} else {
- dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
+ dev_warn(nhi->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
@@ -93,7 +93,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi)
acpi_status status;
bool ret = false;
- if (!has_acpi_companion(&nhi->pdev->dev))
+ if (!has_acpi_companion(nhi->dev))
return false;
/*
@@ -103,7 +103,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi)
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32,
tb_acpi_add_link, NULL, nhi, (void **)&ret);
if (ACPI_FAILURE(status)) {
- dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n");
+ dev_warn(nhi->dev, "failed to enumerate tunneled ports\n");
return false;
}
@@ -305,7 +305,7 @@ static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw)
struct tb_nhi *nhi = sw->tb->nhi;
struct acpi_device *parent_adev;
- parent_adev = ACPI_COMPANION(&nhi->pdev->dev);
+ parent_adev = ACPI_COMPANION(nhi->dev);
if (parent_adev)
adev = acpi_find_child_device(parent_adev, 0, false);
}
diff --git a/drivers/thunderbolt/configfs.c b/drivers/thunderbolt/configfs.c
new file mode 100644
index 000000000000..dc6bc363dfe8
--- /dev/null
+++ b/drivers/thunderbolt/configfs.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ConfigFS support
+ *
+ * Copyright (C) 2026, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#include <linux/configfs.h>
+#include <linux/export.h>
+
+#include "tb.h"
+
+static const struct config_item_type tb_root_group_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem tb_configfs = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = "thunderbolt",
+ .ci_type = &tb_root_group_type,
+ },
+ },
+};
+
+/**
+ * tb_configfs_register_group() - Register Thunderbolt ConfigFS group
+ * @group: Group to register.
+ *
+ * Registers the new @group under Thunderbolt subsystem ConfigFS.
+ *
+ * Return: 0% in case of success, negative errno otherwise.
+ */
+int tb_configfs_register_group(struct config_group *group)
+{
+ return configfs_register_group(&tb_configfs.su_group, group);
+}
+EXPORT_SYMBOL_GPL(tb_configfs_register_group);
+
+/**
+ * tb_configfs_unregister_group() - Unregister previously registered group
+ * @group: Group to unregister.
+ */
+void tb_configfs_unregister_group(struct config_group *group)
+{
+ configfs_unregister_group(group);
+}
+EXPORT_SYMBOL_GPL(tb_configfs_unregister_group);
+
+int tb_configfs_init(void)
+{
+ config_group_init(&tb_configfs.su_group);
+ mutex_init(&tb_configfs.su_mutex);
+ return configfs_register_subsystem(&tb_configfs);
+}
+
+void tb_configfs_exit(void)
+{
+ configfs_unregister_subsystem(&tb_configfs);
+}
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index b2fd60fc7bcc..cd47b627f97b 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -56,22 +56,22 @@ struct tb_ctl {
#define tb_ctl_WARN(ctl, format, arg...) \
- dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg)
+ dev_WARN((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_err(ctl, format, arg...) \
- dev_err(&(ctl)->nhi->pdev->dev, format, ## arg)
+ dev_err((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_warn(ctl, format, arg...) \
- dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg)
+ dev_warn((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_info(ctl, format, arg...) \
- dev_info(&(ctl)->nhi->pdev->dev, format, ## arg)
+ dev_info((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_dbg(ctl, format, arg...) \
- dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg)
+ dev_dbg((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_dbg_once(ctl, format, arg...) \
- dev_dbg_once(&(ctl)->nhi->pdev->dev, format, ## arg)
+ dev_dbg_once((ctl)->nhi->dev, format, ## arg)
static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue);
/* Serializes access to request kref_get/put */
@@ -666,8 +666,8 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec,
mutex_init(&ctl->request_queue_lock);
INIT_LIST_HEAD(&ctl->request_queue);
- ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev,
- TB_FRAME_SIZE, 4, 0);
+ ctl->frame_pool = dma_pool_create("thunderbolt_ctl", nhi->dev,
+ TB_FRAME_SIZE, 4, 0);
if (!ctl->frame_pool)
goto err;
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index 042f6a0d0f7f..f5cf0e177f40 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -366,7 +366,7 @@ static ssize_t sb_regs_write(struct tb_port *port, const struct sb_reg *sb_regs,
if (!sb_reg)
return -EINVAL;
- if (bytes_read > sb_regs->size)
+ if (bytes_read > sb_reg->size)
return -E2BIG;
ret = usb4_port_sb_write(port, target, index, sb_reg->reg, data,
@@ -956,7 +956,9 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
else if (!strcmp(buf, "stop"))
error_counter = USB4_MARGIN_SW_ERROR_COUNTER_STOP;
else
- return -EINVAL;
+ goto err_free;
+
+ free_page((unsigned long)buf);
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
if (!margining->software)
@@ -966,6 +968,10 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
}
return count;
+
+err_free:
+ free_page((unsigned long)buf);
+ return -EINVAL;
}
static int margining_error_counter_show(struct seq_file *s, void *not_used)
@@ -1780,6 +1786,8 @@ static void margining_port_remove(struct tb_port *port)
if (!port->usb4)
return;
+ if (!port->usb4->margining)
+ return;
snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
@@ -2361,8 +2369,10 @@ static int sb_regs_show(struct tb_port *port, const struct sb_reg *sb_regs,
memset(data, 0, sizeof(data));
ret = usb4_port_sb_read(port, target, index, regs->reg, data,
regs->size);
- if (ret)
- return ret;
+ if (ret) {
+ seq_printf(s, "0x%02x <not accessible>\n", regs->reg);
+ continue;
+ }
seq_printf(s, "0x%02x", regs->reg);
for (j = 0; j < regs->size; j++)
diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c
index b4aa79d482a0..7877319b1b03 100644
--- a/drivers/thunderbolt/dma_test.c
+++ b/drivers/thunderbolt/dma_test.c
@@ -87,7 +87,6 @@ static const char * const dma_test_result_names[] = {
* @error_code: Error code of the last run
* @complete: Used to wait for the Rx to complete
* @lock: Lock serializing access to this structure
- * @debugfs_dir: dentry of this dma_test
*/
struct dma_test {
const struct tb_service *svc;
@@ -108,7 +107,6 @@ struct dma_test {
enum dma_test_test_error error_code;
struct completion complete;
struct mutex lock;
- struct dentry *debugfs_dir;
};
/* DMA test property directory UUID: 3188cd10-6523-4a5a-a682-fdca07a248d8 */
@@ -157,6 +155,8 @@ static int dma_test_start_rings(struct dma_test *dt)
dt->tx_ring = ring;
e2e_tx_hop = ring->hop;
+ tb_ring_throttling(ring, 128000);
+
ret = tb_xdomain_alloc_out_hopid(xd, -1);
if (ret < 0) {
dma_test_free_rings(dt);
@@ -164,6 +164,7 @@ static int dma_test_start_rings(struct dma_test *dt)
}
dt->tx_hopid = ret;
+
}
if (dt->packets_to_receive) {
@@ -182,6 +183,8 @@ static int dma_test_start_rings(struct dma_test *dt)
dt->rx_ring = ring;
+ tb_ring_throttling(ring, 128000);
+
ret = tb_xdomain_alloc_in_hopid(xd, -1);
if (ret < 0) {
dma_test_free_rings(dt);
@@ -619,18 +622,18 @@ DEFINE_SHOW_ATTRIBUTE(status);
static void dma_test_debugfs_init(struct tb_service *svc)
{
- struct dma_test *dt = tb_service_get_drvdata(svc);
+ struct dentry *debugfs_dir;
- dt->debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
+ debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
- debugfs_create_file("lanes", 0600, dt->debugfs_dir, svc, &lanes_fops);
- debugfs_create_file("speed", 0600, dt->debugfs_dir, svc, &speed_fops);
- debugfs_create_file("packets_to_receive", 0600, dt->debugfs_dir, svc,
+ debugfs_create_file("lanes", 0600, debugfs_dir, svc, &lanes_fops);
+ debugfs_create_file("speed", 0600, debugfs_dir, svc, &speed_fops);
+ debugfs_create_file("packets_to_receive", 0600, debugfs_dir, svc,
&packets_to_receive_fops);
- debugfs_create_file("packets_to_send", 0600, dt->debugfs_dir, svc,
+ debugfs_create_file("packets_to_send", 0600, debugfs_dir, svc,
&packets_to_send_fops);
- debugfs_create_file("status", 0400, dt->debugfs_dir, svc, &status_fops);
- debugfs_create_file("test", 0200, dt->debugfs_dir, svc, &test_fops);
+ debugfs_create_file("status", 0400, debugfs_dir, svc, &status_fops);
+ debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops);
}
static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id)
@@ -658,7 +661,7 @@ static void dma_test_remove(struct tb_service *svc)
struct dma_test *dt = tb_service_get_drvdata(svc);
mutex_lock(&dt->lock);
- debugfs_remove_recursive(dt->debugfs_dir);
+ debugfs_lookup_and_remove("dma_test", svc->debugfs_dir);
mutex_unlock(&dt->lock);
}
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 317780b99992..479fa4d265c2 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -319,12 +319,15 @@ const struct bus_type tb_bus_type = {
static void tb_domain_release(struct device *dev)
{
struct tb *tb = container_of(dev, struct tb, dev);
+ struct tb_nhi *nhi = tb->nhi;
tb_ctl_free(tb->ctl);
destroy_workqueue(tb->wq);
ida_free(&tb_domain_ida, tb->index);
mutex_destroy(&tb->lock);
kfree(tb);
+
+ complete(&nhi->domain_released);
}
const struct device_type tb_domain_type = {
@@ -402,7 +405,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
if (!tb->ctl)
goto err_destroy_wq;
- tb->dev.parent = &nhi->pdev->dev;
+ tb->dev.parent = nhi->dev;
tb->dev.bus = &tb_bus_type;
tb->dev.type = &tb_domain_type;
tb->dev.groups = domain_attr_groups;
@@ -850,10 +853,41 @@ int tb_domain_disconnect_all_paths(struct tb *tb)
return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
}
+struct unregister_context {
+ const struct tb *tb;
+ int n;
+};
+
+static int unregister_unplugged_xdomain(struct device *dev, void *data)
+{
+ struct unregister_context *ctx = data;
+ struct tb_xdomain *xd;
+
+ xd = tb_to_xdomain(dev);
+ if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
+ tb_xdomain_unregister(xd);
+ ctx->n++;
+ }
+ return 0;
+}
+
+int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
+{
+ struct unregister_context ctx;
+
+ ctx.tb = tb_domain_get(tb);
+ ctx.n = 0;
+ bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
+ tb_domain_put(tb);
+
+ return ctx.n;
+}
+
int tb_domain_init(void)
{
int ret;
+ tb_configfs_init();
tb_debugfs_init();
tb_acpi_init();
@@ -883,4 +917,5 @@ void tb_domain_exit(void)
tb_xdomain_exit();
tb_acpi_exit();
tb_debugfs_exit();
+ tb_configfs_exit();
}
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 5477b9437048..5681c17f82ec 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -465,7 +465,7 @@ static void tb_switch_drom_free(struct tb_switch *sw)
*/
static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
{
- struct device *dev = &sw->tb->nhi->pdev->dev;
+ struct device *dev = sw->tb->nhi->dev;
int len, res;
len = device_property_count_u8(dev, "ThunderboltDROM");
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index 9d95bf3ab44c..10fefac3b1d9 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -587,6 +587,11 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
struct icm_fr_pkg_approve_xdomain request;
int ret;
+ if (atomic_read(&xd->ntunnels) >= 1) {
+ tb_warn(tb, "only one tunnel is supported by the firmware\n");
+ return -EOPNOTSUPP;
+ }
+
memset(&request, 0, sizeof(request));
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link;
@@ -738,6 +743,7 @@ static void remove_xdomain(struct tb_xdomain *xd)
sw = tb_to_switch(xd->dev.parent);
tb_port_at(xd->route, sw)->xdomain = NULL;
+ xd->is_unplugged = true;
tb_xdomain_remove(xd);
}
@@ -1157,6 +1163,11 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
struct icm_tr_pkg_approve_xdomain request;
int ret;
+ if (atomic_read(&xd->ntunnels) >= 1) {
+ tb_warn(tb, "only one tunnel is supported by the firmware\n");
+ return -EOPNOTSUPP;
+ }
+
memset(&request, 0, sizeof(request));
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.route_hi = upper_32_bits(xd->route);
@@ -1455,6 +1466,7 @@ static struct pci_dev *get_upstream_port(struct pci_dev *pdev)
static bool icm_ar_is_supported(struct tb *tb)
{
+ struct pci_dev *pdev = to_pci_dev(tb->nhi->dev);
struct pci_dev *upstream_port;
struct icm *icm = tb_priv(tb);
@@ -1472,7 +1484,7 @@ static bool icm_ar_is_supported(struct tb *tb)
* Find the upstream PCIe port in case we need to do reset
* through its vendor specific registers.
*/
- upstream_port = get_upstream_port(tb->nhi->pdev);
+ upstream_port = get_upstream_port(pdev);
if (upstream_port) {
int cap;
@@ -1508,7 +1520,7 @@ static int icm_ar_get_mode(struct tb *tb)
} while (--retries);
if (!retries) {
- dev_err(&nhi->pdev->dev, "ICM firmware not authenticated\n");
+ dev_err(nhi->dev, "ICM firmware not authenticated\n");
return -ENODEV;
}
@@ -1674,11 +1686,11 @@ icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level,
static void icm_icl_set_uuid(struct tb *tb)
{
- struct tb_nhi *nhi = tb->nhi;
+ struct pci_dev *pdev = to_pci_dev(tb->nhi->dev);
u32 uuid[4];
- pci_read_config_dword(nhi->pdev, VS_CAP_10, &uuid[0]);
- pci_read_config_dword(nhi->pdev, VS_CAP_11, &uuid[1]);
+ pci_read_config_dword(pdev, VS_CAP_10, &uuid[0]);
+ pci_read_config_dword(pdev, VS_CAP_11, &uuid[1]);
uuid[2] = 0xffffffff;
uuid[3] = 0xffffffff;
@@ -1762,6 +1774,8 @@ static void icm_handle_notification(struct work_struct *work)
kfree(n->pkg);
kfree(n);
+
+ tb_domain_unregister_unplugged_xdomains(tb);
}
static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
@@ -1853,7 +1867,7 @@ static int icm_firmware_start(struct tb *tb, struct tb_nhi *nhi)
if (icm_firmware_running(nhi))
return 0;
- dev_dbg(&nhi->pdev->dev, "starting ICM firmware\n");
+ dev_dbg(nhi->dev, "starting ICM firmware\n");
ret = icm_firmware_reset(tb, nhi);
if (ret)
@@ -1948,7 +1962,7 @@ static int icm_firmware_init(struct tb *tb)
ret = icm_firmware_start(tb, nhi);
if (ret) {
- dev_err(&nhi->pdev->dev, "could not start ICM firmware\n");
+ dev_err(nhi->dev, "could not start ICM firmware\n");
return ret;
}
@@ -1980,10 +1994,10 @@ static int icm_firmware_init(struct tb *tb)
*/
ret = icm_reset_phy_port(tb, 0);
if (ret)
- dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n");
+ dev_warn(nhi->dev, "failed to reset links on port0\n");
ret = icm_reset_phy_port(tb, 1);
if (ret)
- dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n");
+ dev_warn(nhi->dev, "failed to reset links on port1\n");
return 0;
}
@@ -2112,6 +2126,8 @@ static void icm_rescan_work(struct work_struct *work)
if (tb->root_switch)
icm_free_unplugged_children(tb->root_switch);
mutex_unlock(&tb->lock);
+
+ tb_domain_unregister_unplugged_xdomains(tb);
}
static void icm_complete(struct tb *tb)
@@ -2462,6 +2478,7 @@ static const struct tb_cm_ops icm_icl_ops = {
struct tb *icm_probe(struct tb_nhi *nhi)
{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
struct icm *icm;
struct tb *tb;
@@ -2473,7 +2490,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
INIT_DELAYED_WORK(&icm->rescan_work, icm_rescan_work);
mutex_init(&icm->request_lock);
- switch (nhi->pdev->device) {
+ switch (pdev->device) {
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
icm->can_upgrade_nvm = true;
@@ -2579,7 +2596,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
}
if (!icm->is_supported || !icm->is_supported(tb)) {
- dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n");
+ dev_dbg(nhi->dev, "ICM not supported on this controller\n");
tb_domain_put(tb);
return NULL;
}
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 2bb2e79ca3cb..0f795ea58756 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -2,7 +2,7 @@
/*
* Thunderbolt driver - NHI driver
*
- * The NHI (native host interface) is the pci device that allows us to send and
+ * The NHI (native host interface) is the device that allows us to send and
* receive frames from the thunderbolt bus.
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
@@ -12,7 +12,6 @@
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/errno.h>
-#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
@@ -34,19 +33,9 @@
* transferred.
*/
#define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID
-/*
- * Minimal number of vectors when we use MSI-X. Two for control channel
- * Rx/Tx and the rest four are for cross domain DMA paths.
- */
-#define MSIX_MIN_VECS 6
-#define MSIX_MAX_VECS 16
#define NHI_MAILBOX_TIMEOUT 500 /* ms */
-/* Host interface quirks */
-#define QUIRK_AUTO_CLEAR_INT BIT(0)
-#define QUIRK_E2E BIT(1)
-
static bool host_reset = true;
module_param(host_reset, bool, 0444);
MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)");
@@ -93,7 +82,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
u32 old, new;
if (ring->irq > 0) {
- u32 step, shift, ivr, misc;
+ u32 step, shift, ivr, misc, itr;
void __iomem *ivr_base;
int auto_clear_bit;
int index;
@@ -131,6 +120,12 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
if (active)
ivr |= ring->vector << shift;
iowrite32(ivr, ivr_base + step);
+
+ /* Throttling is specified in 256ns increments */
+ itr = DIV_ROUND_UP(ring->interval_nsec, 256);
+ itr &= REG_INT_THROTTLING_RATE_INTERVAL_MASK;
+ iowrite32(itr, ring->nhi->iobase + REG_INT_THROTTLING_RATE +
+ ring->vector * 4);
}
old = ioread32(ring->nhi->iobase + reg);
@@ -139,15 +134,14 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
else
new = old & ~mask;
- dev_dbg(&ring->nhi->pdev->dev,
+ dev_dbg(ring->nhi->dev,
"%s interrupt at register %#x bit %d (%#x -> %#x)\n",
active ? "enabling" : "disabling", reg, interrupt_bit, old, new);
if (new == old)
- dev_WARN(&ring->nhi->pdev->dev,
- "interrupt for %s %d is already %s\n",
- RING_TYPE(ring), ring->hop,
- str_enabled_disabled(active));
+ dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n",
+ RING_TYPE(ring), ring->hop,
+ str_enabled_disabled(active));
if (active)
iowrite32(new, ring->nhi->iobase + reg);
@@ -160,7 +154,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
*
* Use only during init and shutdown.
*/
-static void nhi_disable_interrupts(struct tb_nhi *nhi)
+void nhi_disable_interrupts(struct tb_nhi *nhi)
{
int i = 0;
/* disable interrupts */
@@ -319,6 +313,8 @@ invoke_callback:
if (frame->callback)
frame->callback(ring, frame, canceled);
}
+
+ wake_up(&ring->wait);
}
int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame)
@@ -445,7 +441,7 @@ static void ring_clear_msix(const struct tb_ring *ring)
4 * (ring->nhi->hop_count / 32));
}
-static irqreturn_t ring_msix(int irq, void *data)
+irqreturn_t ring_msix(int irq, void *data)
{
struct tb_ring *ring = data;
@@ -459,51 +455,6 @@ static irqreturn_t ring_msix(int irq, void *data)
return IRQ_HANDLED;
}
-static int ring_request_msix(struct tb_ring *ring, bool no_suspend)
-{
- struct tb_nhi *nhi = ring->nhi;
- unsigned long irqflags;
- int ret;
-
- if (!nhi->pdev->msix_enabled)
- return 0;
-
- ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL);
- if (ret < 0)
- return ret;
-
- ring->vector = ret;
-
- ret = pci_irq_vector(ring->nhi->pdev, ring->vector);
- if (ret < 0)
- goto err_ida_remove;
-
- ring->irq = ret;
-
- irqflags = no_suspend ? IRQF_NO_SUSPEND : 0;
- ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring);
- if (ret)
- goto err_ida_remove;
-
- return 0;
-
-err_ida_remove:
- ida_free(&nhi->msix_ida, ring->vector);
-
- return ret;
-}
-
-static void ring_release_msix(struct tb_ring *ring)
-{
- if (ring->irq <= 0)
- return;
-
- free_irq(ring->irq, ring);
- ida_free(&ring->nhi->msix_ida, ring->vector);
- ring->vector = 0;
- ring->irq = 0;
-}
-
static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
{
unsigned int start_hop = RING_FIRST_USABLE_HOPID;
@@ -512,7 +463,7 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
if (nhi->quirks & QUIRK_E2E) {
start_hop = RING_FIRST_USABLE_HOPID + 1;
if (ring->flags & RING_FLAG_E2E && !ring->is_tx) {
- dev_dbg(&nhi->pdev->dev, "quirking E2E TX HopID %u -> %u\n",
+ dev_dbg(nhi->dev, "quirking E2E TX HopID %u -> %u\n",
ring->e2e_tx_hop, RING_E2E_RESERVED_HOPID);
ring->e2e_tx_hop = RING_E2E_RESERVED_HOPID;
}
@@ -543,23 +494,23 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
}
if (ring->hop > 0 && ring->hop < start_hop) {
- dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop);
+ dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop);
ret = -EINVAL;
goto err_unlock;
}
if (ring->hop < 0 || ring->hop >= nhi->hop_count) {
- dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop);
+ dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop);
ret = -EINVAL;
goto err_unlock;
}
if (ring->is_tx && nhi->tx_rings[ring->hop]) {
- dev_warn(&nhi->pdev->dev, "TX hop %d already allocated\n",
+ dev_warn(nhi->dev, "TX hop %d already allocated\n",
ring->hop);
ret = -EBUSY;
goto err_unlock;
}
if (!ring->is_tx && nhi->rx_rings[ring->hop]) {
- dev_warn(&nhi->pdev->dev, "RX hop %d already allocated\n",
+ dev_warn(nhi->dev, "RX hop %d already allocated\n",
ring->hop);
ret = -EBUSY;
goto err_unlock;
@@ -584,7 +535,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
{
struct tb_ring *ring = NULL;
- dev_dbg(&nhi->pdev->dev, "allocating %s ring %d of size %d\n",
+ dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n",
transmit ? "TX" : "RX", hop, size);
ring = kzalloc_obj(*ring);
@@ -595,6 +546,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
INIT_LIST_HEAD(&ring->queue);
INIT_LIST_HEAD(&ring->in_flight);
INIT_WORK(&ring->work, ring_work);
+ init_waitqueue_head(&ring->wait);
ring->nhi = nhi;
ring->hop = hop;
@@ -610,14 +562,16 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
ring->start_poll = start_poll;
ring->poll_data = poll_data;
- ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev,
- size * sizeof(*ring->descriptors),
- &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO);
+ ring->descriptors = dma_alloc_coherent(ring->nhi->dev,
+ size * sizeof(*ring->descriptors),
+ &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO);
if (!ring->descriptors)
goto err_free_ring;
- if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND))
- goto err_free_descs;
+ if (nhi->ops->request_ring_irq) {
+ if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND))
+ goto err_free_descs;
+ }
if (nhi_alloc_hop(nhi, ring))
goto err_release_msix;
@@ -625,9 +579,10 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
return ring;
err_release_msix:
- ring_release_msix(ring);
+ if (nhi->ops->release_ring_irq)
+ nhi->ops->release_ring_irq(ring);
err_free_descs:
- dma_free_coherent(&ring->nhi->pdev->dev,
+ dma_free_coherent(ring->nhi->dev,
ring->size * sizeof(*ring->descriptors),
ring->descriptors, ring->descriptors_dma);
err_free_ring:
@@ -694,10 +649,10 @@ void tb_ring_start(struct tb_ring *ring)
if (ring->nhi->going_away)
goto err;
if (ring->running) {
- dev_WARN(&ring->nhi->pdev->dev, "ring already started\n");
+ dev_WARN(ring->nhi->dev, "ring already started\n");
goto err;
}
- dev_dbg(&ring->nhi->pdev->dev, "starting %s %d\n",
+ dev_dbg(ring->nhi->dev, "starting %s %d\n",
RING_TYPE(ring), ring->hop);
if (ring->flags & RING_FLAG_FRAME) {
@@ -734,11 +689,11 @@ void tb_ring_start(struct tb_ring *ring)
hop &= REG_RX_OPTIONS_E2E_HOP_MASK;
flags |= hop;
- dev_dbg(&ring->nhi->pdev->dev,
+ dev_dbg(ring->nhi->dev,
"enabling E2E for %s %d with TX HopID %d\n",
RING_TYPE(ring), ring->hop, ring->e2e_tx_hop);
} else {
- dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d\n",
+ dev_dbg(ring->nhi->dev, "enabling E2E for %s %d\n",
RING_TYPE(ring), ring->hop);
}
@@ -754,6 +709,31 @@ err:
}
EXPORT_SYMBOL_GPL(tb_ring_start);
+static bool tb_ring_empty(struct tb_ring *ring)
+{
+ guard(spinlock_irqsave)(&ring->lock);
+ return list_empty(&ring->in_flight);
+}
+
+/**
+ * tb_ring_flush() - Waits for a ring to be empty
+ * @ring: Ring to wait
+ * @timeout_msec: Timeout in ms how long to wait.
+ *
+ * This can be called before stopping a ring to make sure all the frames
+ * submitted prior have been completed.
+ *
+ * Return: %true if the ring is empty now, %false otherwise.
+ */
+bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec)
+{
+ if (!wait_event_timeout(ring->wait, tb_ring_empty(ring),
+ msecs_to_jiffies(timeout_msec)))
+ return false;
+ return tb_ring_empty(ring);
+}
+EXPORT_SYMBOL_GPL(tb_ring_flush);
+
/**
* tb_ring_stop() - shutdown a ring
* @ring: Ring to stop
@@ -772,12 +752,12 @@ void tb_ring_stop(struct tb_ring *ring)
{
spin_lock_irq(&ring->nhi->lock);
spin_lock(&ring->lock);
- dev_dbg(&ring->nhi->pdev->dev, "stopping %s %d\n",
+ dev_dbg(ring->nhi->dev, "stopping %s %d\n",
RING_TYPE(ring), ring->hop);
if (ring->nhi->going_away)
goto err;
if (!ring->running) {
- dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n",
+ dev_WARN(ring->nhi->dev, "%s %d already stopped\n",
RING_TYPE(ring), ring->hop);
goto err;
}
@@ -815,6 +795,8 @@ EXPORT_SYMBOL_GPL(tb_ring_stop);
*/
void tb_ring_free(struct tb_ring *ring)
{
+ struct tb_nhi *nhi = ring->nhi;
+
spin_lock_irq(&ring->nhi->lock);
/*
* Dissociate the ring from the NHI. This also ensures that
@@ -826,14 +808,15 @@ void tb_ring_free(struct tb_ring *ring)
ring->nhi->rx_rings[ring->hop] = NULL;
if (ring->running) {
- dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n",
+ dev_WARN(ring->nhi->dev, "%s %d still running\n",
RING_TYPE(ring), ring->hop);
}
spin_unlock_irq(&ring->nhi->lock);
- ring_release_msix(ring);
+ if (nhi->ops->release_ring_irq)
+ nhi->ops->release_ring_irq(ring);
- dma_free_coherent(&ring->nhi->pdev->dev,
+ dma_free_coherent(ring->nhi->dev,
ring->size * sizeof(*ring->descriptors),
ring->descriptors, ring->descriptors_dma);
@@ -841,7 +824,7 @@ void tb_ring_free(struct tb_ring *ring)
ring->descriptors_dma = 0;
- dev_dbg(&ring->nhi->pdev->dev, "freeing %s %d\n", RING_TYPE(ring),
+ dev_dbg(ring->nhi->dev, "freeing %s %d\n", RING_TYPE(ring),
ring->hop);
/*
@@ -855,6 +838,26 @@ void tb_ring_free(struct tb_ring *ring)
EXPORT_SYMBOL_GPL(tb_ring_free);
/**
+ * tb_ring_throttling() - Configure throttling for ring interrupt
+ * @ring: Ring to configure
+ * @interval_nsec: Interval counter for moderation (in ns), %0 disables
+ *
+ * Enables or disables ring interrupt throttling. The ring must be
+ * stopped for this to be called. Granularity is 256 ns.
+ *
+ * Return: %0 on success, negative errno otherwise.
+ */
+int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec)
+{
+ guard(spinlock_irqsave)(&ring->lock);
+ if (WARN_ON_ONCE(ring->running))
+ return -EBUSY;
+ ring->interval_nsec = interval_nsec;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tb_ring_throttling);
+
+/**
* nhi_mailbox_cmd() - Send a command through NHI mailbox
* @nhi: Pointer to the NHI structure
* @cmd: Command to send
@@ -912,7 +915,7 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
return (enum nhi_fw_mode)val;
}
-static void nhi_interrupt_work(struct work_struct *work)
+void nhi_interrupt_work(struct work_struct *work)
{
struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work);
int value = 0; /* Suppress uninitialized usage warning. */
@@ -940,9 +943,7 @@ static void nhi_interrupt_work(struct work_struct *work)
if ((value & (1 << (bit % 32))) == 0)
continue;
if (type == 2) {
- dev_warn(&nhi->pdev->dev,
- "RX overflow for ring %d\n",
- hop);
+ dev_warn(nhi->dev, "RX overflow for ring %d\n", hop);
continue;
}
if (type == 0)
@@ -950,7 +951,7 @@ static void nhi_interrupt_work(struct work_struct *work)
else
ring = nhi->rx_rings[hop];
if (ring == NULL) {
- dev_warn(&nhi->pdev->dev,
+ dev_warn(nhi->dev,
"got interrupt for inactive %s ring %d\n",
type ? "RX" : "TX",
hop);
@@ -964,7 +965,7 @@ static void nhi_interrupt_work(struct work_struct *work)
spin_unlock_irq(&nhi->lock);
}
-static irqreturn_t nhi_msi(int irq, void *data)
+irqreturn_t nhi_msi(int irq, void *data)
{
struct tb_nhi *nhi = data;
schedule_work(&nhi->interrupt_work);
@@ -973,8 +974,7 @@ static irqreturn_t nhi_msi(int irq, void *data)
static int __nhi_suspend_noirq(struct device *dev, bool wakeup)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
struct tb_nhi *nhi = tb->nhi;
int ret;
@@ -982,7 +982,7 @@ static int __nhi_suspend_noirq(struct device *dev, bool wakeup)
if (ret)
return ret;
- if (nhi->ops && nhi->ops->suspend_noirq) {
+ if (nhi->ops->suspend_noirq) {
ret = nhi->ops->suspend_noirq(tb->nhi, wakeup);
if (ret)
return ret;
@@ -998,21 +998,19 @@ static int nhi_suspend_noirq(struct device *dev)
static int nhi_freeze_noirq(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
return tb_domain_freeze_noirq(tb);
}
static int nhi_thaw_noirq(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
return tb_domain_thaw_noirq(tb);
}
-static bool nhi_wake_supported(struct pci_dev *pdev)
+static bool nhi_wake_supported(struct device *dev)
{
u8 val;
@@ -1020,7 +1018,7 @@ static bool nhi_wake_supported(struct pci_dev *pdev)
* If power rails are sustainable for wakeup from S4 this
* property is set by the BIOS.
*/
- if (!device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val))
+ if (!device_property_read_u8(dev, "WAKE_SUPPORTED", &val))
return !!val;
return true;
@@ -1028,33 +1026,15 @@ static bool nhi_wake_supported(struct pci_dev *pdev)
static int nhi_poweroff_noirq(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
bool wakeup;
- wakeup = device_may_wakeup(dev) && nhi_wake_supported(pdev);
+ wakeup = device_may_wakeup(dev) && nhi_wake_supported(dev);
return __nhi_suspend_noirq(dev, wakeup);
}
-static void nhi_enable_int_throttling(struct tb_nhi *nhi)
-{
- /* Throttling is specified in 256ns increments */
- u32 throttle = DIV_ROUND_UP(128 * NSEC_PER_USEC, 256);
- unsigned int i;
-
- /*
- * Configure interrupt throttling for all vectors even if we
- * only use few.
- */
- for (i = 0; i < MSIX_MAX_VECS; i++) {
- u32 reg = REG_INT_THROTTLING_RATE + i * 4;
- iowrite32(throttle, nhi->iobase + reg);
- }
-}
-
static int nhi_resume_noirq(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
struct tb_nhi *nhi = tb->nhi;
int ret;
@@ -1063,15 +1043,12 @@ static int nhi_resume_noirq(struct device *dev)
* unplugged last device which causes the host controller to go
* away on PCs.
*/
- if (!pci_device_is_present(pdev)) {
+ if ((nhi->ops->is_present && !nhi->ops->is_present(nhi))) {
nhi->going_away = true;
- } else {
- if (nhi->ops && nhi->ops->resume_noirq) {
- ret = nhi->ops->resume_noirq(nhi);
- if (ret)
- return ret;
- }
- nhi_enable_int_throttling(tb->nhi);
+ } else if (nhi->ops->resume_noirq) {
+ ret = nhi->ops->resume_noirq(nhi);
+ if (ret)
+ return ret;
}
return tb_domain_resume_noirq(tb);
@@ -1079,32 +1056,29 @@ static int nhi_resume_noirq(struct device *dev)
static int nhi_suspend(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
return tb_domain_suspend(tb);
}
static void nhi_complete(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
/*
* If we were runtime suspended when system suspend started,
* schedule runtime resume now. It should bring the domain back
* to functional state.
*/
- if (pm_runtime_suspended(&pdev->dev))
- pm_runtime_resume(&pdev->dev);
+ if (pm_runtime_suspended(dev))
+ pm_runtime_resume(dev);
else
tb_domain_complete(tb);
}
static int nhi_runtime_suspend(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
struct tb_nhi *nhi = tb->nhi;
int ret;
@@ -1112,7 +1086,7 @@ static int nhi_runtime_suspend(struct device *dev)
if (ret)
return ret;
- if (nhi->ops && nhi->ops->runtime_suspend) {
+ if (nhi->ops->runtime_suspend) {
ret = nhi->ops->runtime_suspend(tb->nhi);
if (ret)
return ret;
@@ -1122,115 +1096,39 @@ static int nhi_runtime_suspend(struct device *dev)
static int nhi_runtime_resume(struct device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- struct tb *tb = pci_get_drvdata(pdev);
+ struct tb *tb = dev_get_drvdata(dev);
struct tb_nhi *nhi = tb->nhi;
int ret;
- if (nhi->ops && nhi->ops->runtime_resume) {
+ if (nhi->ops->runtime_resume) {
ret = nhi->ops->runtime_resume(nhi);
if (ret)
return ret;
}
- nhi_enable_int_throttling(nhi);
return tb_domain_runtime_resume(tb);
}
-static void nhi_shutdown(struct tb_nhi *nhi)
+void nhi_shutdown(struct tb_nhi *nhi)
{
int i;
- dev_dbg(&nhi->pdev->dev, "shutdown\n");
+ dev_dbg(nhi->dev, "shutdown\n");
for (i = 0; i < nhi->hop_count; i++) {
if (nhi->tx_rings[i])
- dev_WARN(&nhi->pdev->dev,
+ dev_WARN(nhi->dev,
"TX ring %d is still active\n", i);
if (nhi->rx_rings[i])
- dev_WARN(&nhi->pdev->dev,
+ dev_WARN(nhi->dev,
"RX ring %d is still active\n", i);
}
nhi_disable_interrupts(nhi);
- /*
- * We have to release the irq before calling flush_work. Otherwise an
- * already executing IRQ handler could call schedule_work again.
- */
- if (!nhi->pdev->msix_enabled) {
- devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi);
- flush_work(&nhi->interrupt_work);
- }
- ida_destroy(&nhi->msix_ida);
- if (nhi->ops && nhi->ops->shutdown)
+ if (nhi->ops->shutdown)
nhi->ops->shutdown(nhi);
}
-static void nhi_check_quirks(struct tb_nhi *nhi)
-{
- if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL) {
- /*
- * Intel hardware supports auto clear of the interrupt
- * status register right after interrupt is being
- * issued.
- */
- nhi->quirks |= QUIRK_AUTO_CLEAR_INT;
-
- switch (nhi->pdev->device) {
- case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
- case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
- /*
- * Falcon Ridge controller needs the end-to-end
- * flow control workaround to avoid losing Rx
- * packets when RING_FLAG_E2E is set.
- */
- nhi->quirks |= QUIRK_E2E;
- break;
- }
- }
-}
-
-static int nhi_check_iommu_pdev(struct pci_dev *pdev, void *data)
-{
- if (!pdev->external_facing ||
- !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION))
- return 0;
- *(bool *)data = true;
- return 1; /* Stop walking */
-}
-
-static void nhi_check_iommu(struct tb_nhi *nhi)
-{
- struct pci_bus *bus = nhi->pdev->bus;
- bool port_ok = false;
-
- /*
- * Ideally what we'd do here is grab every PCI device that
- * represents a tunnelling adapter for this NHI and check their
- * status directly, but unfortunately USB4 seems to make it
- * obnoxiously difficult to reliably make any correlation.
- *
- * So for now we'll have to bodge it... Hoping that the system
- * is at least sane enough that an adapter is in the same PCI
- * segment as its NHI, if we can find *something* on that segment
- * which meets the requirements for Kernel DMA Protection, we'll
- * take that to imply that firmware is aware and has (hopefully)
- * done the right thing in general. We need to know that the PCI
- * layer has seen the ExternalFacingPort property which will then
- * inform the IOMMU layer to enforce the complete "untrusted DMA"
- * flow, but also that the IOMMU driver itself can be trusted not
- * to have been subverted by a pre-boot DMA attack.
- */
- while (bus->parent)
- bus = bus->parent;
-
- pci_walk_bus(bus, nhi_check_iommu_pdev, &port_ok);
-
- nhi->iommu_dma_protection = port_ok;
- dev_dbg(&nhi->pdev->dev, "IOMMU DMA protection is %s\n",
- str_enabled_disabled(port_ok));
-}
-
static void nhi_reset(struct tb_nhi *nhi)
{
ktime_t timeout;
@@ -1242,7 +1140,7 @@ static void nhi_reset(struct tb_nhi *nhi)
return;
if (!host_reset) {
- dev_dbg(&nhi->pdev->dev, "skipping host router reset\n");
+ dev_dbg(nhi->dev, "skipping host router reset\n");
return;
}
@@ -1253,64 +1151,13 @@ static void nhi_reset(struct tb_nhi *nhi)
do {
val = ioread32(nhi->iobase + REG_RESET);
if (!(val & REG_RESET_HRR)) {
- dev_warn(&nhi->pdev->dev, "host router reset successful\n");
+ dev_warn(nhi->dev, "host router reset successful\n");
return;
}
usleep_range(10, 20);
} while (ktime_before(ktime_get(), timeout));
- dev_warn(&nhi->pdev->dev, "timeout resetting host router\n");
-}
-
-static int nhi_init_msi(struct tb_nhi *nhi)
-{
- struct pci_dev *pdev = nhi->pdev;
- struct device *dev = &pdev->dev;
- int res, irq, nvec;
-
- /* In case someone left them on. */
- nhi_disable_interrupts(nhi);
-
- nhi_enable_int_throttling(nhi);
-
- ida_init(&nhi->msix_ida);
-
- /*
- * The NHI has 16 MSI-X vectors or a single MSI. We first try to
- * get all MSI-X vectors and if we succeed, each ring will have
- * one MSI-X. If for some reason that does not work out, we
- * fallback to a single MSI.
- */
- nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS,
- PCI_IRQ_MSIX);
- if (nvec < 0) {
- nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
- if (nvec < 0)
- return nvec;
-
- INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
-
- irq = pci_irq_vector(nhi->pdev, 0);
- if (irq < 0)
- return irq;
-
- res = devm_request_irq(&pdev->dev, irq, nhi_msi,
- IRQF_NO_SUSPEND, "thunderbolt", nhi);
- if (res)
- return dev_err_probe(dev, res, "request_irq failed, aborting\n");
- }
-
- return 0;
-}
-
-static bool nhi_imr_valid(struct pci_dev *pdev)
-{
- u8 val;
-
- if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val))
- return !!val;
-
- return true;
+ dev_warn(nhi->dev, "timeout resetting host router\n");
}
static struct tb *nhi_select_cm(struct tb_nhi *nhi)
@@ -1336,62 +1183,47 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi)
return tb;
}
-static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+int nhi_probe(struct tb_nhi *nhi)
{
- struct device *dev = &pdev->dev;
- struct tb_nhi *nhi;
+ struct device *dev = nhi->dev;
struct tb *tb;
int res;
- if (!nhi_imr_valid(pdev))
- return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n");
+ if (!nhi->ops)
+ return dev_err_probe(dev, -EINVAL, "NHI ops not set\n");
- res = pcim_enable_device(pdev);
- if (res)
- return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n");
-
- nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL);
- if (!nhi)
- return -ENOMEM;
-
- nhi->pdev = pdev;
- nhi->ops = (const struct tb_nhi_ops *)id->driver_data;
-
- nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt");
- res = PTR_ERR_OR_ZERO(nhi->iobase);
- if (res)
- return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
+ if (!nhi->ops->init_interrupts)
+ return dev_err_probe(dev, -EINVAL, "missing required NHI ops\n");
nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff;
dev_dbg(dev, "total paths: %d\n", nhi->hop_count);
- nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count,
+ nhi->tx_rings = devm_kcalloc(dev, nhi->hop_count,
sizeof(*nhi->tx_rings), GFP_KERNEL);
- nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count,
+ nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count,
sizeof(*nhi->rx_rings), GFP_KERNEL);
if (!nhi->tx_rings || !nhi->rx_rings)
return -ENOMEM;
- nhi_check_quirks(nhi);
- nhi_check_iommu(nhi);
nhi_reset(nhi);
- res = nhi_init_msi(nhi);
+ /* In case someone left them on. */
+ nhi_disable_interrupts(nhi);
+
+ res = nhi->ops->init_interrupts(nhi);
if (res)
- return dev_err_probe(dev, res, "cannot enable MSI, aborting\n");
+ return dev_err_probe(dev, res, "cannot enable interrupts, aborting\n");
spin_lock_init(&nhi->lock);
- res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ res = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (res)
return dev_err_probe(dev, res, "failed to set DMA mask\n");
- pci_set_master(pdev);
-
- if (nhi->ops && nhi->ops->init) {
+ if (nhi->ops->init) {
res = nhi->ops->init(nhi);
if (res)
- return res;
+ return dev_err_probe(dev, res, "NHI specific init failed\n");
}
tb = nhi_select_cm(nhi);
@@ -1401,6 +1233,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
+ init_completion(&nhi->domain_released);
+
res = tb_domain_add(tb, host_reset);
if (res) {
/*
@@ -1408,40 +1242,28 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
* activated. Do a proper shutdown.
*/
tb_domain_put(tb);
+ wait_for_completion(&nhi->domain_released);
nhi_shutdown(nhi);
- return res;
+ return dev_err_probe(dev, res, "failed to add domain\n");
}
- pci_set_drvdata(pdev, tb);
+ dev_set_drvdata(dev, tb);
- device_wakeup_enable(&pdev->dev);
+ device_wakeup_enable(dev);
- pm_runtime_allow(&pdev->dev);
- pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY);
- pm_runtime_use_autosuspend(&pdev->dev);
- pm_runtime_put_autosuspend(&pdev->dev);
+ pm_runtime_allow(dev);
+ pm_runtime_set_autosuspend_delay(dev, TB_AUTOSUSPEND_DELAY);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_put_autosuspend(dev);
return 0;
}
-static void nhi_remove(struct pci_dev *pdev)
-{
- struct tb *tb = pci_get_drvdata(pdev);
- struct tb_nhi *nhi = tb->nhi;
-
- pm_runtime_get_sync(&pdev->dev);
- pm_runtime_dont_use_autosuspend(&pdev->dev);
- pm_runtime_forbid(&pdev->dev);
-
- tb_domain_remove(tb);
- nhi_shutdown(nhi);
-}
-
/*
* The tunneled pci bridges are siblings of us. Use resume_noirq to reenable
* the tunnels asap. A corresponding pci quirk blocks the downstream bridges
* resume_noirq until we are done.
*/
-static const struct dev_pm_ops nhi_pm_ops = {
+const struct dev_pm_ops nhi_pm_ops = {
.suspend_noirq = nhi_suspend_noirq,
.resume_noirq = nhi_resume_noirq,
.freeze_noirq = nhi_freeze_noirq, /*
@@ -1457,129 +1279,3 @@ static const struct dev_pm_ops nhi_pm_ops = {
.runtime_suspend = nhi_runtime_suspend,
.runtime_resume = nhi_runtime_resume,
};
-
-static struct pci_device_id nhi_ids[] = {
- /*
- * We have to specify class, the TB bridges use the same device and
- * vendor (sub)id on gen 1 and gen 2 controllers.
- */
- {
- .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
- .vendor = PCI_VENDOR_ID_INTEL,
- .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
- .subvendor = 0x2222, .subdevice = 0x1111,
- },
- {
- .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
- .vendor = PCI_VENDOR_ID_INTEL,
- .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
- .subvendor = 0x2222, .subdevice = 0x1111,
- },
- {
- .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
- .vendor = PCI_VENDOR_ID_INTEL,
- .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI,
- .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
- },
- {
- .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
- .vendor = PCI_VENDOR_ID_INTEL,
- .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI,
- .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
- },
-
- /* Thunderbolt 3 */
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- /* Thunderbolt 4 */
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0),
- .driver_data = (kernel_ulong_t)&icl_nhi_ops },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) },
- { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) },
-
- /* Any USB4 compliant host */
- { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) },
-
- { 0,}
-};
-
-MODULE_DEVICE_TABLE(pci, nhi_ids);
-MODULE_DESCRIPTION("Thunderbolt/USB4 core driver");
-MODULE_LICENSE("GPL");
-
-static struct pci_driver nhi_driver = {
- .name = "thunderbolt",
- .id_table = nhi_ids,
- .probe = nhi_probe,
- .remove = nhi_remove,
- .shutdown = nhi_remove,
- .driver.pm = &nhi_pm_ops,
-};
-
-static int __init nhi_init(void)
-{
- int ret;
-
- ret = tb_domain_init();
- if (ret)
- return ret;
- ret = pci_register_driver(&nhi_driver);
- if (ret)
- tb_domain_exit();
- return ret;
-}
-
-static void __exit nhi_unload(void)
-{
- pci_unregister_driver(&nhi_driver);
- tb_domain_exit();
-}
-
-rootfs_initcall(nhi_init);
-module_exit(nhi_unload);
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 24ac4246d0ca..d488eadadfce 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -29,6 +29,14 @@ enum nhi_mailbox_cmd {
int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data);
enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
+void nhi_enable_int_throttling(struct tb_nhi *nhi);
+void nhi_disable_interrupts(struct tb_nhi *nhi);
+void nhi_interrupt_work(struct work_struct *work);
+irqreturn_t nhi_msi(int irq, void *data);
+irqreturn_t ring_msix(int irq, void *data);
+int nhi_probe(struct tb_nhi *nhi);
+void nhi_shutdown(struct tb_nhi *nhi);
+extern const struct dev_pm_ops nhi_pm_ops;
/**
* struct tb_nhi_ops - NHI specific optional operations
@@ -38,6 +46,12 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
* @runtime_suspend: NHI specific runtime_suspend hook
* @runtime_resume: NHI specific runtime_resume hook
* @shutdown: NHI specific shutdown
+ * @pre_nvm_auth: hook to run before Thunderbolt 3 NVM authentication
+ * @post_nvm_auth: hook to run after Thunderbolt 3 NVM authentication
+ * @request_ring_irq: NHI specific interrupt retrieval hook
+ * @release_ring_irq: NHI specific interrupt release hook
+ * @is_present: Whether the device is currently present on the parent bus
+ * @init_interrupts: NHI specific interrupt initialization hook
*/
struct tb_nhi_ops {
int (*init)(struct tb_nhi *nhi);
@@ -46,10 +60,14 @@ struct tb_nhi_ops {
int (*runtime_suspend)(struct tb_nhi *nhi);
int (*runtime_resume)(struct tb_nhi *nhi);
void (*shutdown)(struct tb_nhi *nhi);
+ void (*pre_nvm_auth)(struct tb_nhi *nhi);
+ void (*post_nvm_auth)(struct tb_nhi *nhi);
+ int (*request_ring_irq)(struct tb_ring *ring, bool no_suspend);
+ void (*release_ring_irq)(struct tb_ring *ring);
+ bool (*is_present)(struct tb_nhi *nhi);
+ int (*init_interrupts)(struct tb_nhi *nhi);
};
-extern const struct tb_nhi_ops icl_nhi_ops;
-
/*
* PCI IDs used in this driver from Win Ridge forward. There is no
* need for the PCI quirk anymore as we will use ICM also on Apple
@@ -100,4 +118,15 @@ extern const struct tb_nhi_ops icl_nhi_ops;
#define PCI_CLASS_SERIAL_USB_USB4 0x0c0340
+/* Host interface quirks */
+#define QUIRK_AUTO_CLEAR_INT BIT(0)
+#define QUIRK_E2E BIT(1)
+
+/*
+ * Minimal number of vectors when we use MSI-X. Two for control channel
+ * Rx/Tx and the rest four are for cross domain DMA paths.
+ */
+#define MSIX_MIN_VECS 6
+#define MSIX_MAX_VECS 16
+
#endif
diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c
deleted file mode 100644
index 96da07e88c52..000000000000
--- a/drivers/thunderbolt/nhi_ops.c
+++ /dev/null
@@ -1,185 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * NHI specific operations
- *
- * Copyright (C) 2019, Intel Corporation
- * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
- */
-
-#include <linux/delay.h>
-#include <linux/suspend.h>
-
-#include "nhi.h"
-#include "nhi_regs.h"
-#include "tb.h"
-
-/* Ice Lake specific NHI operations */
-
-#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */
-
-static int check_for_device(struct device *dev, void *data)
-{
- return tb_is_switch(dev);
-}
-
-static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
-{
- struct tb *tb = pci_get_drvdata(nhi->pdev);
- int ret;
-
- ret = device_for_each_child(&tb->root_switch->dev, NULL,
- check_for_device);
- return ret > 0;
-}
-
-static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
-{
- u32 vs_cap;
-
- /*
- * The Thunderbolt host controller is present always in Ice Lake
- * but the firmware may not be loaded and running (depending
- * whether there is device connected and so on). Each time the
- * controller is used we need to "Force Power" it first and wait
- * for the firmware to indicate it is up and running. This "Force
- * Power" is really not about actually powering on/off the
- * controller so it is accessible even if "Force Power" is off.
- *
- * The actual power management happens inside shared ACPI power
- * resources using standard ACPI methods.
- */
- pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap);
- if (power) {
- vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
- vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
- vs_cap |= VS_CAP_22_FORCE_POWER;
- } else {
- vs_cap &= ~VS_CAP_22_FORCE_POWER;
- }
- pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
-
- if (power) {
- unsigned int retries = 350;
- u32 val;
-
- /* Wait until the firmware tells it is up and running */
- do {
- pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
- if (val & VS_CAP_9_FW_READY)
- return 0;
- usleep_range(3000, 3100);
- } while (--retries);
-
- return -ETIMEDOUT;
- }
-
- return 0;
-}
-
-static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
-{
- u32 data;
-
- data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
- pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);
-}
-
-static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
-{
- unsigned long end;
- u32 data;
-
- if (!timeout)
- goto clear;
-
- end = jiffies + msecs_to_jiffies(timeout);
- do {
- pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
- if (data & VS_CAP_18_DONE)
- goto clear;
- usleep_range(1000, 1100);
- } while (time_before(jiffies, end));
-
- return -ETIMEDOUT;
-
-clear:
- /* Clear the valid bit */
- pci_write_config_dword(nhi->pdev, VS_CAP_19, 0);
- return 0;
-}
-
-static void icl_nhi_set_ltr(struct tb_nhi *nhi)
-{
- u32 max_ltr, ltr;
-
- pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr);
- max_ltr &= 0xffff;
- /* Program the same value for both snoop and no-snoop */
- ltr = max_ltr << 16 | max_ltr;
- pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr);
-}
-
-static int icl_nhi_suspend(struct tb_nhi *nhi)
-{
- struct tb *tb = pci_get_drvdata(nhi->pdev);
- int ret;
-
- if (icl_nhi_is_device_connected(nhi))
- return 0;
-
- if (tb_switch_is_icm(tb->root_switch)) {
- /*
- * If there is no device connected we need to perform
- * both: a handshake through LC mailbox and force power
- * down before entering D3.
- */
- icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
- ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
- if (ret)
- return ret;
- }
-
- return icl_nhi_force_power(nhi, false);
-}
-
-static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
-{
- struct tb *tb = pci_get_drvdata(nhi->pdev);
- enum icl_lc_mailbox_cmd cmd;
-
- if (!pm_suspend_via_firmware())
- return icl_nhi_suspend(nhi);
-
- if (!tb_switch_is_icm(tb->root_switch))
- return 0;
-
- cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
- icl_nhi_lc_mailbox_cmd(nhi, cmd);
- return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
-}
-
-static int icl_nhi_resume(struct tb_nhi *nhi)
-{
- int ret;
-
- ret = icl_nhi_force_power(nhi, true);
- if (ret)
- return ret;
-
- icl_nhi_set_ltr(nhi);
- return 0;
-}
-
-static void icl_nhi_shutdown(struct tb_nhi *nhi)
-{
- icl_nhi_force_power(nhi, false);
-}
-
-const struct tb_nhi_ops icl_nhi_ops = {
- .init = icl_nhi_resume,
- .suspend_noirq = icl_nhi_suspend_noirq,
- .resume_noirq = icl_nhi_resume,
- .runtime_suspend = icl_nhi_suspend,
- .runtime_resume = icl_nhi_resume,
- .shutdown = icl_nhi_shutdown,
-};
diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h
index cf5222bee971..d6a197fabc74 100644
--- a/drivers/thunderbolt/nhi_regs.h
+++ b/drivers/thunderbolt/nhi_regs.h
@@ -101,7 +101,8 @@ struct ring_desc {
#define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208
-#define REG_INT_THROTTLING_RATE 0x38c00
+#define REG_INT_THROTTLING_RATE 0x38c00
+#define REG_INT_THROTTLING_RATE_INTERVAL_MASK GENMASK(15, 0)
/* Interrupt Vector Allocation */
#define REG_INT_VEC_ALLOC_BASE 0x38c40
diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
index 8713ea0f47c1..b2c322e76b8a 100644
--- a/drivers/thunderbolt/path.c
+++ b/drivers/thunderbolt/path.c
@@ -412,7 +412,8 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
* in the USB4 spec so we clear them
* only for pre-USB4 adapters.
*/
- if (!tb_switch_is_usb4(port->sw)) {
+ if (tb_port_is_null(port) ||
+ !tb_switch_is_usb4(port->sw)) {
hop.ingress_fc = 0;
hop.ingress_shared_buffer = 0;
}
@@ -483,7 +484,7 @@ void tb_path_deactivate(struct tb_path *path)
* tb_path_activate() - activate a path
* @path: Path to activate
*
- * Activate a path starting with the last hop and iterating backwards. The
+ * Activate a path starting with the first hop and ending on the last hop. The
* caller must fill path->hops before calling tb_path_activate().
*
* Return: %0 on success, negative errno otherwise.
@@ -525,22 +526,25 @@ int tb_path_activate(struct tb_path *path)
}
/* Activate hops. */
- for (i = path->path_length - 1; i >= 0; i--) {
+ for (i = 0; i < path->path_length; i++) {
struct tb_regs_hop hop = { 0 };
/* If it is left active deactivate it first */
__tb_path_deactivate_hop(path->hops[i].in_port,
path->hops[i].in_hop_index, path->clear_fc);
- /* dword 0 */
+ /* Needed for USB4 routers, read path config space before write */
+ res = tb_port_read(path->hops[i].in_port, &hop, TB_CFG_HOPS,
+ 2 * path->hops[i].in_hop_index, 2);
+ if (res)
+ goto err;
+
hop.next_hop = path->hops[i].next_hop_index;
hop.out_port = path->hops[i].out_port->port;
- hop.initial_credits = path->hops[i].initial_credits;
hop.pmps = path->hops[i].pm_support;
hop.unknown1 = 0;
hop.enable = 1;
- /* dword 1 */
out_mask = (i == path->path_length - 1) ?
TB_PATH_DESTINATION : TB_PATH_INTERNAL;
in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL;
@@ -550,12 +554,21 @@ int tb_path_activate(struct tb_path *path)
hop.drop_packages = path->drop_packages;
hop.counter = path->hops[i].in_counter_index;
hop.counter_enable = path->hops[i].in_counter_index != -1;
- hop.ingress_fc = path->ingress_fc_enable & in_mask;
hop.egress_fc = path->egress_fc_enable & out_mask;
- hop.ingress_shared_buffer = path->ingress_shared_buffer
- & in_mask;
- hop.egress_shared_buffer = path->egress_shared_buffer
- & out_mask;
+ hop.egress_shared_buffer = path->egress_shared_buffer & out_mask;
+ /*
+ * Protocol adapters IFC and ISE bits, and Path Credits
+ * Allocated are vendor defined in the USB4 spec so we
+ * program them only for pre-USB4 and lane adapters.
+ */
+ if (tb_port_is_null(path->hops[i].in_port) ||
+ !tb_switch_is_usb4(path->hops[i].in_port->sw)) {
+ hop.initial_credits = path->hops[i].initial_credits;
+ hop.ingress_fc = path->ingress_fc_enable & in_mask;
+ hop.ingress_shared_buffer =
+ path->ingress_shared_buffer & in_mask;
+ }
+
hop.unknown3 = 0;
tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i);
@@ -563,7 +576,7 @@ int tb_path_activate(struct tb_path *path)
res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS,
2 * path->hops[i].in_hop_index, 2);
if (res) {
- __tb_path_deactivate_hops(path, i);
+ __tb_path_deactivate_hops(path, 0);
__tb_path_deallocate_nfc(path, 0);
goto err;
}
diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c
new file mode 100644
index 000000000000..bbd186c29ef7
--- /dev/null
+++ b/drivers/thunderbolt/pci.c
@@ -0,0 +1,622 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Thunderbolt driver - PCI NHI driver
+ *
+ * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
+ * Copyright (C) 2018, Intel Corporation
+ */
+
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iommu.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/string_helpers.h>
+#include <linux/suspend.h>
+
+#include "nhi.h"
+#include "nhi_regs.h"
+#include "tb.h"
+
+/**
+ * struct tb_nhi_pci - NHI device connected over PCIe
+ * @nhi: NHI device
+ * @msix_ida: Used to allocate MSI-X vectors for rings
+ */
+struct tb_nhi_pci {
+ struct tb_nhi nhi;
+ struct ida msix_ida;
+};
+
+static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi)
+{
+ return container_of(nhi, struct tb_nhi_pci, nhi);
+}
+
+static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci)
+{
+ struct tb_nhi *nhi = &nhi_pci->nhi;
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+
+ if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
+ /*
+ * Intel hardware supports auto clear of the interrupt
+ * status register right after interrupt is being
+ * issued.
+ */
+ nhi->quirks |= QUIRK_AUTO_CLEAR_INT;
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
+ case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
+ /*
+ * Falcon Ridge controller needs the end-to-end
+ * flow control workaround to avoid losing Rx
+ * packets when RING_FLAG_E2E is set.
+ */
+ nhi->quirks |= QUIRK_E2E;
+ break;
+ }
+ }
+}
+
+static int nhi_pci_check_iommu_pdev(struct pci_dev *pdev, void *data)
+{
+ if (!pdev->external_facing ||
+ !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION))
+ return 0;
+ *(bool *)data = true;
+ return 1; /* Stop walking */
+}
+
+static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci)
+{
+ struct tb_nhi *nhi = &nhi_pci->nhi;
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ struct pci_bus *bus = pdev->bus;
+ bool port_ok = false;
+
+ /*
+ * Ideally what we'd do here is grab every PCI device that
+ * represents a tunnelling adapter for this NHI and check their
+ * status directly, but unfortunately USB4 seems to make it
+ * obnoxiously difficult to reliably make any correlation.
+ *
+ * So for now we'll have to bodge it... Hoping that the system
+ * is at least sane enough that an adapter is in the same PCI
+ * segment as its NHI, if we can find *something* on that segment
+ * which meets the requirements for Kernel DMA Protection, we'll
+ * take that to imply that firmware is aware and has (hopefully)
+ * done the right thing in general. We need to know that the PCI
+ * layer has seen the ExternalFacingPort property which will then
+ * inform the IOMMU layer to enforce the complete "untrusted DMA"
+ * flow, but also that the IOMMU driver itself can be trusted not
+ * to have been subverted by a pre-boot DMA attack.
+ */
+ while (bus->parent)
+ bus = bus->parent;
+
+ pci_walk_bus(bus, nhi_pci_check_iommu_pdev, &port_ok);
+
+ nhi->iommu_dma_protection = port_ok;
+ dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n",
+ str_enabled_disabled(port_ok));
+}
+
+static int nhi_pci_init_msi(struct tb_nhi *nhi)
+{
+ struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ struct device *dev = &pdev->dev;
+ int res, irq, nvec;
+
+ ida_init(&nhi_pci->msix_ida);
+
+ /*
+ * The NHI has 16 MSI-X vectors or a single MSI. We first try to
+ * get all MSI-X vectors and if we succeed, each ring will have
+ * one MSI-X. If for some reason that does not work out, we
+ * fallback to a single MSI.
+ */
+ nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS,
+ PCI_IRQ_MSIX);
+ if (nvec < 0) {
+ nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+ if (nvec < 0)
+ return nvec;
+
+ INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
+
+ irq = pci_irq_vector(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ res = devm_request_irq(&pdev->dev, irq, nhi_msi,
+ IRQF_NO_SUSPEND, "thunderbolt", nhi);
+ if (res)
+ return dev_err_probe(dev, res, "request_irq failed, aborting\n");
+ }
+
+ return 0;
+}
+
+static bool nhi_pci_imr_valid(struct pci_dev *pdev)
+{
+ u8 val;
+
+ if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val))
+ return !!val;
+
+ return true;
+}
+
+static void nhi_pci_start_dma_port(struct tb_nhi *nhi)
+{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ struct pci_dev *root_port;
+
+ /*
+ * During host router NVM upgrade we should not allow root port to
+ * go into D3cold because some root ports cannot trigger PME
+ * itself. To be on the safe side keep the root port in D0 during
+ * the whole upgrade process.
+ */
+ root_port = pcie_find_root_port(pdev);
+ if (root_port)
+ pm_runtime_get_noresume(&root_port->dev);
+}
+
+static void nhi_pci_complete_dma_port(struct tb_nhi *nhi)
+{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ struct pci_dev *root_port;
+
+ root_port = pcie_find_root_port(pdev);
+ if (root_port)
+ pm_runtime_put(&root_port->dev);
+}
+
+static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend)
+{
+ struct tb_nhi *nhi = ring->nhi;
+ struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ unsigned long irqflags;
+ int ret;
+
+ if (!pdev->msix_enabled)
+ return 0;
+
+ ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ ring->vector = ret;
+
+ ret = pci_irq_vector(pdev, ring->vector);
+ if (ret < 0)
+ goto err_ida_remove;
+
+ ring->irq = ret;
+
+ irqflags = no_suspend ? IRQF_NO_SUSPEND : 0;
+ ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring);
+ if (ret)
+ goto err_ida_remove;
+
+ return 0;
+
+err_ida_remove:
+ ida_free(&nhi_pci->msix_ida, ring->vector);
+
+ return ret;
+}
+
+static void nhi_pci_ring_release_msix(struct tb_ring *ring)
+{
+ struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi);
+
+ if (ring->irq <= 0)
+ return;
+
+ free_irq(ring->irq, ring);
+ ida_free(&nhi_pci->msix_ida, ring->vector);
+ ring->vector = 0;
+ ring->irq = 0;
+}
+
+static void nhi_pci_shutdown(struct tb_nhi *nhi)
+{
+ struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+
+ /*
+ * We have to release the irq before calling flush_work. Otherwise an
+ * already executing IRQ handler could call schedule_work again.
+ */
+ if (!pdev->msix_enabled) {
+ devm_free_irq(nhi->dev, pdev->irq, nhi);
+ flush_work(&nhi->interrupt_work);
+ }
+ ida_destroy(&nhi_pci->msix_ida);
+}
+
+static bool nhi_pci_is_present(struct tb_nhi *nhi)
+{
+ return pci_device_is_present(to_pci_dev(nhi->dev));
+}
+
+static const struct tb_nhi_ops pci_nhi_default_ops = {
+ .pre_nvm_auth = nhi_pci_start_dma_port,
+ .post_nvm_auth = nhi_pci_complete_dma_port,
+ .request_ring_irq = nhi_pci_ring_request_msix,
+ .release_ring_irq = nhi_pci_ring_release_msix,
+ .shutdown = nhi_pci_shutdown,
+ .is_present = nhi_pci_is_present,
+ .init_interrupts = nhi_pci_init_msi,
+};
+
+/* Ice Lake specific NHI operations */
+
+#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */
+
+static int check_for_device(struct device *dev, void *data)
+{
+ return tb_is_switch(dev);
+}
+
+static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
+{
+ struct tb *tb = dev_get_drvdata(nhi->dev);
+ int ret;
+
+ ret = device_for_each_child(&tb->root_switch->dev, NULL,
+ check_for_device);
+ return ret > 0;
+}
+
+static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
+{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ u32 vs_cap;
+
+ /*
+ * The Thunderbolt host controller is present always in Ice Lake
+ * but the firmware may not be loaded and running (depending
+ * whether there is device connected and so on). Each time the
+ * controller is used we need to "Force Power" it first and wait
+ * for the firmware to indicate it is up and running. This "Force
+ * Power" is really not about actually powering on/off the
+ * controller so it is accessible even if "Force Power" is off.
+ *
+ * The actual power management happens inside shared ACPI power
+ * resources using standard ACPI methods.
+ */
+ pci_read_config_dword(pdev, VS_CAP_22, &vs_cap);
+ if (power) {
+ vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
+ vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
+ vs_cap |= VS_CAP_22_FORCE_POWER;
+ } else {
+ vs_cap &= ~VS_CAP_22_FORCE_POWER;
+ }
+ pci_write_config_dword(pdev, VS_CAP_22, vs_cap);
+
+ if (power) {
+ unsigned int retries = 350;
+ u32 val;
+
+ /* Wait until the firmware tells it is up and running */
+ do {
+ pci_read_config_dword(pdev, VS_CAP_9, &val);
+ if (val & VS_CAP_9_FW_READY)
+ return 0;
+ usleep_range(3000, 3100);
+ } while (--retries);
+
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
+{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ u32 data;
+
+ data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
+ pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID);
+}
+
+static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
+{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ unsigned long end;
+ u32 data;
+
+ if (!timeout)
+ goto clear;
+
+ end = jiffies + msecs_to_jiffies(timeout);
+ do {
+ pci_read_config_dword(pdev, VS_CAP_18, &data);
+ if (data & VS_CAP_18_DONE)
+ goto clear;
+ usleep_range(1000, 1100);
+ } while (time_before(jiffies, end));
+
+ return -ETIMEDOUT;
+
+clear:
+ /* Clear the valid bit */
+ pci_write_config_dword(pdev, VS_CAP_19, 0);
+ return 0;
+}
+
+static void icl_nhi_set_ltr(struct tb_nhi *nhi)
+{
+ struct pci_dev *pdev = to_pci_dev(nhi->dev);
+ u32 max_ltr, ltr;
+
+ pci_read_config_dword(pdev, VS_CAP_16, &max_ltr);
+ max_ltr &= 0xffff;
+ /* Program the same value for both snoop and no-snoop */
+ ltr = max_ltr << 16 | max_ltr;
+ pci_write_config_dword(pdev, VS_CAP_15, ltr);
+}
+
+static int icl_nhi_suspend(struct tb_nhi *nhi)
+{
+ struct tb *tb = dev_get_drvdata(nhi->dev);
+ int ret;
+
+ if (icl_nhi_is_device_connected(nhi))
+ return 0;
+
+ if (tb_switch_is_icm(tb->root_switch)) {
+ /*
+ * If there is no device connected we need to perform
+ * both: a handshake through LC mailbox and force power
+ * down before entering D3.
+ */
+ icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
+ ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
+ if (ret)
+ return ret;
+ }
+
+ return icl_nhi_force_power(nhi, false);
+}
+
+static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
+{
+ struct tb *tb = dev_get_drvdata(nhi->dev);
+ enum icl_lc_mailbox_cmd cmd;
+
+ if (!pm_suspend_via_firmware())
+ return icl_nhi_suspend(nhi);
+
+ if (!tb_switch_is_icm(tb->root_switch))
+ return 0;
+
+ cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
+ icl_nhi_lc_mailbox_cmd(nhi, cmd);
+ return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
+}
+
+static int icl_nhi_resume(struct tb_nhi *nhi)
+{
+ int ret;
+
+ ret = icl_nhi_force_power(nhi, true);
+ if (ret)
+ return ret;
+
+ icl_nhi_set_ltr(nhi);
+ return 0;
+}
+
+static void icl_nhi_shutdown(struct tb_nhi *nhi)
+{
+ nhi_pci_shutdown(nhi);
+
+ icl_nhi_force_power(nhi, false);
+}
+
+static const struct tb_nhi_ops icl_nhi_ops = {
+ .init = icl_nhi_resume,
+ .suspend_noirq = icl_nhi_suspend_noirq,
+ .resume_noirq = icl_nhi_resume,
+ .runtime_suspend = icl_nhi_suspend,
+ .runtime_resume = icl_nhi_resume,
+ .shutdown = icl_nhi_shutdown,
+ .pre_nvm_auth = nhi_pci_start_dma_port,
+ .post_nvm_auth = nhi_pci_complete_dma_port,
+ .request_ring_irq = nhi_pci_ring_request_msix,
+ .release_ring_irq = nhi_pci_ring_release_msix,
+ .is_present = nhi_pci_is_present,
+ .init_interrupts = nhi_pci_init_msi,
+};
+
+static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct tb_nhi_pci *nhi_pci;
+ struct tb_nhi *nhi;
+ int res;
+
+ if (!nhi_pci_imr_valid(pdev))
+ return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n");
+
+ res = pcim_enable_device(pdev);
+ if (res)
+ return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n");
+
+ nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL);
+ if (!nhi_pci)
+ return -ENOMEM;
+
+ nhi = &nhi_pci->nhi;
+ nhi->dev = dev;
+ nhi->ops = (const struct tb_nhi_ops *)id->driver_data ?: &pci_nhi_default_ops;
+
+ nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt");
+ res = PTR_ERR_OR_ZERO(nhi->iobase);
+ if (res)
+ return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
+
+ nhi_pci_check_quirks(nhi_pci);
+ nhi_pci_check_iommu(nhi_pci);
+
+ pci_set_master(pdev);
+
+ return nhi_probe(&nhi_pci->nhi);
+}
+
+static void nhi_pci_remove(struct pci_dev *pdev)
+{
+ struct tb *tb = pci_get_drvdata(pdev);
+ struct tb_nhi *nhi = tb->nhi;
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_forbid(&pdev->dev);
+
+ tb_domain_remove(tb);
+ wait_for_completion(&nhi->domain_released);
+ nhi_shutdown(nhi);
+}
+
+static struct pci_device_id nhi_ids[] = {
+ /*
+ * We have to specify class, the TB bridges use the same device and
+ * vendor (sub)id on gen 1 and gen 2 controllers.
+ */
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
+ .subvendor = 0x2222, .subdevice = 0x1111,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
+ .subvendor = 0x2222, .subdevice = 0x1111,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI,
+ .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
+ },
+ {
+ .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI,
+ .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
+ },
+
+ /* Thunderbolt 3 */
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ /* Thunderbolt 4 */
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0),
+ .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) },
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) },
+
+ /* Any USB4 compliant host */
+ { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) },
+
+ { 0,}
+};
+
+MODULE_DEVICE_TABLE(pci, nhi_ids);
+MODULE_DESCRIPTION("Thunderbolt/USB4 core driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_driver nhi_driver = {
+ .name = "thunderbolt",
+ .id_table = nhi_ids,
+ .probe = nhi_pci_probe,
+ .remove = nhi_pci_remove,
+ .shutdown = nhi_pci_remove,
+ .driver.pm = &nhi_pm_ops,
+};
+
+static int __init nhi_init(void)
+{
+ int ret;
+
+ ret = tb_domain_init();
+ if (ret)
+ return ret;
+
+ ret = pci_register_driver(&nhi_driver);
+ if (ret)
+ tb_domain_exit();
+
+ return ret;
+}
+
+static void __exit nhi_unload(void)
+{
+ pci_unregister_driver(&nhi_driver);
+ tb_domain_exit();
+}
+
+rootfs_initcall(nhi_init);
+module_exit(nhi_unload);
diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c
index 59beab43f90a..df18a6d69d8b 100644
--- a/drivers/thunderbolt/property.c
+++ b/drivers/thunderbolt/property.c
@@ -40,6 +40,7 @@ struct tb_property_dir_entry {
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
size_t block_len, unsigned int dir_offset, size_t dir_len,
bool is_root, unsigned int depth);
+static struct tb_property *tb_property_copy(const struct tb_property *property);
static inline void parse_dwdata(void *dst, const void *src, size_t dwords)
{
@@ -527,17 +528,9 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
return ret < 0 ? ret : 0;
}
-/**
- * tb_property_copy_dir() - Take a deep copy of directory
- * @dir: Directory to copy
- *
- * The resulting directory needs to be released by calling tb_property_free_dir().
- *
- * Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
- */
-struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
+static struct tb_property_dir *copy_dir(const struct tb_property_dir *dir)
{
- struct tb_property *property, *p = NULL;
+ struct tb_property *property, *p;
struct tb_property_dir *d;
if (!dir)
@@ -548,57 +541,132 @@ struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
return NULL;
list_for_each_entry(property, &dir->properties, list) {
- struct tb_property *p;
-
- p = tb_property_alloc(property->key, property->type);
+ p = tb_property_copy(property);
if (!p)
goto err_free;
+ list_add_tail(&p->list, &d->properties);
+ }
- p->length = property->length;
+ return d;
- switch (property->type) {
- case TB_PROPERTY_TYPE_DIRECTORY:
- p->value.dir = tb_property_copy_dir(property->value.dir);
- if (!p->value.dir)
- goto err_free;
- break;
+err_free:
+ tb_property_free_dir(d);
+ return NULL;
+}
- case TB_PROPERTY_TYPE_DATA:
- p->value.data = kmemdup(property->value.data,
- property->length * 4,
- GFP_KERNEL);
- if (!p->value.data)
- goto err_free;
- break;
+static struct tb_property *tb_property_copy(const struct tb_property *property)
+{
+ struct tb_property *p;
- case TB_PROPERTY_TYPE_TEXT:
- p->value.text = kzalloc(p->length * 4, GFP_KERNEL);
- if (!p->value.text)
- goto err_free;
- strcpy(p->value.text, property->value.text);
- break;
+ p = tb_property_alloc(property->key, property->type);
+ if (!p)
+ return NULL;
- case TB_PROPERTY_TYPE_VALUE:
- p->value.immediate = property->value.immediate;
- break;
+ p->length = property->length;
+ switch (property->type) {
+ case TB_PROPERTY_TYPE_DIRECTORY:
+ p->value.dir = copy_dir(property->value.dir);
+ if (!p->value.dir)
+ goto err_free;
+ break;
- default:
- break;
- }
+ case TB_PROPERTY_TYPE_DATA:
+ p->value.data = kmemdup(property->value.data,
+ property->length * 4,
+ GFP_KERNEL);
+ if (!p->value.data)
+ goto err_free;
+ break;
- list_add_tail(&p->list, &d->properties);
+ case TB_PROPERTY_TYPE_TEXT:
+ p->value.text = kzalloc(p->length * 4, GFP_KERNEL);
+ if (!p->value.text)
+ goto err_free;
+ strcpy(p->value.text, property->value.text);
+ break;
+
+ case TB_PROPERTY_TYPE_VALUE:
+ p->value.immediate = property->value.immediate;
+ break;
+
+ default:
+ break;
}
- return d;
+ return p;
err_free:
kfree(p);
- tb_property_free_dir(d);
-
return NULL;
}
/**
+ * tb_property_copy_dir() - Take a deep copy of directory
+ * @dir: Directory to copy
+ *
+ * The resulting directory needs to be released by calling tb_property_free_dir().
+ *
+ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
+ */
+struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
+{
+ return copy_dir(dir);
+}
+EXPORT_SYMBOL_GPL(tb_property_copy_dir);
+
+/**
+ * tb_property_merge_dir() - Merges directory into parent
+ * @parent: Directory to merge @dir
+ * @dir: Directory that is merged
+ * @replace: Replace existing entries
+ *
+ * This will merge @dir into @parent. Both must have same UUID. The
+ * properties in @dir will overwrite overlapping properties in @parent
+ * if @replace is %true. Contents of @dir is copied (so if it is not
+ * needed afterwards it needs to relesed by calling tb_property_free_dir()).
+ */
+int tb_property_merge_dir(struct tb_property_dir *parent,
+ const struct tb_property_dir *dir,
+ bool replace)
+{
+ const struct tb_property *property;
+
+ if (WARN_ON(parent == dir))
+ return -EINVAL;
+
+ if (!uuid_equal(parent->uuid, dir->uuid))
+ return -EINVAL;
+
+ list_for_each_entry(property, &dir->properties, list) {
+ struct tb_property *p, *tmp;
+
+ tmp = tb_property_copy(property);
+ if (!tmp)
+ return -ENOMEM;
+
+ p = tb_property_find(parent, property->key, property->type);
+ if (p) {
+ if (replace) {
+ /*
+ * Found existing property in parent so
+ * replace with the new one.
+ */
+ list_replace(&p->list, &tmp->list);
+ tb_property_free(p);
+ } else {
+ tb_property_free(tmp);
+ continue;
+ }
+ } else {
+ list_add_tail(&tmp->list, &parent->properties);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tb_property_merge_dir);
+
+/**
* tb_property_add_immediate() - Add immediate property to directory
* @parent: Directory to add the property
* @key: Key for the property
diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c
new file mode 100644
index 000000000000..c1f5c55583d0
--- /dev/null
+++ b/drivers/thunderbolt/stream.c
@@ -0,0 +1,1698 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Stream data over Thunderbolt/USB4 cable
+ *
+ * Copyright (C) 2026, Intel Corporation
+ * Authors: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#define pr_fmt(fmt) "tbstream: " fmt
+
+#include <linux/configfs.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sizes.h>
+#include <linux/thunderbolt.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/uuid.h>
+#include <linux/wait.h>
+
+/*
+ * USB4STREAM - Stream data directly over Thunderbolt/USB4 cable
+ *
+ * HopIDs are configured by the user. In Linux this is done through
+ * ConfigFS. Once that is done paths are be established the first time
+ * the stream is opened. Typically the read side is opened first to make
+ * sure all the data will be received.
+ *
+ * End-to-end flow control is mandatory on both sides.
+ *
+ * Data is sent to the other side as tunneled DATA packets. All the data
+ * is owned by the user and passed as-is from the writer to the reader.
+ *
+ * Once the stream device is closed, a CLOSE packet is sent to the peer
+ * so it can take the necessary action. On Linux this typically results
+ * in EOF being returned to the reader.
+ *
+ * Tunneled packet types:
+ *
+ * +-------+---------+------------------+
+ * | PDF | Type | Payload size |
+ * +-------+---------+------------------+
+ * | 2 | DATA | up to 4 KiB |
+ * | 3 | CLOSE | up to 256 bytes |
+ * +-------+---------+------------------+
+ *
+ * Each stream can optionally publish configuration values under its own
+ * XDomain property directory. The name of the directory is the name of
+ * the stream in question and the UUID is up to the stream. For example
+ * if the stream exposes video output then the directory name could be
+ * "video".
+ *
+ * Below values are reserved and can be used by the stream:
+ *
+ * +----------+-----------+-------------------------+
+ * | Key | Type | Contents |
+ * +----------+-----------+-------------------------+
+ * | inhopid | IMMEDIATE | Configured input HopID |
+ * | outhopid | IMMEDIATE | Configured output HopID |
+ * +----------+-----------+-------------------------+
+ *
+ * It is allowed to add more stream specific properties as well if the
+ * above are not enough.
+ */
+
+#define TBSTREAM_DEV_RING_SIZE 256
+#define TBSTREAM_DEV_MIN_RING_SIZE 32
+#define TBSTREAM_DEV_MAX_RING_SIZE 4096
+#define TBSTREAM_DEV_THROTTLING 8192
+#define TBSTREAM_DEV_MAX_THROTTLING 16776960
+
+/**
+ * enum tbstream_frame_pdf - PDF numbers for tunneled frames
+ * @TBSTREAM_FRAME_START: PDF of the start of the frame
+ * @TBSTREAM_DATA: PDF of the DATA frame
+ * @TBSTREAM_CLOSE: PDF of the CLOSE frame
+ */
+enum tbstream_frame_pdf {
+ TBSTREAM_FRAME_START = 1,
+ TBSTREAM_DATA,
+ TBSTREAM_CLOSE,
+};
+
+/**
+ * struct tbstream_frame - Frame submitted to/from the rings
+ * @sdev: Pointer to the stream device
+ * @page: Page holding the packet
+ * @offset: Offset inside @page if partial read is done
+ * @completed: %true if the RX frame is completed
+ * @frame: Underlying frame structure
+ */
+struct tbstream_frame {
+ struct tbstream_dev *sdev;
+ struct page *page;
+ unsigned int offset;
+ bool completed;
+ struct ring_frame frame;
+};
+
+/**
+ * struct tbstream_ring - Stream RX/TX ring structure
+ * @ring: Pointer to the API ring
+ * @prod: Current value of producer
+ * @cons: Current value of consumer
+ * @frames: Holds the ring frames
+ */
+struct tbstream_ring {
+ struct tb_ring *ring;
+ unsigned long prod;
+ unsigned long cons;
+ struct tbstream_frame *frames;
+};
+
+/**
+ * struct tbstream_dev - Stream character device
+ * @group: ConfigFS group for this device
+ * @stream: Pointer to the stream if it is attached (%NULL otherwise)
+ * @misc: Character device used for tunneling
+ * @kref: Reference count
+ * @index: Unique identifier for the character device
+ * @in_hopid: In HopID
+ * @out_hopid: Out HopID
+ * @ring_size: Size of the rings
+ * @throttling: Interrupt throttling rate in ns
+ * @users: Number of times @cdev has been opened
+ * @closed: CLOSE packet was received
+ * @removed: Userspace removed the ConfigFS group underneath.
+ * @wait: Waitqueue for open, read and write
+ * @lock: Lock protecting this structure
+ * @tx_ring: Transmit ring
+ * @rx_ring: Receive ring
+ * @list: Stream devices are linked through this
+ */
+struct tbstream_dev {
+ struct config_group group;
+ struct tbstream *stream;
+ struct miscdevice misc;
+ struct kref kref;
+ int index;
+ int in_hopid;
+ int out_hopid;
+ unsigned int ring_size;
+ unsigned int throttling;
+ int users;
+ bool closed;
+ bool removed;
+ wait_queue_head_t wait;
+ struct mutex lock;
+ struct tbstream_ring tx_ring;
+ struct tbstream_ring rx_ring;
+ struct list_head list;
+};
+
+/**
+ * struct tbstream_group - Config group for stream
+ * @group: ConfigFS group for @stream
+ * @stream: Stream the ConfigFS group is attached to. %NULL if there is
+ * no stream attached.
+ * @lock: Lock protecting this structure
+ * @dev_list: List of stream devices
+ *
+ * This is the ConfigFS directory for one connection to another host.
+ * There can be several &struct stream_dev linked through @dev_list of
+ * this structure. Reference count managed through @group.
+ */
+struct tbstream_group {
+ struct config_group group;
+ struct tbstream *stream;
+ struct mutex lock;
+ struct list_head dev_list;
+};
+
+/**
+ * struct tbstream - Stream service private data
+ * @kref: Reference count
+ * @svc: Pointer to the service device
+ * @list: Streams are linked through this in @stream_list
+ *
+ * This represents the actual physical connection between two hosts.
+ */
+struct tbstream {
+ struct kref kref;
+ struct tb_service *svc;
+ struct list_head list;
+};
+
+static DEFINE_IDA(tbstream_indices);
+
+/* Protects tbstream_list */
+static DEFINE_MUTEX(tbstream_lock);
+static LIST_HEAD(tbstream_list);
+
+/* Serializes tbstream_get()/put() */
+static DEFINE_MUTEX(tbstream_kref_lock);
+
+/* Serializes tbstream_dev_get()/put() */
+static DEFINE_MUTEX(tbstream_dev_kref_lock);
+
+/* Stream property directory UUID: 3a1cb984-c4d9-4469-a277-ce2fdfd11f0d */
+static const uuid_t tbstream_dir_uuid =
+ UUID_INIT(0x3a1cb984, 0xc4d9, 0x4469,
+ 0xa2, 0x77, 0xce, 0x2f, 0xdf, 0xd1, 0x1f, 0x0d);
+
+static struct tb_property_dir *tbstream_dir;
+
+static void tbstream_release(struct kref *kref)
+{
+ struct tbstream *stream = container_of(kref, typeof(*stream), kref);
+
+ tb_service_put(stream->svc);
+ kfree(stream);
+}
+
+static void tbstream_put(struct tbstream *stream)
+{
+ if (stream) {
+ guard(mutex)(&tbstream_kref_lock);
+ kref_put(&stream->kref, tbstream_release);
+ }
+}
+
+static struct tbstream *tbstream_get(struct tbstream *stream)
+{
+ if (stream) {
+ guard(mutex)(&tbstream_kref_lock);
+ kref_get(&stream->kref);
+ }
+ return stream;
+}
+
+static inline bool tbstream_valid(const struct tbstream *stream)
+{
+ if (stream)
+ return !tb_service_parent(stream->svc)->is_unplugged;
+ return false;
+}
+
+static void tbstream_ring_free(struct tbstream_ring *ring)
+{
+ struct device *dma_dev = tb_ring_dma_device(ring->ring);
+ enum dma_data_direction dir;
+ int i;
+
+ if (ring->ring->is_tx)
+ dir = DMA_TO_DEVICE;
+ else
+ dir = DMA_FROM_DEVICE;
+
+ for (i = 0; i < tb_ring_size(ring->ring); i++) {
+ struct tbstream_frame *sf = &ring->frames[i];
+
+ if (sf->frame.buffer_phy)
+ dma_unmap_page(dma_dev, sf->frame.buffer_phy,
+ tb_ring_frame_size(&sf->frame), dir);
+ sf->frame.buffer_phy = 0;
+ if (sf->page)
+ __free_page(sf->page);
+ sf->page = NULL;
+ }
+
+ ring->prod = 0;
+ ring->cons = 0;
+ kfree(ring->frames);
+}
+
+static inline bool tbstream_ring_available(const struct tbstream_ring *ring)
+{
+ return ring->prod > ring->cons;
+}
+
+static inline struct tb_xdomain *tbstream_dev_xdomain(struct tbstream_dev *sdev)
+{
+ if (sdev->stream)
+ return tb_service_parent(sdev->stream->svc);
+ return NULL;
+}
+
+static void tbstream_dev_release(struct kref *kref)
+{
+ struct tbstream_dev *sdev = container_of(kref, struct tbstream_dev, kref);
+
+ if (sdev->stream) {
+ struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
+
+ if (sdev->out_hopid > 0)
+ tb_xdomain_release_out_hopid(xd, sdev->out_hopid);
+ if (sdev->in_hopid > 0)
+ tb_xdomain_release_in_hopid(xd, sdev->in_hopid);
+
+ tbstream_put(sdev->stream);
+ }
+ ida_free(&tbstream_indices, sdev->index);
+ kfree(sdev->misc.name);
+ kfree(sdev);
+}
+
+static inline void tbstream_dev_put(struct tbstream_dev *sdev)
+{
+ guard(mutex)(&tbstream_dev_kref_lock);
+ kref_put(&sdev->kref, tbstream_dev_release);
+}
+
+static inline struct tbstream_dev *tbstream_dev_get(struct tbstream_dev *sdev)
+{
+ guard(mutex)(&tbstream_dev_kref_lock);
+ kref_get(&sdev->kref);
+ return sdev;
+}
+
+static inline struct tbstream_dev *to_tbstream_dev(struct miscdevice *misc)
+{
+ return container_of(misc, struct tbstream_dev, misc);
+}
+
+static inline int tbstream_dev_valid(const struct tbstream_dev *sdev)
+{
+ const struct tbstream *stream = sdev->stream;
+
+ if (!tbstream_valid(stream))
+ return -ENXIO;
+ if (sdev->in_hopid <= 0 || sdev->out_hopid <= 0)
+ return -EINVAL;
+ return 0;
+}
+
+static inline bool tbstream_dev_removed(const struct tbstream_dev *sdev)
+{
+ return sdev->removed;
+}
+
+static inline bool tbstream_dev_closed(const struct tbstream_dev *sdev)
+{
+ return sdev->closed;
+}
+
+static void
+tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
+ bool canceled)
+{
+ struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame);
+ struct tbstream_dev *sdev = sf->sdev;
+
+ if (canceled)
+ return;
+
+ sf->completed = true;
+ sdev->rx_ring.prod++;
+
+ if (sf->frame.flags & RING_DESC_CRC_ERROR)
+ pr_warn("RX CRC error\n");
+ else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN)
+ pr_warn("RX buffer overrun\n");
+ else
+ wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM);
+}
+
+static struct tbstream_frame *
+tbstream_dev_completed_rx(struct tbstream_dev *sdev)
+{
+ struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring);
+ struct tbstream_frame *sf;
+ int index;
+
+ index = sdev->rx_ring.cons % tb_ring_size(sdev->rx_ring.ring);
+ sf = &sdev->rx_ring.frames[index];
+ if (!sf->completed)
+ return NULL;
+
+ dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy,
+ tb_ring_frame_size(&sf->frame),
+ DMA_FROM_DEVICE);
+ return sf;
+}
+
+static int tbstream_dev_consume_rx(struct tbstream_dev *sdev)
+{
+ struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring);
+ struct tbstream_frame *sf;
+ int index;
+
+ index = sdev->rx_ring.cons % tb_ring_size(sdev->rx_ring.ring);
+ sdev->rx_ring.cons++;
+
+ sf = &sdev->rx_ring.frames[index];
+ sf->completed = false;
+ sf->offset = 0;
+ sf->frame.size = 0;
+
+ dma_sync_single_for_device(dma_dev, sf->frame.buffer_phy,
+ tb_ring_frame_size(&sf->frame),
+ DMA_FROM_DEVICE);
+
+ return tb_ring_rx(sdev->rx_ring.ring, &sf->frame);
+}
+
+static int tbstream_dev_alloc_rx_buffers(struct tbstream_dev *sdev)
+{
+ size_t ring_size = tb_ring_size(sdev->rx_ring.ring);
+ int i;
+
+ sdev->rx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame),
+ GFP_KERNEL);
+ if (!sdev->rx_ring.frames)
+ return -ENOMEM;
+
+ for (i = 0; i < ring_size; i++) {
+ struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring);
+ struct tbstream_frame *sf = &sdev->rx_ring.frames[i];
+ dma_addr_t dma_addr;
+
+ sf->page = alloc_page(GFP_KERNEL);
+ if (!sf->page)
+ return -ENOMEM;
+
+ dma_addr = dma_map_page(dma_dev, sf->page, 0, TB_MAX_FRAME_SIZE,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(dma_dev, dma_addr)) {
+ __free_page(sf->page);
+ sf->page = NULL;
+ return -ENOMEM;
+ }
+
+ sf->sdev = sdev;
+ sf->frame.callback = tbstream_dev_rx_callback;
+ sf->frame.buffer_phy = dma_addr;
+
+ tb_ring_rx(sdev->rx_ring.ring, &sf->frame);
+ }
+
+ sdev->rx_ring.cons = 0;
+ sdev->rx_ring.prod = 0;
+ return 0;
+}
+
+static void
+tbstream_dev_tx_callback(struct tb_ring *ring, struct ring_frame *frame,
+ bool canceled)
+{
+ struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame);
+ struct tbstream_dev *sdev = sf->sdev;
+
+ if (canceled)
+ return;
+
+ sdev->tx_ring.prod++;
+ if (sf->frame.eof == TBSTREAM_DATA)
+ wake_up_interruptible_poll(&sdev->wait, EPOLLOUT | EPOLLWRNORM);
+}
+
+static int tbstream_dev_alloc_tx_buffers(struct tbstream_dev *sdev)
+{
+ struct device *dma_dev = tb_ring_dma_device(sdev->tx_ring.ring);
+ size_t ring_size = tb_ring_size(sdev->tx_ring.ring);
+ int i;
+
+ sdev->tx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame),
+ GFP_KERNEL);
+ if (!sdev->tx_ring.frames)
+ return -ENOMEM;
+
+ for (i = 0; i < ring_size; i++) {
+ struct tbstream_frame *sf = &sdev->tx_ring.frames[i];
+ dma_addr_t dma_addr;
+
+ sf->page = alloc_page(GFP_KERNEL);
+ if (!sf->page)
+ return -ENOMEM;
+
+ dma_addr = dma_map_page(dma_dev, sf->page, 0, TB_MAX_FRAME_SIZE,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(dma_dev, dma_addr)) {
+ __free_page(sf->page);
+ sf->page = NULL;
+ return -ENOMEM;
+ }
+
+ sf->sdev = sdev;
+ sf->frame.callback = tbstream_dev_tx_callback;
+ sf->frame.buffer_phy = dma_addr;
+ sf->frame.sof = TBSTREAM_FRAME_START;
+ }
+
+ sdev->tx_ring.cons = 0;
+ sdev->tx_ring.prod = ring_size - 1;
+ return 0;
+}
+
+static struct tbstream_frame *
+tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf,
+ struct iov_iter *from, size_t size)
+{
+ struct device *dma_dev = tb_ring_dma_device(sdev->tx_ring.ring);
+ struct tbstream_frame *sf;
+ int index;
+
+ if (!tbstream_ring_available(&sdev->tx_ring))
+ return ERR_PTR(-ENOBUFS);
+
+ index = sdev->tx_ring.cons % tb_ring_size(sdev->tx_ring.ring);
+ sdev->tx_ring.cons++;
+
+ sf = &sdev->tx_ring.frames[index];
+ sf->frame.size = size < TB_MAX_FRAME_SIZE ? size : 0;
+ sf->frame.eof = pdf;
+
+ dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size,
+ DMA_TO_DEVICE);
+ if (pdf == TBSTREAM_DATA) {
+ if (copy_page_from_iter(sf->page, 0, size, from) != size)
+ return ERR_PTR(-EFAULT);
+ } else {
+ memset(page_address(sf->page), 0, size);
+ }
+ dma_sync_single_for_device(dma_dev, sf->frame.buffer_phy, size,
+ DMA_TO_DEVICE);
+ return sf;
+}
+
+static int
+tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from,
+ size_t size)
+{
+ struct tbstream_frame *sf;
+
+ sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_DATA, from, size);
+ if (IS_ERR(sf))
+ return PTR_ERR(sf);
+ return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
+}
+
+static int tbstream_dev_send_close(struct tbstream_dev *sdev)
+{
+ struct tbstream_frame *sf;
+
+ sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256);
+ if (IS_ERR(sf))
+ return PTR_ERR(sf);
+ return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
+}
+
+static int tbstream_dev_start(struct tbstream_dev *sdev)
+{
+ struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
+ u16 sof_mask, eof_mask;
+ struct tb_ring *ring;
+ int ret, e2e_tx_hop;
+
+ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size,
+ RING_FLAG_FRAME | RING_FLAG_E2E);
+ if (!ring)
+ return -ENOMEM;
+ sdev->tx_ring.ring = ring;
+
+ ret = tbstream_dev_alloc_tx_buffers(sdev);
+ if (ret)
+ goto err_free_tx;
+
+ e2e_tx_hop = ring->hop;
+ sof_mask = BIT(TBSTREAM_FRAME_START);
+ eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE);
+
+ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size,
+ RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop,
+ sof_mask, eof_mask, NULL, NULL);
+ if (!ring) {
+ ret = -ENOMEM;
+ goto err_free_tx_buffers;
+ }
+ sdev->rx_ring.ring = ring;
+
+ ret = tb_xdomain_enable_paths(xd, sdev->out_hopid,
+ sdev->tx_ring.ring->hop,
+ sdev->in_hopid,
+ sdev->rx_ring.ring->hop);
+ if (ret)
+ goto err_free_rx;
+
+ tb_ring_throttling(sdev->tx_ring.ring, sdev->throttling);
+ tb_ring_throttling(sdev->rx_ring.ring, sdev->throttling);
+
+ tb_ring_start(sdev->tx_ring.ring);
+ tb_ring_start(sdev->rx_ring.ring);
+
+ ret = tbstream_dev_alloc_rx_buffers(sdev);
+ if (ret)
+ goto err_stop;
+ return 0;
+
+err_stop:
+ tb_ring_stop(sdev->rx_ring.ring);
+ tb_ring_stop(sdev->tx_ring.ring);
+err_free_rx:
+ tb_ring_free(sdev->rx_ring.ring);
+err_free_tx_buffers:
+ tbstream_ring_free(&sdev->tx_ring);
+err_free_tx:
+ tb_ring_free(sdev->tx_ring.ring);
+
+ return ret;
+}
+
+static void tbstream_dev_stop(struct tbstream_dev *sdev)
+{
+ struct tb_xdomain *xd;
+
+ /* Wait for the ring to complete any outstanding frames */
+ tb_ring_flush(sdev->tx_ring.ring, 500);
+ tb_ring_stop(sdev->tx_ring.ring);
+ tb_ring_flush(sdev->rx_ring.ring, 500);
+ tb_ring_stop(sdev->rx_ring.ring);
+
+ xd = tbstream_dev_xdomain(sdev);
+ if (xd) {
+ tb_xdomain_disable_paths(xd, sdev->out_hopid,
+ sdev->tx_ring.ring->hop,
+ sdev->in_hopid,
+ sdev->rx_ring.ring->hop);
+ }
+
+ tbstream_ring_free(&sdev->rx_ring);
+ tb_ring_free(sdev->rx_ring.ring);
+ sdev->rx_ring.ring = NULL;
+ tbstream_ring_free(&sdev->tx_ring);
+ tb_ring_free(sdev->tx_ring.ring);
+ sdev->tx_ring.ring = NULL;
+}
+
+static ssize_t
+tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
+{
+ struct file *file = kiocb->ki_filp;
+ struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
+ size_t nbytes;
+ int ret;
+
+ ret = tbstream_dev_valid(sdev);
+ if (ret)
+ return ret;
+
+ if (mutex_lock_interruptible(&sdev->lock))
+ return -ERESTARTSYS;
+
+ while (!tbstream_ring_available(&sdev->rx_ring)) {
+ mutex_unlock(&sdev->lock);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ ret = wait_event_interruptible(sdev->wait,
+ tbstream_ring_available(&sdev->rx_ring) ||
+ tbstream_dev_valid(sdev) != 0 ||
+ tbstream_dev_closed(sdev) ||
+ tbstream_dev_removed(sdev));
+ if (ret)
+ return ret;
+
+ ret = tbstream_dev_valid(sdev);
+ if (ret)
+ return ret;
+
+ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
+ return 0;
+
+ if (mutex_lock_interruptible(&sdev->lock))
+ return -ERESTARTSYS;
+ }
+
+ nbytes = 0;
+ while (nbytes < iov_iter_count(to)) {
+ struct tbstream_frame *sf;
+ size_t size, sf_size;
+
+ sf = tbstream_dev_completed_rx(sdev);
+ if (!sf)
+ break;
+ /*
+ * CLOSE tunneled packet. If userspace already read
+ * something then we stop processing now and return
+ * those bytes. Next time the first frame will be CLOSE
+ * in which case we return EOF to the user.
+ */
+ if (sf->frame.eof == TBSTREAM_CLOSE) {
+ if (!nbytes) {
+ tbstream_dev_consume_rx(sdev);
+ sdev->closed = true;
+ }
+ break;
+ }
+
+ sf_size = tb_ring_frame_size(&sf->frame);
+ size = min(iov_iter_count(to) - nbytes, sf_size);
+
+ if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) {
+ ret = -EFAULT;
+ break;
+ }
+
+ /*
+ * If not all data from the frame is read so leave it in
+ * place and update the offset accordingly so next read
+ * gets the rest.
+ */
+ if (size < sf_size) {
+ sf->offset += size;
+ sf->frame.size = sf_size - size;
+ } else {
+ ret = tbstream_dev_consume_rx(sdev);
+ if (ret)
+ break;
+ }
+
+ nbytes += size;
+ }
+
+ mutex_unlock(&sdev->lock);
+ if (ret)
+ return ret;
+ return nbytes;
+}
+
+static ssize_t
+tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
+{
+ struct file *file = kiocb->ki_filp;
+ struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
+ size_t nbytes;
+ int ret;
+
+ ret = tbstream_dev_valid(sdev);
+ if (ret)
+ return ret;
+
+ if (mutex_lock_interruptible(&sdev->lock))
+ return -ERESTARTSYS;
+
+ while (!tbstream_ring_available(&sdev->tx_ring)) {
+ mutex_unlock(&sdev->lock);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ ret = wait_event_interruptible(sdev->wait,
+ tbstream_ring_available(&sdev->tx_ring) ||
+ tbstream_dev_valid(sdev) != 0 ||
+ tbstream_dev_closed(sdev) ||
+ tbstream_dev_removed(sdev));
+ if (ret)
+ return ret;
+
+ ret = tbstream_dev_valid(sdev);
+ if (ret)
+ return ret;
+
+ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
+ return -ENXIO;
+
+ if (mutex_lock_interruptible(&sdev->lock))
+ return -ERESTARTSYS;
+ }
+
+ nbytes = 0;
+ while (nbytes < iov_iter_count(from)) {
+ size_t size;
+
+ size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE);
+ ret = tbstream_dev_send_data(sdev, from, size);
+ if (ret) {
+ /*
+ * If there are no more buffers we are done for
+ * this write.
+ */
+ if (ret == -ENOBUFS)
+ ret = 0;
+ break;
+ }
+
+ nbytes += size;
+ }
+
+ mutex_unlock(&sdev->lock);
+ if (ret)
+ return ret;
+ return nbytes;
+}
+
+static __poll_t
+tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait)
+{
+ struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
+ __poll_t mask = 0;
+
+ poll_wait(file, &sdev->wait, wait);
+ guard(mutex)(&sdev->lock);
+ if (tbstream_dev_valid(sdev) != 0) {
+ mask |= EPOLLHUP | EPOLLERR;
+ } else {
+ if (tbstream_ring_available(&sdev->tx_ring))
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ if (tbstream_ring_available(&sdev->rx_ring))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ }
+ return mask;
+}
+
+static int tbstream_dev_fops_open(struct inode *inode, struct file *file)
+{
+ struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
+ int ret;
+
+ tbstream_dev_get(sdev);
+
+ if (mutex_lock_interruptible(&sdev->lock)) {
+ tbstream_dev_put(sdev);
+ return -ERESTARTSYS;
+ }
+
+ /*
+ * If there is no stream attached yet, block until it appears
+ * unless this is opened in non-blocking mode.
+ */
+ while ((ret = tbstream_dev_valid(sdev))) {
+ mutex_unlock(&sdev->lock);
+
+ if (ret != -ENXIO || (file->f_flags & O_NONBLOCK))
+ goto err_put;
+
+ ret = wait_event_interruptible(sdev->wait,
+ tbstream_dev_valid(sdev) == 0 ||
+ tbstream_dev_removed(sdev));
+ if (ret)
+ goto err_put;
+
+ if (tbstream_dev_removed(sdev)) {
+ ret = -ENXIO;
+ goto err_put;
+ }
+
+ if (mutex_lock_interruptible(&sdev->lock)) {
+ ret = -ERESTARTSYS;
+ goto err_put;
+ }
+ }
+
+ /* Only on first open we allocate rings and enable paths */
+ if (!sdev->users++) {
+ ret = tbstream_dev_start(sdev);
+ if (ret) {
+ sdev->users--;
+ goto err_unlock;
+ }
+ sdev->closed = false;
+ }
+
+ mutex_unlock(&sdev->lock);
+ return 0;
+
+err_unlock:
+ mutex_unlock(&sdev->lock);
+err_put:
+ tbstream_dev_put(sdev);
+
+ return ret;
+}
+
+static int tbstream_dev_fops_release(struct inode *inode, struct file *file)
+{
+ struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
+
+ mutex_lock(&sdev->lock);
+ if (--sdev->users == 0) {
+ /*
+ * Send CLOSE tunneled packet to notify the other end
+ * that we are closing the file. We do this twice if the
+ * first one fails.
+ */
+ tbstream_dev_send_close(sdev);
+ tbstream_dev_stop(sdev);
+ }
+ mutex_unlock(&sdev->lock);
+
+ tbstream_dev_put(sdev);
+ return 0;
+}
+
+static const struct file_operations tbstream_dev_fops = {
+ .owner = THIS_MODULE,
+ .llseek = noop_llseek,
+ .read_iter = tbstream_dev_fops_read_iter,
+ .write_iter = tbstream_dev_fops_write_iter,
+ .poll = tbstream_dev_fops_poll,
+ .open = tbstream_dev_fops_open,
+ .release = tbstream_dev_fops_release,
+};
+
+static inline struct tbstream_dev *
+tbstream_dev_from_group(struct config_group *group)
+{
+ return container_of(group, struct tbstream_dev, group);
+}
+
+static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ return sysfs_emit(buf, "%d\n", sdev->index);
+}
+CONFIGFS_ATTR_RO(tbstream_dev_, index);
+
+static ssize_t tbstream_dev_in_hopid_show(struct config_item *item, char *buf)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ return sysfs_emit(buf, "%d\n", sdev->in_hopid);
+}
+
+/* svc->lock must be held */
+static void service_remove_properties(struct tb_service *svc, const char *name)
+{
+ struct tb_property *p;
+
+ if (!svc->local_properties)
+ return;
+
+ p = tb_property_find(svc->local_properties, name,
+ TB_PROPERTY_TYPE_DIRECTORY);
+ if (p) {
+ tb_property_free_dir(p->value.dir);
+ tb_property_remove(p);
+
+ dev_dbg(&svc->dev, "removed local directory %s\n", name);
+
+ /*
+ * Is the service directory empty already? If it is then
+ * we can release it as well.
+ */
+ tb_property_for_each(svc->local_properties, p) {
+ if (p->type == TB_PROPERTY_TYPE_DIRECTORY)
+ return;
+ }
+
+ tb_property_free_dir(svc->local_properties);
+ svc->local_properties = NULL;
+ }
+}
+
+static int service_update_properties(struct tb_service *svc, const char *name,
+ int in_hopid, int out_hopid)
+{
+ struct tb_property_dir *dir;
+ struct tb_property *p;
+
+ guard(mutex)(&svc->lock);
+
+ if (in_hopid < 8 || out_hopid < 8) {
+ service_remove_properties(svc, name);
+ return 0;
+ }
+
+ if (!svc->local_properties) {
+ /*
+ * Add the service directory first time we
+ * populate the entries.
+ */
+ svc->local_properties = tb_property_copy_dir(tbstream_dir);
+ if (!svc->local_properties)
+ return -ENOMEM;
+ }
+
+ p = tb_property_find(svc->local_properties, name,
+ TB_PROPERTY_TYPE_DIRECTORY);
+ if (p) {
+ dir = p->value.dir;
+
+ p = tb_property_find(dir, "inhopid", TB_PROPERTY_TYPE_VALUE);
+ if (p && p->value.immediate != in_hopid)
+ p->value.immediate = in_hopid;
+ p = tb_property_find(dir, "outhopid", TB_PROPERTY_TYPE_VALUE);
+ if (p && p->value.immediate != out_hopid)
+ p->value.immediate = out_hopid;
+
+ dev_dbg(&svc->dev,
+ "updated local directory %s: in HopID %d, out HopID %d\n",
+ name, in_hopid, out_hopid);
+ } else {
+ uuid_t uuid;
+ int ret;
+
+ uuid_gen(&uuid);
+ dir = tb_property_create_dir(&uuid);
+ if (!dir)
+ return -ENOMEM;
+
+ tb_property_add_immediate(dir, "inhopid", in_hopid);
+ tb_property_add_immediate(dir, "outhopid", out_hopid);
+
+ ret = tb_property_add_dir(svc->local_properties, name, dir);
+ if (ret) {
+ tb_property_free_dir(dir);
+ return ret;
+ }
+
+ dev_dbg(&svc->dev,
+ "added local directory %s: in HopID %d, out HopID %d\n",
+ name, in_hopid, out_hopid);
+ }
+
+ return 0;
+}
+
+static int tbstream_dev_update_properties(struct tbstream_dev *sdev)
+{
+ struct tbstream *stream;
+ int ret;
+
+ stream = tbstream_get(sdev->stream);
+ if (!stream)
+ return 0;
+
+ ret = service_update_properties(stream->svc,
+ config_item_name(&sdev->group.cg_item),
+ sdev->in_hopid, sdev->out_hopid);
+ if (!ret)
+ tb_service_properties_changed(stream->svc);
+
+ tbstream_put(stream);
+ return ret;
+}
+
+static int tbstream_dev_alloc_in_hopid(struct tbstream_dev *sdev, int hopid)
+{
+ struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
+ int ret;
+
+ if (sdev->in_hopid > 0 && sdev->in_hopid != hopid)
+ tb_xdomain_release_in_hopid(xd, sdev->in_hopid);
+ if (!hopid) {
+ sdev->in_hopid = hopid;
+ return 0;
+ }
+ ret = tb_xdomain_alloc_in_hopid(xd, hopid);
+ if (ret < 0)
+ return ret;
+ /*
+ * If specific HopID was asked by the user and we did not get
+ * that one then release and return error instead.
+ */
+ if (hopid > 0 && hopid != ret) {
+ tb_xdomain_release_in_hopid(xd, ret);
+ return -EBUSY;
+ }
+ sdev->in_hopid = ret;
+ return 0;
+}
+
+static int tbstream_dev_alloc_out_hopid(struct tbstream_dev *sdev, int hopid)
+{
+ struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
+ int ret;
+
+ if (sdev->out_hopid > 0 && sdev->out_hopid != hopid)
+ tb_xdomain_release_out_hopid(xd, sdev->out_hopid);
+ if (!hopid) {
+ sdev->out_hopid = hopid;
+ return 0;
+ }
+ ret = tb_xdomain_alloc_out_hopid(xd, hopid);
+ if (ret < 0)
+ return ret;
+ if (hopid > 0 && hopid != ret) {
+ tb_xdomain_release_out_hopid(xd, ret);
+ return -EBUSY;
+ }
+ sdev->out_hopid = ret;
+ return 0;
+}
+
+static ssize_t
+tbstream_dev_in_hopid_store(struct config_item *item, const char *buf,
+ size_t count)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+ int ret, in_hopid;
+
+ ret = kstrtoint(buf, 0, &in_hopid);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&sdev->lock);
+ if (sdev->users)
+ return -EBUSY;
+ if (sdev->stream) {
+ ret = tbstream_dev_alloc_in_hopid(sdev, in_hopid);
+ if (ret)
+ return ret;
+ ret = tbstream_dev_update_properties(sdev);
+ } else {
+ sdev->in_hopid = in_hopid;
+ }
+ return ret ? ret : count;
+}
+CONFIGFS_ATTR(tbstream_dev_, in_hopid);
+
+static ssize_t tbstream_dev_out_hopid_show(struct config_item *item, char *buf)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ return sysfs_emit(buf, "%d\n", sdev->out_hopid);
+}
+
+static ssize_t
+tbstream_dev_out_hopid_store(struct config_item *item, const char *buf,
+ size_t count)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+ int ret, out_hopid;
+
+ ret = kstrtoint(buf, 0, &out_hopid);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&sdev->lock);
+ if (sdev->users)
+ return -EBUSY;
+ if (sdev->stream) {
+ ret = tbstream_dev_alloc_out_hopid(sdev, out_hopid);
+ if (ret)
+ return ret;
+ ret = tbstream_dev_update_properties(sdev);
+ } else {
+ sdev->out_hopid = out_hopid;
+ }
+ return ret ? ret : count;
+}
+CONFIGFS_ATTR(tbstream_dev_, out_hopid);
+
+static ssize_t tbstream_dev_ring_size_show(struct config_item *item, char *buf)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ return sysfs_emit(buf, "%u\n", sdev->ring_size);
+}
+
+static ssize_t
+tbstream_dev_ring_size_store(struct config_item *item, const char *buf,
+ size_t count)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+ unsigned int ring_size;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &ring_size);
+ if (ret)
+ return ret;
+
+ if (ring_size < TBSTREAM_DEV_MIN_RING_SIZE ||
+ ring_size > TBSTREAM_DEV_MAX_RING_SIZE)
+ return -EINVAL;
+
+ guard(mutex)(&sdev->lock);
+ if (sdev->users)
+ return -EBUSY;
+ sdev->ring_size = ring_size;
+ return count;
+}
+CONFIGFS_ATTR(tbstream_dev_, ring_size);
+
+static ssize_t tbstream_dev_throttling_show(struct config_item *item, char *buf)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ return sysfs_emit(buf, "%u\n", sdev->throttling);
+}
+
+static ssize_t
+tbstream_dev_throttling_store(struct config_item *item, const char *buf,
+ size_t count)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+ unsigned int throttling;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &throttling);
+ if (ret)
+ return ret;
+
+ if (throttling > TBSTREAM_DEV_MAX_THROTTLING)
+ return -EINVAL;
+
+ guard(mutex)(&sdev->lock);
+ if (sdev->users)
+ return -EBUSY;
+ sdev->throttling = throttling;
+ return count;
+}
+CONFIGFS_ATTR(tbstream_dev_, throttling);
+
+static struct configfs_attribute *tbstream_dev_attrs[] = {
+ &tbstream_dev_attr_index,
+ &tbstream_dev_attr_in_hopid,
+ &tbstream_dev_attr_out_hopid,
+ &tbstream_dev_attr_ring_size,
+ &tbstream_dev_attr_throttling,
+ NULL,
+};
+
+static void tbstream_dev_item_release(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(group);
+
+ misc_deregister(&sdev->misc);
+ tbstream_dev_put(sdev);
+}
+
+static struct configfs_item_operations tbstream_dev_item_ops = {
+ .release = tbstream_dev_item_release,
+};
+
+static const struct config_item_type tbstream_dev_type = {
+ .ct_owner = THIS_MODULE,
+ .ct_item_ops = &tbstream_dev_item_ops,
+ .ct_attrs = tbstream_dev_attrs,
+};
+
+static void service_get_hopids(struct tb_service *svc, const char *name,
+ int *in_hopid, int *out_hopid)
+{
+ struct tb_property_dir *dir;
+ struct tb_property *p;
+
+ guard(mutex)(&svc->lock);
+
+ /* See if we have directory entry with the matching name */
+ p = tb_property_find(svc->remote_properties, name,
+ TB_PROPERTY_TYPE_DIRECTORY);
+ if (!p)
+ return;
+
+ dir = p->value.dir;
+
+ /*
+ * We need to reverse the HopIDs on our end so that in becomes
+ * out and vice versa.
+ */
+ p = tb_property_find(dir, "inhopid", TB_PROPERTY_TYPE_VALUE);
+ if (p && p->value.immediate >= 8)
+ *out_hopid = p->value.immediate;
+ p = tb_property_find(dir, "outhopid", TB_PROPERTY_TYPE_VALUE);
+ if (p && p->value.immediate >= 8)
+ *in_hopid = p->value.immediate;
+}
+
+static void
+tbstream_dev_attach_stream(struct tbstream_dev *sdev, struct tbstream_group *sg)
+{
+ const char *name = config_item_name(&sdev->group.cg_item);
+ struct tbstream *stream;
+
+ stream = tbstream_get(sg->stream);
+ if (!stream)
+ return;
+
+ scoped_guard(mutex, &sdev->lock) {
+ sdev->stream = stream;
+ /*
+ * If there is no existing configuration (or automatic
+ * configuration is being used) check if the other side
+ * has configuration for this and use it.
+ */
+ if (sdev->in_hopid <= 0 && sdev->out_hopid <= 0)
+ service_get_hopids(stream->svc, name, &sdev->in_hopid,
+ &sdev->out_hopid);
+ if (sdev->in_hopid)
+ tbstream_dev_alloc_in_hopid(sdev, sdev->in_hopid);
+ if (sdev->out_hopid)
+ tbstream_dev_alloc_out_hopid(sdev, sdev->out_hopid);
+ }
+
+ service_update_properties(stream->svc, name, sdev->in_hopid,
+ sdev->out_hopid);
+ tb_service_properties_changed(stream->svc);
+
+ /* Notify any openerers that the stream is now attached */
+ wake_up_interruptible(&sdev->wait);
+}
+
+static void tbstream_dev_detach_stream(struct tbstream_dev *sdev)
+{
+ const char *name = config_item_name(&sdev->group.cg_item);
+ struct tbstream *stream;
+ struct tb_xdomain *xd;
+
+ scoped_guard(mutex, &sdev->lock) {
+ stream = sdev->stream;
+ if (!stream)
+ return;
+ sdev->stream = NULL;
+ xd = tb_service_parent(stream->svc);
+ if (sdev->out_hopid > 0)
+ tb_xdomain_release_out_hopid(xd, sdev->out_hopid);
+ if (sdev->in_hopid > 0)
+ tb_xdomain_release_in_hopid(xd, sdev->in_hopid);
+ }
+
+ service_update_properties(stream->svc, name, 0, 0);
+ tb_service_properties_changed(stream->svc);
+
+ tbstream_put(stream);
+
+ /* Notify any task that the stream is not valid anymore */
+ wake_up_interruptible_poll(&sdev->wait, EPOLLHUP | EPOLLERR);
+}
+
+static inline struct tbstream_group *
+to_tbstream_group(struct config_group *group)
+{
+ return container_of(group, struct tbstream_group, group);
+}
+
+static struct config_group *
+tbstream_dev_make_group(struct config_group *group, const char *name)
+{
+ struct tbstream_group *sg = to_tbstream_group(group);
+ struct tbstream_dev *sdev;
+ int ret, index;
+
+ /*
+ * We want the names to be suitable for passing as property
+ * directory names.
+ */
+ if (strlen(name) > TB_PROPERTY_KEY_SIZE)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ sdev = kzalloc_obj(*sdev, GFP_KERNEL);
+ if (!sdev)
+ return ERR_PTR(-ENOMEM);
+
+ index = ida_alloc(&tbstream_indices, GFP_KERNEL);
+ if (index < 0) {
+ kfree(sdev);
+ return ERR_PTR(index);
+ }
+
+ sdev->index = index;
+ sdev->ring_size = TBSTREAM_DEV_RING_SIZE;
+ sdev->throttling = TBSTREAM_DEV_THROTTLING;
+ mutex_init(&sdev->lock);
+ init_waitqueue_head(&sdev->wait);
+ INIT_LIST_HEAD(&sdev->list);
+ /* This point forward tbstream_dev_put() must be used to release sdev */
+ kref_init(&sdev->kref);
+
+ config_group_init_type_name(&sdev->group, name, &tbstream_dev_type);
+
+ scoped_guard(mutex, &sg->lock)
+ list_add_tail(&sdev->list, &sg->dev_list);
+
+ tbstream_dev_attach_stream(sdev, sg);
+
+ sdev->misc.name = kasprintf(GFP_KERNEL, "tbstream%d", index);
+ sdev->misc.minor = MISC_DYNAMIC_MINOR;
+ sdev->misc.fops = &tbstream_dev_fops;
+
+ ret = misc_register(&sdev->misc);
+ if (ret) {
+ tbstream_dev_detach_stream(sdev);
+ scoped_guard(mutex, &sg->lock)
+ list_del(&sdev->list);
+ /* Calls tbstream_dev_put() */
+ config_group_put(&sdev->group);
+ return ERR_PTR(ret);
+ }
+
+ return &sdev->group;
+}
+
+static void
+tbstream_dev_drop_item(struct config_group *group, struct config_item *item)
+{
+ struct config_group *sdev_group = to_config_group(item);
+ struct tbstream_dev *sdev = tbstream_dev_from_group(sdev_group);
+ struct tbstream_group *sg = to_tbstream_group(group);
+
+ scoped_guard(mutex, &sg->lock)
+ list_del(&sdev->list);
+ /* Notify any task that the underlying group was removed */
+ sdev->removed = true;
+ wake_up_interruptible_poll(&sdev->wait, EPOLLHUP | EPOLLERR);
+ config_item_put(item);
+}
+
+static struct configfs_group_operations tbstream_dev_group_ops = {
+ .make_group = tbstream_dev_make_group,
+ .drop_item = tbstream_dev_drop_item,
+};
+
+static void tbstream_item_release(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+ struct tbstream_group *sg = to_tbstream_group(group);
+
+ tbstream_put(sg->stream);
+ kfree(sg);
+}
+
+static struct configfs_item_operations tbstream_item_ops = {
+ .release = tbstream_item_release,
+};
+
+static const struct config_item_type tbstream_dev_group_type = {
+ .ct_owner = THIS_MODULE,
+ .ct_group_ops = &tbstream_dev_group_ops,
+ .ct_item_ops = &tbstream_item_ops,
+};
+
+static struct config_group *
+tbstream_make_group(struct config_group *group, const char *name)
+{
+ struct tbstream_group *sg;
+ struct tbstream *stream;
+ int domain, index;
+ u64 route;
+
+ /* Make sure the format is correct */
+ if (sscanf(name, "%u-%llx.%u", &domain, &route, &index) != 3)
+ return ERR_PTR(-EINVAL);
+
+ sg = kzalloc_obj(*sg, GFP_KERNEL);
+ if (!sg)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&sg->lock);
+ INIT_LIST_HEAD(&sg->dev_list);
+
+ guard(mutex)(&tbstream_lock);
+ list_for_each_entry(stream, &tbstream_list, list) {
+ tbstream_get(stream);
+ if (sysfs_streq(name, dev_name(&stream->svc->dev))) {
+ sg->stream = stream;
+ break;
+ }
+ tbstream_put(stream);
+ }
+
+ config_group_init_type_name(&sg->group, name, &tbstream_dev_group_type);
+ return &sg->group;
+}
+
+static struct configfs_group_operations tbstream_group_ops = {
+ .make_group = tbstream_make_group,
+};
+
+static const struct config_item_type tbstream_group_type = {
+ .ct_owner = THIS_MODULE,
+ .ct_group_ops = &tbstream_group_ops,
+};
+
+static struct config_group tbstream_group = {
+ .cg_item = {
+ .ci_namebuf = "stream",
+ .ci_type = &tbstream_group_type,
+ },
+};
+
+/* Returns reference count increased */
+static struct tbstream_group *tbstream_group_find(struct tbstream *stream)
+{
+ const char *name = dev_name(&stream->svc->dev);
+ struct config_item *item;
+
+ guard(mutex)(&tbstream_group.cg_subsys->su_mutex);
+ item = config_group_find_item(&tbstream_group, name);
+ if (!item)
+ return NULL;
+ return to_tbstream_group(to_config_group(item));
+}
+
+static void tbstream_group_attach_stream(struct tbstream *stream)
+{
+ struct tbstream_group *sg;
+ struct tbstream_dev *sdev;
+
+ sg = tbstream_group_find(stream);
+ if (!sg)
+ return;
+
+ guard(mutex)(&sg->lock);
+ if (WARN_ON(sg->stream)) {
+ config_group_put(&sg->group);
+ return;
+ }
+ sg->stream = tbstream_get(stream);
+ /*
+ * If there are existing stream devices, attach the stream to
+ * them now.
+ */
+ list_for_each_entry(sdev, &sg->dev_list, list) {
+ tbstream_dev_get(sdev);
+ tbstream_dev_attach_stream(sdev, sg);
+ tbstream_dev_put(sdev);
+ }
+
+ config_group_put(&sg->group);
+}
+
+static void tbstream_group_detach_stream(struct tbstream *stream)
+{
+ struct tbstream_group *sg;
+ struct tbstream_dev *sdev;
+
+ sg = tbstream_group_find(stream);
+ if (!sg)
+ return;
+
+ guard(mutex)(&sg->lock);
+ if (sg->stream) {
+ /* Detach this stream from the stream devices */
+ list_for_each_entry_reverse(sdev, &sg->dev_list, list) {
+ tbstream_dev_get(sdev);
+ tbstream_dev_detach_stream(sdev);
+ tbstream_dev_put(sdev);
+ }
+ tbstream_put(sg->stream);
+ sg->stream = NULL;
+ }
+
+ config_group_put(&sg->group);
+}
+
+static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id)
+{
+ struct tbstream *stream;
+
+ stream = kzalloc_obj(*stream, GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+
+ /* After this point, release stream by calling tbstream_put() */
+ kref_init(&stream->kref);
+ stream->svc = tb_service_get(svc);
+ INIT_LIST_HEAD(&stream->list);
+
+ scoped_guard(mutex, &tbstream_lock)
+ list_add_tail(&stream->list, &tbstream_list);
+
+ tbstream_group_attach_stream(stream);
+ tb_service_set_drvdata(svc, stream);
+ return 0;
+}
+
+static void tbstream_remove(struct tb_service *svc)
+{
+ struct tbstream *stream = tb_service_get_drvdata(svc);
+
+ tbstream_group_detach_stream(stream);
+ scoped_guard(mutex, &tbstream_lock)
+ list_del(&stream->list);
+ tbstream_put(stream);
+}
+
+static int __maybe_unused tbstream_suspend(struct device *dev)
+{
+ struct tb_service *svc = tb_to_service(dev);
+ struct tbstream *stream = tb_service_get_drvdata(svc);
+ struct tbstream_group *sg;
+ struct tbstream_dev *sdev;
+
+ sg = tbstream_group_find(stream);
+ if (!sg)
+ return 0;
+
+ list_for_each_entry_reverse(sdev, &sg->dev_list, list) {
+ tbstream_dev_get(sdev);
+ /* Stop the stream (if it was open) */
+ if (sdev->users)
+ tbstream_dev_stop(sdev);
+ tbstream_dev_put(sdev);
+ }
+
+ config_group_put(&sg->group);
+ return 0;
+}
+
+static int __maybe_unused tbstream_resume(struct device *dev)
+{
+ struct tb_service *svc = tb_to_service(dev);
+ struct tbstream *stream = tb_service_get_drvdata(svc);
+ struct tbstream_group *sg;
+ struct tbstream_dev *sdev;
+
+ sg = tbstream_group_find(stream);
+ if (!sg)
+ return 0;
+
+ list_for_each_entry(sdev, &sg->dev_list, list) {
+ tbstream_dev_get(sdev);
+ if (sdev->users) {
+ int ret;
+
+ ret = tbstream_dev_start(sdev);
+ if (ret) {
+ tbstream_dev_put(sdev);
+ config_group_put(&sg->group);
+ return ret;
+ }
+ }
+ tbstream_dev_put(sdev);
+ }
+
+ config_group_put(&sg->group);
+ return 0;
+}
+
+static const struct dev_pm_ops tbstream_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(tbstream_suspend, tbstream_resume)
+};
+
+static const struct tb_service_id tbstream_ids[] = {
+ { TB_SERVICE("stream", 1) },
+ { },
+};
+MODULE_DEVICE_TABLE(tbsvc, tbstream_ids);
+
+static struct tb_service_driver tbstream_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "thunderbolt_stream",
+ .pm = &tbstream_pm_ops,
+ },
+ .probe = tbstream_probe,
+ .remove = tbstream_remove,
+ .id_table = tbstream_ids,
+};
+
+static int __init tbstream_init(void)
+{
+ int ret;
+
+ tbstream_dir = tb_property_create_dir(&tbstream_dir_uuid);
+ if (!tbstream_dir)
+ return -ENOMEM;
+
+ tb_property_add_immediate(tbstream_dir, "prtcid", 1);
+ tb_property_add_immediate(tbstream_dir, "prtcvers", 1);
+ tb_property_add_immediate(tbstream_dir, "prtcrevs", 0);
+ tb_property_add_immediate(tbstream_dir, "prtcstns", 0);
+
+ ret = tb_register_property_dir("stream", tbstream_dir);
+ if (ret)
+ goto err_free_dir;
+
+ config_group_init(&tbstream_group);
+ ret = tb_configfs_register_group(&tbstream_group);
+ if (ret)
+ goto err_unregister_dir;
+
+ ret = tb_register_service_driver(&tbstream_driver);
+ if (ret)
+ goto err_unregister_group;
+ return 0;
+
+err_unregister_group:
+ tb_configfs_unregister_group(&tbstream_group);
+err_unregister_dir:
+ tb_unregister_property_dir("stream", tbstream_dir);
+err_free_dir:
+ tb_property_free_dir(tbstream_dir);
+ return ret;
+}
+module_init(tbstream_init);
+
+static void __exit tbstream_exit(void)
+{
+ tb_unregister_service_driver(&tbstream_driver);
+ tb_configfs_unregister_group(&tbstream_group);
+ tb_unregister_property_dir("stream", tbstream_dir);
+ tb_property_free_dir(tbstream_dir);
+ ida_destroy(&tbstream_indices);
+}
+module_exit(tbstream_exit);
+
+MODULE_AUTHOR("Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_DESCRIPTION("Stream data over Thunderbolt/USB4 cable");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index c2ad58b19e7b..a830c82bb905 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -209,30 +209,6 @@ static int nvm_authenticate_device_dma_port(struct tb_switch *sw)
return -ETIMEDOUT;
}
-static void nvm_authenticate_start_dma_port(struct tb_switch *sw)
-{
- struct pci_dev *root_port;
-
- /*
- * During host router NVM upgrade we should not allow root port to
- * go into D3cold because some root ports cannot trigger PME
- * itself. To be on the safe side keep the root port in D0 during
- * the whole upgrade process.
- */
- root_port = pcie_find_root_port(sw->tb->nhi->pdev);
- if (root_port)
- pm_runtime_get_noresume(&root_port->dev);
-}
-
-static void nvm_authenticate_complete_dma_port(struct tb_switch *sw)
-{
- struct pci_dev *root_port;
-
- root_port = pcie_find_root_port(sw->tb->nhi->pdev);
- if (root_port)
- pm_runtime_put(&root_port->dev);
-}
-
static inline bool nvm_readable(struct tb_switch *sw)
{
if (tb_switch_is_usb4(sw)) {
@@ -258,6 +234,7 @@ static inline bool nvm_upgradeable(struct tb_switch *sw)
static int nvm_authenticate(struct tb_switch *sw, bool auth_only)
{
+ struct tb_nhi *nhi = sw->tb->nhi;
int ret;
if (tb_switch_is_usb4(sw)) {
@@ -274,7 +251,8 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only)
sw->nvm->authenticating = true;
if (!tb_route(sw)) {
- nvm_authenticate_start_dma_port(sw);
+ if (nhi->ops->pre_nvm_auth)
+ nhi->ops->pre_nvm_auth(nhi);
ret = nvm_authenticate_host_dma_port(sw);
} else {
ret = nvm_authenticate_device_dma_port(sw);
@@ -1620,6 +1598,12 @@ static int tb_switch_reset_host(struct tb_switch *sw)
ret = tb_port_reset(port);
if (ret)
return ret;
+ /*
+ * USB4 Lane 1 adapters do not have accessible
+ * path config space.
+ */
+ if (tb_switch_is_usb4(sw) && !port->usb4)
+ continue;
} else if (tb_port_is_usb3_down(port) ||
tb_port_is_usb3_up(port)) {
tb_usb3_port_enable(port, false);
@@ -1761,8 +1745,6 @@ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
/*
* tb_plug_events_active() - enable/disable plug events on a switch
*
- * Also configures a sane plug_events_delay of 255ms.
- *
* Return: %0 on success, negative errno otherwise.
*/
static int tb_plug_events_active(struct tb_switch *sw, bool active)
@@ -1773,11 +1755,6 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw))
return 0;
- sw->config.plug_events_delay = 0xff;
- res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1);
- if (res)
- return res;
-
res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
if (res)
return res;
@@ -2639,6 +2616,8 @@ int tb_switch_configure(struct tb_switch *sw)
sw->config.enabled = 1;
+ /* Set Notification Timeout to 255 ms for all routers */
+ sw->config.plug_events_delay = 0xff;
if (tb_switch_is_usb4(sw)) {
/*
* For USB4 devices, we need to program the CM version
@@ -2650,7 +2629,6 @@ int tb_switch_configure(struct tb_switch *sw)
sw->config.cmuv = ROUTER_CS_4_CMUV_V1;
else
sw->config.cmuv = ROUTER_CS_4_CMUV_V2;
- sw->config.plug_events_delay = 0xa;
/* Enumerate the switch */
ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
@@ -2671,7 +2649,7 @@ int tb_switch_configure(struct tb_switch *sw)
/* Enumerate the switch */
ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
- ROUTER_CS_1, 3);
+ ROUTER_CS_1, 4);
}
if (ret)
return ret;
@@ -2743,6 +2721,7 @@ static int tb_switch_set_uuid(struct tb_switch *sw)
static int tb_switch_add_dma_port(struct tb_switch *sw)
{
+ struct tb_nhi *nhi = sw->tb->nhi;
u32 status;
int ret;
@@ -2802,8 +2781,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
*/
nvm_get_auth_status(sw, &status);
if (status) {
- if (!tb_route(sw))
- nvm_authenticate_complete_dma_port(sw);
+ if (!tb_route(sw)) {
+ if (nhi->ops->post_nvm_auth)
+ nhi->ops->post_nvm_auth(nhi);
+ }
return 0;
}
@@ -2817,8 +2798,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
return ret;
/* Now we can allow root port to suspend again */
- if (!tb_route(sw))
- nvm_authenticate_complete_dma_port(sw);
+ if (!tb_route(sw)) {
+ if (nhi->ops->post_nvm_auth)
+ nhi->ops->post_nvm_auth(nhi);
+ }
if (status) {
tb_sw_info(sw, "switch flash authentication failed\n");
@@ -2971,14 +2954,14 @@ static int tb_switch_lane_bonding_enable(struct tb_switch *sw)
int ret;
if (!tb_switch_lane_bonding_possible(sw))
- return 0;
+ return -EOPNOTSUPP;
up = tb_upstream_port(sw);
down = tb_switch_downstream_port(sw);
if (!tb_port_width_supported(up, TB_LINK_WIDTH_DUAL) ||
!tb_port_width_supported(down, TB_LINK_WIDTH_DUAL))
- return 0;
+ return -EOPNOTSUPP;
/*
* Both lanes need to be in CL0. Here we assume lane 0 already be in
@@ -3625,6 +3608,20 @@ int tb_switch_resume(struct tb_switch *sw, bool runtime)
tb_port_warn(port,
"lost during suspend, disconnecting\n");
tb_sw_set_unplugged(port->remote->sw);
+ } else if (port->xdomain) {
+ /*
+ * If the user replaced the XDomain with
+ * another router, this will succeed in
+ * which case we must remove the XDomain
+ * before adding the new router.
+ */
+ err = tb_cfg_get_upstream_port(sw->tb->ctl,
+ port->xdomain->route);
+ if (err > 0) {
+ tb_port_warn(port,
+ "XDomain was disconnected\n");
+ port->xdomain->is_unplugged = true;
+ }
}
}
}
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index c69c323e6952..76323255439a 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -98,7 +98,7 @@ static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug)
if (!ev)
return;
- ev->tb = tb;
+ ev->tb = tb_domain_get(tb);
ev->route = route;
ev->port = port;
ev->unplug = unplug;
@@ -2524,9 +2524,14 @@ put_sw:
out:
mutex_unlock(&tb->lock);
+ tb_domain_unregister_unplugged_xdomains(tb);
+
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
+ /* Undo the refcount increased in tb_queue_hotplug() */
+ tb_domain_put(tb);
+
kfree(ev);
}
@@ -2844,6 +2849,9 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work)
/* Update other clients about the allocation change */
tb_recalc_estimated_bandwidth(tb);
+
+ tb_dbg(tb, "checking if more DP tunnels can be established now\n");
+ tb_tunnel_dp(tb);
}
put_sw:
@@ -2949,6 +2957,7 @@ static void tb_stop(struct tb *tb)
tb_tunnel_put(tunnel);
}
tb_switch_remove(tb->root_switch);
+ tb->root_switch = NULL;
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
}
@@ -2991,7 +3000,8 @@ static int tb_start(struct tb *tb, bool reset)
tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0);
if (IS_ERR(tb->root_switch))
- return PTR_ERR(tb->root_switch);
+ return dev_err_probe(tb->nhi->dev, PTR_ERR(tb->root_switch),
+ "failed to allocate host router\n");
/*
* ICM firmware upgrade needs running firmware and in native
@@ -3008,14 +3018,14 @@ static int tb_start(struct tb *tb, bool reset)
ret = tb_switch_configure(tb->root_switch);
if (ret) {
tb_switch_put(tb->root_switch);
- return ret;
+ return dev_err_probe(tb->nhi->dev, ret, "failed to configure host router\n");
}
/* Announce the switch to the world */
ret = tb_switch_add(tb->root_switch);
if (ret) {
tb_switch_put(tb->root_switch);
- return ret;
+ return dev_err_probe(tb->nhi->dev, ret, "failed to add host router\n");
}
/*
@@ -3110,6 +3120,24 @@ static void tb_restore_children(struct tb_switch *sw)
}
}
+static void tb_free_unplugged_xdomains(struct tb_switch *sw)
+{
+ struct tb_port *port;
+
+ tb_switch_for_each_port(sw, port) {
+ if (tb_is_upstream_port(port))
+ continue;
+ if (port->xdomain && port->xdomain->is_unplugged) {
+ tb_retimer_remove_all(port);
+ tb_xdomain_remove(port->xdomain);
+ tb_port_unconfigure_xdomain(port);
+ port->xdomain = NULL;
+ } else if (port->remote) {
+ tb_free_unplugged_xdomains(port->remote->sw);
+ }
+ }
+}
+
static int tb_resume_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
@@ -3129,6 +3157,7 @@ static int tb_resume_noirq(struct tb *tb)
tb_switch_resume(tb->root_switch, false);
tb_free_invalid_tunnels(tb);
tb_free_unplugged_children(tb->root_switch);
+ tb_free_unplugged_xdomains(tb->root_switch);
tb_restore_children(tb->root_switch);
/*
@@ -3171,28 +3200,6 @@ static int tb_resume_noirq(struct tb *tb)
return 0;
}
-static int tb_free_unplugged_xdomains(struct tb_switch *sw)
-{
- struct tb_port *port;
- int ret = 0;
-
- tb_switch_for_each_port(sw, port) {
- if (tb_is_upstream_port(port))
- continue;
- if (port->xdomain && port->xdomain->is_unplugged) {
- tb_retimer_remove_all(port);
- tb_xdomain_remove(port->xdomain);
- tb_port_unconfigure_xdomain(port);
- port->xdomain = NULL;
- ret++;
- } else if (port->remote) {
- ret += tb_free_unplugged_xdomains(port->remote->sw);
- }
- }
-
- return ret;
-}
-
static int tb_freeze_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
@@ -3212,14 +3219,14 @@ static int tb_thaw_noirq(struct tb *tb)
static void tb_complete(struct tb *tb)
{
/*
- * Release any unplugged XDomains and if there is a case where
+ * Unregister unplugged XDomains and if there is a case where
* another domain is swapped in place of unplugged XDomain we
* need to run another rescan.
*/
- mutex_lock(&tb->lock);
- if (tb_free_unplugged_xdomains(tb->root_switch))
- tb_scan_switch(tb->root_switch);
- mutex_unlock(&tb->lock);
+ if (tb_domain_unregister_unplugged_xdomains(tb)) {
+ scoped_guard(mutex, &tb->lock)
+ tb_scan_switch(tb->root_switch);
+ }
}
static int tb_runtime_suspend(struct tb *tb)
@@ -3246,11 +3253,11 @@ static void tb_remove_work(struct work_struct *work)
struct tb *tb = tcm_to_tb(tcm);
mutex_lock(&tb->lock);
- if (tb->root_switch) {
+ if (tb->root_switch)
tb_free_unplugged_children(tb->root_switch);
- tb_free_unplugged_xdomains(tb->root_switch);
- }
mutex_unlock(&tb->lock);
+
+ tb_free_unplugged_xdomains(tb->root_switch);
}
static int tb_runtime_resume(struct tb *tb)
@@ -3304,13 +3311,14 @@ static const struct tb_cm_ops tb_cm_ops = {
*/
static bool tb_apple_add_links(struct tb_nhi *nhi)
{
+ struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
struct pci_dev *upstream, *pdev;
bool ret;
if (!x86_apple_machine)
return false;
- switch (nhi->pdev->device) {
+ switch (nhi_pdev->device) {
case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
@@ -3320,7 +3328,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi)
return false;
}
- upstream = pci_upstream_bridge(nhi->pdev);
+ upstream = pci_upstream_bridge(nhi_pdev);
while (upstream) {
if (!pci_is_pcie(upstream))
return false;
@@ -3347,15 +3355,15 @@ static bool tb_apple_add_links(struct tb_nhi *nhi)
!pdev->is_pciehp)
continue;
- link = device_link_add(&pdev->dev, &nhi->pdev->dev,
+ link = device_link_add(&pdev->dev, nhi->dev,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_PM_RUNTIME);
if (link) {
- dev_dbg(&nhi->pdev->dev, "created link from %s\n",
+ dev_dbg(nhi->dev, "created link from %s\n",
dev_name(&pdev->dev));
ret = true;
} else {
- dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
+ dev_warn(nhi->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
}
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 217c3114bec8..ec9192b61bc0 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -725,11 +725,11 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer,
length);
}
-#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_dbg(tb, fmt, arg...) dev_dbg(&(tb)->nhi->pdev->dev, fmt, ## arg)
+#define tb_err(tb, fmt, arg...) dev_err((tb)->nhi->dev, fmt, ## arg)
+#define tb_WARN(tb, fmt, arg...) dev_WARN((tb)->nhi->dev, fmt, ## arg)
+#define tb_warn(tb, fmt, arg...) dev_warn((tb)->nhi->dev, fmt, ## arg)
+#define tb_info(tb, fmt, arg...) dev_info((tb)->nhi->dev, fmt, ## arg)
+#define tb_dbg(tb, fmt, arg...) dev_dbg((tb)->nhi->dev, fmt, ## arg)
#define __TB_SW_PRINT(level, sw, fmt, arg...) \
do { \
@@ -793,6 +793,7 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring);
int tb_domain_disconnect_all_paths(struct tb *tb);
+int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
static inline struct tb *tb_domain_get(struct tb *tb)
{
@@ -1263,6 +1264,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
const uuid_t *remote_uuid);
void tb_xdomain_add(struct tb_xdomain *xd);
void tb_xdomain_remove(struct tb_xdomain *xd);
+void tb_xdomain_unregister(struct tb_xdomain *xd);
struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
u8 depth);
@@ -1479,6 +1481,7 @@ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw);
int usb4_dp_port_requested_bandwidth(struct tb_port *port);
int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable);
+int usb4_pci_port_ltssm_state(struct tb_port *port);
static inline bool tb_is_usb4_port_device(const struct device *dev)
{
@@ -1556,4 +1559,12 @@ static inline void tb_retimer_debugfs_init(struct tb_retimer *rt) { }
static inline void tb_retimer_debugfs_remove(struct tb_retimer *rt) { }
#endif
+#if IS_REACHABLE(CONFIG_CONFIGFS_FS)
+int tb_configfs_init(void);
+void tb_configfs_exit(void);
+#else
+static inline int tb_configfs_init(void) { return 0; }
+static inline void tb_configfs_exit(void) { }
+#endif
+
#endif
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
index c0bf136236e6..92d893634d2b 100644
--- a/drivers/thunderbolt/tb_regs.h
+++ b/drivers/thunderbolt/tb_regs.h
@@ -182,8 +182,7 @@ struct tb_regs_switch_header {
/* DWORD 4 */
u32 plug_events_delay:8; /*
* RW, pause between plug events in
- * milliseconds. Writing 0x00 is interpreted
- * as 255ms.
+ * milliseconds.
*/
u32 cmuv:8;
u32 __unknown4:8;
@@ -216,6 +215,7 @@ struct tb_regs_switch_header {
#define ROUTER_CS_6_WOPS BIT(2)
#define ROUTER_CS_6_WOUS BIT(3)
#define ROUTER_CS_6_HCI BIT(18)
+#define ROUTER_CS_6_RR BIT(24)
#define ROUTER_CS_6_CR BIT(25)
#define ROUTER_CS_7 0x07
#define ROUTER_CS_9 0x09
@@ -473,10 +473,25 @@ struct tb_regs_port_header {
/* PCIe adapter registers */
#define ADP_PCIE_CS_0 0x00
+#define ADP_PCIE_CS_0_LTSSM_MASK GENMASK(28, 25)
#define ADP_PCIE_CS_0_PE BIT(31)
#define ADP_PCIE_CS_1 0x01
#define ADP_PCIE_CS_1_EE BIT(0)
+enum tb_pcie_ltssm_state {
+ USB4_PCIE_LTSSM_DETECT,
+ USB4_PCIE_LTSSM_POLLING,
+ USB4_PCIE_LTSSM_CONFIG,
+ USB4_PCIE_LTSSM_CONFIG_IDLE,
+ USB4_PCIE_LTSSM_RECOVERY,
+ USB4_PCIE_LTSSM_RECOVERY_IDLE,
+ USB4_PCIE_LTSSM_L0,
+ USB4_PCIE_LTSSM_L1,
+ USB4_PCIE_LTSSM_L2,
+ USB4_PCIE_LTSSM_DISABLED,
+ USB4_PCIE_LTSSM_HOT_RESET,
+};
+
/* USB adapter registers */
#define ADP_USB3_CS_0 0x00
#define ADP_USB3_CS_0_V BIT(30)
diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c
index 1f4318249c22..05652ee82fbf 100644
--- a/drivers/thunderbolt/test.c
+++ b/drivers/thunderbolt/test.c
@@ -1661,6 +1661,7 @@ static void tb_test_tunnel_3dp(struct kunit *test)
KUNIT_ASSERT_EQ(test, tunnel3->npaths, 3);
KUNIT_ASSERT_EQ(test, tunnel3->paths[0]->path_length, 3);
+ tb_tunnel_put(tunnel3);
tb_tunnel_put(tunnel2);
tb_tunnel_put(tunnel1);
}
@@ -2852,7 +2853,252 @@ static void tb_test_property_copy(struct kunit *test)
tb_property_free_dir(src);
}
+/*
+ * Reproducers for three memory-safety defects in
+ * drivers/thunderbolt/property.c reached from a crafted XDomain
+ * PROPERTIES_RESPONSE payload. Without the fix these trip KASAN or
+ * smash the kernel stack; with the fix each returns NULL cleanly.
+ *
+ * The on-wire entry layout matches struct tb_property_entry in
+ * property.c (private to that translation unit): u32 key_hi, u32
+ * key_lo, then a packed u32 = (type << 24) | (reserved << 16) |
+ * length, then u32 value. Each entry is 4 dwords.
+ */
+
+static void tb_test_property_parse_u32_wrap(struct kunit *test)
+{
+ /*
+ * 0x102 dwords: enough for the entry's length field (0x100) to
+ * pass the "entry->length > block_len" gate so the wrap check
+ * is actually exercised. parse_dwdata's downstream OOB read
+ * lands ~16 GiB past the allocation regardless.
+ */
+ u32 *block = kunit_kzalloc(test, 0x102 * sizeof(u32), GFP_KERNEL);
+ struct tb_property_dir *dir;
+
+ KUNIT_ASSERT_NOT_NULL(test, block);
+
+ block[0] = 0x55584401; /* "UXD" v1 magic */
+ block[1] = 0x00000004; /* Root directory length: one entry */
+
+ /*
+ * DATA entry whose value 0xffffff00 + length 0x100 wrap to 0
+ * in u32, passing the sum <= block_len guard even though the
+ * real offset is far past the allocation.
+ */
+ block[2] = 0x61616161; /* key_hi */
+ block[3] = 0x61616161; /* key_lo */
+ block[4] = 0x64000100; /* type=DATA, reserved=0, length=0x100 */
+ block[5] = 0xffffff00; /* value */
+
+ dir = tb_property_parse_dir(block, 0x102);
+ KUNIT_EXPECT_NULL(test, dir);
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_parse_recursion(struct kunit *test)
+{
+ /*
+ * 10 dwords: rootdir header (2) + parent DIRECTORY entry (4) +
+ * the child entry that lives at dir_offset(2) + UUID(4) = 6,
+ * occupying block[6..9]. Each recursive level re-reads the
+ * same block[6..9] as its first child entry, which is itself
+ * a DIRECTORY pointing at offset 2.
+ */
+ u32 *block = kunit_kzalloc(test, 10 * sizeof(u32), GFP_KERNEL);
+ struct tb_property_dir *dir;
+
+ KUNIT_ASSERT_NOT_NULL(test, block);
+
+ block[0] = 0x55584401; /* "UXD" v1 magic */
+ block[1] = 0x00000004; /* Root directory length: one entry */
+
+ /*
+ * DIRECTORY entry pointing at dir_offset = 2 with length = 8.
+ * Non-root parse derives content_offset = 6, content_len = 4,
+ * nentries = 1. block[6..9] is read both as the parent's UUID
+ * (kmemdup'd into dir->uuid) and as the single child entry --
+ * which is itself a DIRECTORY pointing at offset 2, so the
+ * recursion never terminates and the kernel stack is exhausted.
+ */
+ block[2] = 0x61616161; /* key_hi */
+ block[3] = 0x61616161; /* key_lo */
+ block[4] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */
+ block[5] = 0x00000002; /* value = dir_offset */
+
+ block[6] = 0x62626262; /* doubles as UUID dword 0 / child key_hi */
+ block[7] = 0x62626262; /* doubles as UUID dword 1 / child key_lo */
+ block[8] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */
+ block[9] = 0x00000002; /* value = dir_offset (back at parent) */
+
+ dir = tb_property_parse_dir(block, 10);
+ KUNIT_EXPECT_NULL(test, dir);
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_parse_dir_len_underflow(struct kunit *test)
+{
+ /*
+ * Allocate exactly 7 dwords (28 bytes) so the kmalloc-32 chunk
+ * leaves a 4-byte slab redzone tail that KASAN-Generic can flag.
+ * With block_len = 7, dir_offset = 4, dir_len = 3, the non-root
+ * UUID kmemdup reads 16 bytes from byte 16, so bytes 28..31 fall
+ * in the redzone and trip a KASAN slab-out-of-bounds report on
+ * the pre-fix kernel. Sizing the buffer at a power of two (32,
+ * 64, ...) puts the over-read into the slab cache tail where
+ * KASAN's generic shadow does not flag it, and the test reduces
+ * to the downstream content_len = dir_len - 4 underflow path
+ * which also returns NULL.
+ */
+ u32 *block = kunit_kzalloc(test, 7 * sizeof(u32), GFP_KERNEL);
+ struct tb_property_dir *dir;
+
+ KUNIT_ASSERT_NOT_NULL(test, block);
+
+ block[0] = 0x55584401; /* "UXD" v1 magic */
+ block[1] = 0x00000004; /* Root directory length: one entry */
+
+ /*
+ * DIRECTORY entry with length = 3 pointing at dir_offset = 4.
+ * tb_property_entry_valid() permits value(4) + length(3) <=
+ * block_len(7). Non-root parse begins with a kmemdup of 4
+ * dwords from dir_offset for the UUID; that read runs past the
+ * 28-byte allocation before the dir_len < 4 reject would fire.
+ */
+ block[2] = 0x61616161; /* key_hi */
+ block[3] = 0x61616161; /* key_lo */
+ block[4] = 0x44000003; /* type=DIRECTORY, reserved=0, length=3 */
+ block[5] = 0x00000004; /* value = dir_offset */
+ /* block[6] is the start of the four UUID dwords; block[7..] is OOB. */
+
+ dir = tb_property_parse_dir(block, 7);
+ KUNIT_EXPECT_NULL(test, dir);
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_parse_zero_length(struct kunit *test)
+{
+ u32 *block = kunit_kzalloc(test, 6 * sizeof(u32), GFP_KERNEL);
+ struct tb_property_dir *dir;
+
+ KUNIT_ASSERT_NOT_NULL(test, block);
+
+ block[0] = 0x55584401; /* rootdir magic */
+ block[1] = 0x00000004; /* root length: one entry */
+
+ block[2] = 0x61616161; /* key_hi */
+ block[3] = 0x61616161; /* key_lo */
+ block[4] = 0x74000000; /* type=TEXT, reserved=0, length=0 */
+ block[5] = 0x00000000; /* value */
+
+ dir = tb_property_parse_dir(block, 6);
+ KUNIT_EXPECT_NULL(test, dir);
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_parse_rootdir_overflow(struct kunit *test)
+{
+ u32 *block = kunit_kzalloc(test, 4 * sizeof(u32), GFP_KERNEL);
+ struct tb_property_dir *dir;
+
+ KUNIT_ASSERT_NOT_NULL(test, block);
+
+ block[0] = 0x55584401; /* rootdir magic */
+ block[1] = 0x00000004; /* root length claims 4 dwords of content */
+ block[2] = 0x61616161;
+ block[3] = 0x61616161;
+
+ /* content_offset(2) + content_len(4) = 6 > block_len(4) */
+ dir = tb_property_parse_dir(block, 4);
+ KUNIT_EXPECT_NULL(test, dir);
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_merge(struct kunit *test)
+{
+ struct tb_property_dir *dir1, *dir2, *dir3;
+ struct tb_property *p;
+ uuid_t uuid;
+ int ret;
+
+ dir1 = tb_property_create_dir(&network_dir_uuid);
+ KUNIT_ASSERT_NOT_NULL(test, dir1);
+ ret = tb_property_add_immediate(dir1, "prtcid", 1);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = tb_property_add_immediate(dir1, "prtcvers", 1);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = tb_property_add_immediate(dir1, "prtcrevs", 0);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = tb_property_add_immediate(dir1, "prtcstns", 0);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ dir2 = tb_property_create_dir(&network_dir_uuid);
+ KUNIT_ASSERT_NOT_NULL(test, dir2);
+ ret = tb_property_add_text(dir2, "descr", "This is text");
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ /* This replaces the value in dir1 */
+ ret = tb_property_add_immediate(dir2, "prtcvers", 0x1234);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ uuid_gen(&uuid);
+ dir3 = tb_property_create_dir(&uuid);
+ KUNIT_ASSERT_NOT_NULL(test, dir3);
+ ret = tb_property_add_immediate(dir3, "value0", 0);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = tb_property_add_text(dir3, "value1", "Text value");
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = tb_property_add_dir(dir2, "my", dir3);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ ret = tb_property_merge_dir(dir1, dir2, true);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ p = tb_property_get_next(dir1, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcid");
+ KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_EQ(test, p->length, 1);
+ KUNIT_ASSERT_EQ(test, p->value.immediate, 1);
+ p = tb_property_get_next(dir1, p);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcvers");
+ KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_EQ(test, p->length, 1);
+ KUNIT_ASSERT_EQ(test, p->value.immediate, 0x1234);
+ p = tb_property_get_next(dir1, p);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcrevs");
+ KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_EQ(test, p->length, 1);
+ KUNIT_ASSERT_EQ(test, p->value.immediate, 0);
+ p = tb_property_get_next(dir1, p);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcstns");
+ KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_EQ(test, p->length, 1);
+ KUNIT_ASSERT_EQ(test, p->value.immediate, 0);
+ p = tb_property_get_next(dir1, p);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_ASSERT_STREQ(test, &p->key[0], "descr");
+ KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_EQ(test, p->length, 4);
+ KUNIT_ASSERT_STREQ(test, p->value.text, "This is text");
+ p = tb_property_get_next(dir1, p);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_ASSERT_STREQ(test, &p->key[0], "my");
+ KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_DIRECTORY);
+ compare_dirs(test, p->value.dir, dir3);
+ p = tb_property_get_next(dir1, p);
+ KUNIT_ASSERT_NULL(test, p);
+
+ tb_property_free_dir(dir2);
+ tb_property_free_dir(dir1);
+}
+
static struct kunit_case tb_test_cases[] = {
+ KUNIT_CASE(tb_test_property_parse_u32_wrap),
+ KUNIT_CASE(tb_test_property_parse_recursion),
+ KUNIT_CASE(tb_test_property_parse_dir_len_underflow),
KUNIT_CASE(tb_test_path_basic),
KUNIT_CASE(tb_test_path_not_connected_walk),
KUNIT_CASE(tb_test_path_single_hop_walk),
@@ -2892,6 +3138,9 @@ static struct kunit_case tb_test_cases[] = {
KUNIT_CASE(tb_test_property_parse),
KUNIT_CASE(tb_test_property_format),
KUNIT_CASE(tb_test_property_copy),
+ KUNIT_CASE(tb_test_property_parse_zero_length),
+ KUNIT_CASE(tb_test_property_parse_rootdir_overflow),
+ KUNIT_CASE(tb_test_property_merge),
{ }
};
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index f38f7753b6e4..b7f32305f14a 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -290,6 +290,40 @@ static inline void tb_tunnel_changed(struct tb_tunnel *tunnel)
tunnel->src_port, tunnel->dst_port);
}
+static int tb_pci_port_ltssm_state_detect(struct tb_port *port)
+{
+ ktime_t timeout = ktime_add_ms(ktime_get(), 500);
+
+ do {
+ int ret;
+
+ ret = usb4_pci_port_ltssm_state(port);
+ if (ret < 0)
+ return ret;
+ if (ret == USB4_PCIE_LTSSM_DETECT)
+ return 0;
+
+ fsleep(50);
+ } while (ktime_before(ktime_get(), timeout));
+
+ return -ETIMEDOUT;
+}
+
+static int tb_pci_pre_activate(struct tb_tunnel *tunnel)
+{
+ struct tb_port *down = tunnel->src_port;
+ struct tb_port *up = tunnel->dst_port;
+ int ret;
+
+ ret = tb_switch_is_usb4(down->sw) ?
+ tb_pci_port_ltssm_state_detect(down) : 0;
+ if (ret)
+ return ret;
+
+ return tb_switch_is_usb4(up->sw) ?
+ tb_pci_port_ltssm_state_detect(up) : 0;
+}
+
static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable)
{
struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw);
@@ -505,6 +539,7 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
if (!tunnel)
return NULL;
+ tunnel->pre_activate = tb_pci_pre_activate;
tunnel->activate = tb_pci_activate;
tunnel->src_port = down;
tunnel->dst_port = up;
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
index 9e810b2ae0b5..5fd4fe070f25 100644
--- a/drivers/thunderbolt/usb4.c
+++ b/drivers/thunderbolt/usb4.c
@@ -294,7 +294,12 @@ int usb4_switch_setup(struct tb_switch *sw)
/* TBT3 supported by the CM */
val &= ~ROUTER_CS_5_CNS;
- return tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+ ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+ if (ret)
+ return ret;
+
+ return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_RR,
+ ROUTER_CS_6_RR, 500);
}
/**
@@ -304,7 +309,7 @@ int usb4_switch_setup(struct tb_switch *sw)
* Sets configuration valid bit for the router. Must be called before
* any tunnels can be set through the router and after
* usb4_switch_setup() has been called. Can be called to host and device
- * routers (does nothing for the latter).
+ * routers (does nothing for the former).
*
* Return: %0 on success, negative errno otherwise.
*/
@@ -327,7 +332,7 @@ int usb4_switch_configuration_valid(struct tb_switch *sw)
return ret;
return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR,
- ROUTER_CS_6_CR, 50);
+ ROUTER_CS_6_CR, 500);
}
/**
@@ -3145,3 +3150,27 @@ int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable)
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_adap + ADP_PCIE_CS_1, 1);
}
+
+/**
+ * usb4_pci_port_ltssm_state() - Read PCIe adapter LTSSM state
+ * @port: PCIe adapter
+ *
+ * Return:
+ * * LTSSM state of @port.
+ * * Negative errno - On failure.
+ */
+int usb4_pci_port_ltssm_state(struct tb_port *port)
+{
+ u32 val;
+ int ret;
+
+ if (!tb_port_is_pcie_down(port) && !tb_port_is_pcie_up(port))
+ return -EINVAL;
+
+ ret = tb_port_read(port, &val, TB_CFG_PORT,
+ port->cap_adap + ADP_PCIE_CS_0, 1);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(ADP_PCIE_CS_0_LTSSM_MASK, val);
+}
diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c
index c32d3516e780..890de530debc 100644
--- a/drivers/thunderbolt/usb4_port.c
+++ b/drivers/thunderbolt/usb4_port.c
@@ -138,7 +138,7 @@ bool usb4_usb3_port_match(struct device *usb4_port_dev,
return false;
/* Check if USB3 fwnode references same NHI where USB4 port resides */
- if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode))
+ if (!device_match_fwnode(nhi->dev, nhi_fwnode))
return false;
if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num))
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 1fd1cf4295a2..86b2f7474670 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -139,6 +139,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
size_t size, enum tb_cfg_pkg_type type)
{
struct tb_cfg_request *req;
+ int ret;
req = tb_cfg_request_alloc();
if (!req)
@@ -150,7 +151,11 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
req->request_size = size;
req->request_type = type;
- return tb_cfg_request(ctl, req, response_ready, req);
+ ret = tb_cfg_request(ctl, req, response_ready, req);
+ if (ret)
+ tb_cfg_request_put(req);
+
+ return ret;
}
/**
@@ -540,29 +545,19 @@ static int tb_xdp_link_state_status_request(struct tb_ctl *ctl, u64 route,
}
static int tb_xdp_link_state_status_response(struct tb *tb, struct tb_ctl *ctl,
- struct tb_xdomain *xd, u8 sequence)
+ struct tb_xdomain *xd, u8 sequence,
+ u8 slw, u8 sls, u8 tls, u8 tlw)
{
struct tb_xdp_link_state_status_response res;
- struct tb_port *port = tb_xdomain_downstream_port(xd);
- u32 val[2];
- int ret;
memset(&res, 0, sizeof(res));
tb_xdp_fill_header(&res.hdr, xd->route, sequence,
LINK_STATE_STATUS_RESPONSE, sizeof(res));
- ret = tb_port_read(port, val, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_0, ARRAY_SIZE(val));
- if (ret)
- return ret;
-
- res.slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
- LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
- res.sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >>
- LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT;
- res.tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK;
- res.tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >>
- LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
+ res.slw = slw;
+ res.sls = sls;
+ res.tls = tls;
+ res.tlw = tlw;
return __tb_xdomain_response(ctl, &res, sizeof(res),
TB_CFG_PKG_XDOMAIN_RESP);
@@ -650,6 +645,32 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler)
}
EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler);
+static int update_service_properties(struct device *dev, void *data)
+{
+ struct tb_property_dir *root = data;
+ struct tb_service *svc;
+ struct tb_property *p;
+
+ svc = tb_to_service(dev);
+ if (!svc)
+ return 0;
+
+ guard(mutex)(&svc->lock);
+
+ /*
+ * Replace the static service properties with the dynamic one.
+ * Typically this is the same but service drivers can add their
+ * own dynamic properties here too.
+ */
+ p = tb_property_find(root, svc->key, TB_PROPERTY_TYPE_DIRECTORY);
+ if (!p)
+ return 0;
+ if (svc->local_properties)
+ return tb_property_merge_dir(p->value.dir,
+ svc->local_properties, false);
+ return 0;
+}
+
static void update_property_block(struct tb_xdomain *xd)
{
mutex_lock(&xdomain_lock);
@@ -674,6 +695,9 @@ static void update_property_block(struct tb_xdomain *xd)
tb_property_add_text(dir, "deviceid", utsname()->nodename);
tb_property_add_immediate(dir, "maxhopid", xd->local_max_hopid);
+ /* Add service specific dynamic properties */
+ device_for_each_child(&xd->dev, dir, update_service_properties);
+
ret = tb_property_format_dir(dir, NULL, 0);
if (ret < 0) {
dev_warn(&xd->dev, "local property block creation failed\n");
@@ -751,7 +775,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
mutex_lock(&tb->lock);
if (tb->root_switch)
- uuid = tb->root_switch->uuid;
+ uuid = kmemdup(tb->root_switch->uuid, sizeof(*uuid), GFP_KERNEL);
else
uuid = NULL;
mutex_unlock(&tb->lock);
@@ -785,9 +809,13 @@ static void tb_xdp_handle_request(struct work_struct *work)
* the xdomain related to this connection as well in
* case there is a change in services it offers.
*/
- if (xd && device_is_registered(&xd->dev))
- queue_delayed_work(tb->wq, &xd->state_work,
- msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
+ if (xd) {
+ mutex_lock(&xd->lock);
+ if (!xd->removing && device_is_registered(&xd->dev))
+ queue_delayed_work(tb->wq, &xd->state_work,
+ msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
+ mutex_unlock(&xd->lock);
+ }
break;
case UUID_REQUEST_OLD:
@@ -800,8 +828,12 @@ static void tb_xdp_handle_request(struct work_struct *work)
* received UUID request from the remote host.
*/
if (!ret && xd && xd->state == XDOMAIN_STATE_ERROR) {
- dev_dbg(&xd->dev, "restarting handshake\n");
- start_handshake(xd);
+ mutex_lock(&xd->lock);
+ if (!xd->removing) {
+ dev_dbg(&xd->dev, "restarting handshake\n");
+ start_handshake(xd);
+ }
+ mutex_unlock(&xd->lock);
}
break;
@@ -810,8 +842,47 @@ static void tb_xdp_handle_request(struct work_struct *work)
route);
if (xd) {
- ret = tb_xdp_link_state_status_response(tb, ctl, xd,
- sequence);
+ struct tb_port *port = tb_xdomain_downstream_port(xd);
+ u8 slw, sls, tls, tlw;
+ u32 val[2];
+
+ /*
+ * Read the adapter supported and target widths
+ * and speeds.
+ */
+ ret = tb_port_read(port, val, TB_CFG_PORT,
+ port->cap_phy + LANE_ADP_CS_0,
+ ARRAY_SIZE(val));
+ if (ret)
+ break;
+
+ slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
+ LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
+ sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >>
+ LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT;
+ tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK;
+ tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >>
+ LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
+
+ /*
+ * When we have higher UUID, we are supposed to
+ * return ERROR_NOT_READY if the tlw is not yet
+ * set according to the Inter-Domain spec for
+ * USB4 v2.
+ */
+ if (xd->state == XDOMAIN_STATE_BONDING_UUID_HIGH &&
+ xd->target_link_width &&
+ xd->target_link_width != tlw) {
+ tb_dbg(tb, "%llx: target link width not yet set %#x != %#x\n",
+ route, tlw, xd->target_link_width);
+ tb_xdp_error_response(ctl, route, sequence,
+ ERROR_NOT_READY);
+ } else {
+ tb_dbg(tb, "%llx: replying with target link width set to %#x\n",
+ route, tlw);
+ ret = tb_xdp_link_state_status_response(tb, ctl,
+ xd, sequence, slw, sls, tls, tlw);
+ }
} else {
tb_xdp_error_response(ctl, route, sequence,
ERROR_NOT_READY);
@@ -829,9 +900,13 @@ static void tb_xdp_handle_request(struct work_struct *work)
ret = tb_xdp_link_state_change_response(ctl, route,
sequence, 0);
- xd->target_link_width = lsc->tlw;
- queue_delayed_work(tb->wq, &xd->state_work,
- msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
+ mutex_lock(&xd->lock);
+ if (!xd->removing) {
+ xd->target_link_width = lsc->tlw;
+ queue_delayed_work(tb->wq, &xd->state_work,
+ msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
+ }
+ mutex_unlock(&xd->lock);
} else {
tb_xdp_error_response(ctl, route, sequence,
ERROR_NOT_READY);
@@ -853,6 +928,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
}
out:
+ kfree(uuid);
kfree(xw->pkg);
kfree(xw);
@@ -909,6 +985,44 @@ void tb_unregister_service_driver(struct tb_service_driver *drv)
}
EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
+static int update_xdomain(struct device *dev, void *data)
+{
+ struct tb_xdomain *xd;
+
+ xd = tb_to_xdomain(dev);
+ if (xd) {
+ mutex_lock(&xd->lock);
+ if (!xd->removing)
+ queue_delayed_work(xd->tb->wq,
+ &xd->properties_changed_work,
+ msecs_to_jiffies(50));
+ mutex_unlock(&xd->lock);
+ }
+
+ return 0;
+}
+
+/**
+ * tb_service_properties_changed() - Notify the other host about changes
+ * @svc: Service whose properties changed
+ *
+ * Notifies the other host that service properties may have been
+ * changed. This should be called whenever @svc->local_properties is
+ * updated.
+ */
+void tb_service_properties_changed(struct tb_service *svc)
+{
+ struct tb_xdomain *xd = tb_service_parent(svc);
+
+ if (xd->is_unplugged)
+ return;
+
+ scoped_guard(mutex, &xdomain_lock)
+ xdomain_property_block_gen++;
+ update_xdomain(&xd->dev, NULL);
+}
+EXPORT_SYMBOL_GPL(tb_service_properties_changed);
+
static ssize_t key_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -1008,10 +1122,11 @@ static void tb_service_release(struct device *dev)
struct tb_service *svc = container_of(dev, struct tb_service, dev);
struct tb_xdomain *xd = tb_service_parent(svc);
- tb_service_debugfs_remove(svc);
+ tb_property_free_dir(svc->remote_properties);
ida_free(&xd->service_ids, svc->id);
kfree(svc->key);
kfree(svc);
+ tb_xdomain_put(xd);
}
const struct device_type tb_service_type = {
@@ -1022,6 +1137,24 @@ const struct device_type tb_service_type = {
};
EXPORT_SYMBOL_GPL(tb_service_type);
+static void update_service(struct tb_service *svc, struct tb_property *property)
+{
+ struct tb_property_dir *dir = property->value.dir;
+
+ guard(mutex)(&svc->lock);
+ tb_property_free_dir(svc->remote_properties);
+ svc->remote_properties = tb_property_copy_dir(dir);
+ kobject_uevent(&svc->dev.kobj, KOBJ_CHANGE);
+}
+
+static void __unregister_service(struct device *dev)
+{
+ struct tb_service *svc = tb_to_service(dev);
+
+ tb_service_debugfs_remove(svc);
+ device_unregister(&svc->dev);
+}
+
static int remove_missing_service(struct device *dev, void *data)
{
struct tb_xdomain *xd = data;
@@ -1033,7 +1166,7 @@ static int remove_missing_service(struct device *dev, void *data)
if (!tb_property_find(xd->remote_properties, svc->key,
TB_PROPERTY_TYPE_DIRECTORY))
- device_unregister(dev);
+ __unregister_service(dev);
return 0;
}
@@ -1074,6 +1207,12 @@ static int populate_service(struct tb_service *svc,
if (!svc->key)
return -ENOMEM;
+ svc->remote_properties = tb_property_copy_dir(dir);
+ if (!svc->remote_properties) {
+ kfree(svc->key);
+ return -ENOMEM;
+ }
+
return 0;
}
@@ -1098,6 +1237,7 @@ static void enumerate_services(struct tb_xdomain *xd)
/* If the service exists already we are fine */
dev = device_find_child(&xd->dev, p, find_service);
if (dev) {
+ update_service(tb_to_service(dev), p);
put_device(dev);
continue;
}
@@ -1120,12 +1260,14 @@ static void enumerate_services(struct tb_xdomain *xd)
svc->id = id;
svc->dev.bus = &tb_bus_type;
svc->dev.type = &tb_service_type;
- svc->dev.parent = &xd->dev;
+ svc->dev.parent = get_device(&xd->dev);
+ mutex_init(&svc->lock);
dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
tb_service_debugfs_init(svc);
if (device_register(&svc->dev)) {
+ tb_service_debugfs_remove(svc);
put_device(&svc->dev);
break;
}
@@ -1939,7 +2081,13 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd)
if (tb_port_get_link_generation(down) >= 4) {
down->bonded = false;
down->dual_link_port->bonded = false;
- } else if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
+ return;
+ }
+
+ if (!xd->bonding_possible)
+ return;
+
+ if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
/*
* Just return port structures back to way they were and
* update credits. No need to update userspace because
@@ -1996,6 +2144,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
INIT_DELAYED_WORK(&xd->state_work, tb_xdomain_state_work);
INIT_DELAYED_WORK(&xd->properties_changed_work,
tb_xdomain_properties_changed);
+ atomic_set(&xd->ntunnels, 0);
xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t), GFP_KERNEL);
if (!xd->local_uuid)
@@ -2058,46 +2207,64 @@ void tb_xdomain_add(struct tb_xdomain *xd)
static int unregister_service(struct device *dev, void *data)
{
- device_unregister(dev);
+ __unregister_service(dev);
return 0;
}
/**
- * tb_xdomain_remove() - Remove XDomain from the bus
+ * tb_xdomain_remove() - Remove XDomain
* @xd: XDomain to remove
*
- * This will stop all ongoing configuration work and remove the XDomain
- * along with any services from the bus. When the last reference to @xd
- * is released the object will be released as well.
+ * This will stop all ongoing configuration work. XDomain is not removed
+ * from the bus if it was added. That needs to be done separately by
+ * calling tb_xdomain_unregister().
+ *
+ * Called with @tb->lock held.
*/
void tb_xdomain_remove(struct tb_xdomain *xd)
{
tb_xdomain_debugfs_remove(xd);
- stop_handshake(xd);
-
- device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+ mutex_lock(&xd->lock);
+ xd->removing = true;
+ mutex_unlock(&xd->lock);
+ stop_handshake(xd);
tb_xdomain_link_exit(xd);
- /*
- * Undo runtime PM here explicitly because it is possible that
- * the XDomain was never added to the bus and thus device_del()
- * is not called for it (device_del() would handle this otherwise).
- */
- pm_runtime_disable(&xd->dev);
- pm_runtime_put_noidle(&xd->dev);
- pm_runtime_set_suspended(&xd->dev);
-
if (!device_is_registered(&xd->dev)) {
+ /*
+ * Undo runtime PM here explicitly because it is
+ * possible that the XDomain was never added to the bus
+ * and thus device_del() is not called for it
+ * (device_del() would handle this otherwise).
+ */
+ pm_runtime_disable(&xd->dev);
+ pm_runtime_put_noidle(&xd->dev);
+ pm_runtime_set_suspended(&xd->dev);
put_device(&xd->dev);
- } else {
- dev_info(&xd->dev, "host disconnected\n");
- device_unregister(&xd->dev);
}
}
/**
+ * tb_xdomain_unregister() - Unregister XDomain
+ * @xd: XDomain to unregister
+ *
+ * This will unregister the XDomain along with any services from the
+ * bus. When the last reference to @xd is released the object will be
+ * released as well.
+ */
+void tb_xdomain_unregister(struct tb_xdomain *xd)
+{
+ lockdep_assert_not_held(&xd->tb->lock);
+
+ device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+
+ dev_info(&xd->dev, "host disconnected\n");
+ device_unregister(&xd->dev);
+}
+
+/**
* tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
* @xd: XDomain connection
*
@@ -2273,9 +2440,15 @@ int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring)
{
- return tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
- transmit_ring, receive_path,
- receive_ring);
+ int ret;
+
+ ret = tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
+ if (ret)
+ return ret;
+ atomic_inc(&xd->ntunnels);
+ return 0;
}
EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
@@ -2298,9 +2471,15 @@ int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring)
{
- return tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
- transmit_ring, receive_path,
- receive_ring);
+ int ret;
+
+ ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
+ if (ret)
+ return ret;
+ atomic_dec(&xd->ntunnels);
+ return 0;
}
EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
@@ -2316,6 +2495,9 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
{
struct tb_port *port;
+ if (!sw)
+ return NULL;
+
tb_switch_for_each_port(sw, port) {
struct tb_xdomain *xd;
@@ -2478,19 +2660,6 @@ bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type,
return ret > 0;
}
-static int update_xdomain(struct device *dev, void *data)
-{
- struct tb_xdomain *xd;
-
- xd = tb_to_xdomain(dev);
- if (xd) {
- queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
- msecs_to_jiffies(50));
- }
-
- return 0;
-}
-
static void update_all_xdomains(void)
{
bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 6f3c86149887..eecbd631fdab 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -14,8 +14,6 @@ obj-$(CONFIG_USB_DWC2) += dwc2/
obj-$(CONFIG_USB_ISP1760) += isp1760/
obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3/
-obj-$(CONFIG_USB_CDNS3) += cdns3/
-obj-$(CONFIG_USB_CDNSP_PCI) += cdns3/
obj-$(CONFIG_USB_FOTG210) += fotg210/
diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c
index 11e3490d62ca..d610cdcef7d0 100644
--- a/drivers/usb/atm/ueagle-atm.c
+++ b/drivers/usb/atm/ueagle-atm.c
@@ -51,12 +51,6 @@
"[ueagle-atm vdbg] " format, ##args); \
} while (0)
-#define uea_enters(usb_dev) \
- uea_vdbg(usb_dev, "entering %s\n" , __func__)
-
-#define uea_leaves(usb_dev) \
- uea_vdbg(usb_dev, "leaving %s\n" , __func__)
-
#define uea_err(usb_dev, format, args...) \
dev_err(&(usb_dev)->dev , "[UEAGLE-ATM] " format , ##args)
@@ -606,7 +600,6 @@ static void uea_upload_pre_firmware(const struct firmware *fw_entry,
u32 crc = 0;
int ret, size;
- uea_enters(usb);
if (!fw_entry) {
uea_err(usb, "firmware is not available\n");
goto err;
@@ -670,7 +663,6 @@ err_fw_corrupted:
uea_err(usb, "firmware is corrupted\n");
err:
release_firmware(fw_entry);
- uea_leaves(usb);
}
/*
@@ -681,7 +673,6 @@ static int uea_load_firmware(struct usb_device *usb, unsigned int ver)
int ret;
char *fw_name = EAGLE_FIRMWARE;
- uea_enters(usb);
uea_info(usb, "pre-firmware device, uploading firmware\n");
switch (ver) {
@@ -710,7 +701,6 @@ static int uea_load_firmware(struct usb_device *usb, unsigned int ver)
else
uea_info(usb, "loading firmware %s\n", fw_name);
- uea_leaves(usb);
return ret;
}
@@ -1137,7 +1127,6 @@ static int uea_cmv_e1(struct uea_softc *sc,
struct cmv_e1 cmv;
int ret;
- uea_enters(INS_TO_USBDEV(sc));
uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Address : %c%c%c%c, "
"offset : 0x%04x, data : 0x%08x\n",
E1_FUNCTION_TYPE(function),
@@ -1164,9 +1153,8 @@ static int uea_cmv_e1(struct uea_softc *sc,
sizeof(cmv), &cmv);
if (ret < 0)
return ret;
- ret = wait_cmv_ack(sc);
- uea_leaves(INS_TO_USBDEV(sc));
- return ret;
+
+ return wait_cmv_ack(sc);
}
static int uea_cmv_e4(struct uea_softc *sc,
@@ -1175,7 +1163,6 @@ static int uea_cmv_e4(struct uea_softc *sc,
struct cmv_e4 cmv;
int ret;
- uea_enters(INS_TO_USBDEV(sc));
memset(&cmv, 0, sizeof(cmv));
uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Group : 0x%04x, "
@@ -1199,9 +1186,8 @@ static int uea_cmv_e4(struct uea_softc *sc,
sizeof(cmv), &cmv);
if (ret < 0)
return ret;
- ret = wait_cmv_ack(sc);
- uea_leaves(INS_TO_USBDEV(sc));
- return ret;
+
+ return wait_cmv_ack(sc);
}
static inline int uea_read_cmv_e1(struct uea_softc *sc,
@@ -1295,7 +1281,6 @@ static int uea_stat_e1(struct uea_softc *sc)
u32 data;
int ret;
- uea_enters(INS_TO_USBDEV(sc));
data = sc->stats.phy.state;
ret = uea_read_cmv_e1(sc, E1_SA_STAT, 0, &sc->stats.phy.state);
@@ -1438,7 +1423,6 @@ static int uea_stat_e4(struct uea_softc *sc)
u32 tmp_arr[2];
int ret;
- uea_enters(INS_TO_USBDEV(sc));
data = sc->stats.phy.state;
/* XXX only need to be done before operationnal... */
@@ -1803,7 +1787,6 @@ static int uea_start_reset(struct uea_softc *sc)
u16 zero = 0; /* ;-) */
int ret;
- uea_enters(INS_TO_USBDEV(sc));
uea_info(INS_TO_USBDEV(sc), "(re)booting started\n");
/* mask interrupt */
@@ -1871,7 +1854,6 @@ static int uea_start_reset(struct uea_softc *sc)
return ret;
sc->reset = 0;
- uea_leaves(INS_TO_USBDEV(sc));
return ret;
}
@@ -1887,7 +1869,6 @@ static int uea_kthread(void *data)
int ret = -EAGAIN;
set_freezable();
- uea_enters(INS_TO_USBDEV(sc));
while (!kthread_should_stop()) {
if (ret < 0 || sc->reset)
ret = uea_start_reset(sc);
@@ -1896,7 +1877,7 @@ static int uea_kthread(void *data)
if (ret != -EAGAIN)
uea_wait(sc, 0, msecs_to_jiffies(1000));
}
- uea_leaves(INS_TO_USBDEV(sc));
+
return ret;
}
@@ -1909,8 +1890,6 @@ static int load_XILINX_firmware(struct uea_softc *sc)
u8 value;
char *fw_name = FPGA930_FIRMWARE;
- uea_enters(INS_TO_USBDEV(sc));
-
ret = request_firmware(&fw_entry, fw_name, &sc->usb_dev->dev);
if (ret) {
uea_err(INS_TO_USBDEV(sc), "firmware %s is not available\n",
@@ -1954,7 +1933,6 @@ static int load_XILINX_firmware(struct uea_softc *sc)
err1:
release_firmware(fw_entry);
err0:
- uea_leaves(INS_TO_USBDEV(sc));
return ret;
}
@@ -1964,7 +1942,6 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr)
struct cmv_dsc_e1 *dsc = &sc->cmv_dsc.e1;
struct cmv_e1 *cmv = &intr->u.e1.s2.cmv;
- uea_enters(INS_TO_USBDEV(sc));
if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE)
goto bad1;
@@ -1988,7 +1965,6 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr)
if (cmv->bFunction == E1_MAKEFUNCTION(E1_ADSLDIRECTIVE,
E1_MODEMREADY)) {
wake_up_cmv_ack(sc);
- uea_leaves(INS_TO_USBDEV(sc));
return;
}
@@ -2002,7 +1978,6 @@ static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr)
sc->data = sc->data << 16 | sc->data >> 16;
wake_up_cmv_ack(sc);
- uea_leaves(INS_TO_USBDEV(sc));
return;
bad2:
@@ -2010,14 +1985,12 @@ bad2:
"Function : %d, Subfunction : %d\n",
E1_FUNCTION_TYPE(cmv->bFunction),
E1_FUNCTION_SUBTYPE(cmv->bFunction));
- uea_leaves(INS_TO_USBDEV(sc));
return;
bad1:
uea_err(INS_TO_USBDEV(sc), "invalid cmv received, "
"wPreamble %d, bDirection %d\n",
le16_to_cpu(cmv->wPreamble), cmv->bDirection);
- uea_leaves(INS_TO_USBDEV(sc));
}
/* The modem send us an ack. First with check if it right */
@@ -2026,7 +1999,6 @@ static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr)
struct cmv_dsc_e4 *dsc = &sc->cmv_dsc.e4;
struct cmv_e4 *cmv = &intr->u.e4.s2.cmv;
- uea_enters(INS_TO_USBDEV(sc));
uea_dbg(INS_TO_USBDEV(sc), "cmv %x %x %x %x %x %x\n",
be16_to_cpu(cmv->wGroup), be16_to_cpu(cmv->wFunction),
be16_to_cpu(cmv->wOffset), be16_to_cpu(cmv->wAddress),
@@ -2038,7 +2010,6 @@ static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr)
if (be16_to_cpu(cmv->wFunction) == E4_MAKEFUNCTION(E4_ADSLDIRECTIVE,
E4_MODEMREADY, 1)) {
wake_up_cmv_ack(sc);
- uea_leaves(INS_TO_USBDEV(sc));
return;
}
@@ -2051,7 +2022,6 @@ static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr)
sc->data = be32_to_cpu(cmv->dwData[0]);
sc->data1 = be32_to_cpu(cmv->dwData[1]);
wake_up_cmv_ack(sc);
- uea_leaves(INS_TO_USBDEV(sc));
return;
bad2:
@@ -2059,7 +2029,6 @@ bad2:
"Function : %d, Subfunction : %d\n",
E4_FUNCTION_TYPE(cmv->wFunction),
E4_FUNCTION_SUBTYPE(cmv->wFunction));
- uea_leaves(INS_TO_USBDEV(sc));
return;
}
@@ -2087,8 +2056,6 @@ static void uea_intr(struct urb *urb)
struct intr_pkt *intr = urb->transfer_buffer;
int status = urb->status;
- uea_enters(INS_TO_USBDEV(sc));
-
if (unlikely(status < 0)) {
uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n",
status);
@@ -2128,8 +2095,6 @@ static int uea_boot(struct uea_softc *sc, struct usb_interface *intf)
int ret = -ENOMEM;
int size;
- uea_enters(INS_TO_USBDEV(sc));
-
if (UEA_CHIP_VERSION(sc) == EAGLE_IV) {
size = E4_INTR_PKT_SIZE;
sc->dispatch_cmv = uea_dispatch_cmv_e4;
@@ -2186,7 +2151,6 @@ static int uea_boot(struct uea_softc *sc, struct usb_interface *intf)
goto err2;
}
- uea_leaves(INS_TO_USBDEV(sc));
return 0;
err2:
@@ -2196,7 +2160,6 @@ err1:
sc->urb_int = NULL;
kfree(intr);
err0:
- uea_leaves(INS_TO_USBDEV(sc));
return ret;
}
@@ -2206,7 +2169,7 @@ err0:
static void uea_stop(struct uea_softc *sc)
{
int ret;
- uea_enters(INS_TO_USBDEV(sc));
+
ret = kthread_stop(sc->kthread);
uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret);
@@ -2220,7 +2183,6 @@ static void uea_stop(struct uea_softc *sc)
flush_work(&sc->task);
release_firmware(sc->dsp_firm);
- uea_leaves(INS_TO_USBDEV(sc));
}
/* syfs interface */
@@ -2493,8 +2455,6 @@ static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf,
int ret, ifnum = intf->altsetting->desc.bInterfaceNumber;
unsigned int alt;
- uea_enters(usb);
-
/* interface 0 is for firmware/monitoring */
if (ifnum != UEA_INTR_IFACE_NO)
return -ENODEV;
@@ -2587,8 +2547,7 @@ static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id)
struct usb_device *usb = interface_to_usbdev(intf);
int ret;
- uea_enters(usb);
- uea_info(usb, "ADSL device founded vid (%#X) pid (%#X) Rev (%#X): %s\n",
+ uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n",
le16_to_cpu(usb->descriptor.idVendor),
le16_to_cpu(usb->descriptor.idProduct),
le16_to_cpu(usb->descriptor.bcdDevice),
@@ -2618,7 +2577,6 @@ static void uea_disconnect(struct usb_interface *intf)
{
struct usb_device *usb = interface_to_usbdev(intf);
int ifnum = intf->altsetting->desc.bInterfaceNumber;
- uea_enters(usb);
/* ADI930 has 2 interfaces and eagle 3 interfaces.
* Pre-firmware device has one interface
@@ -2629,8 +2587,6 @@ static void uea_disconnect(struct usb_interface *intf)
mutex_unlock(&uea_mutex);
uea_info(usb, "ADSL device removed\n");
}
-
- uea_leaves(usb);
}
/*
diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
index 0a514b591527..39ad23d1ada8 100644
--- a/drivers/usb/cdns3/Kconfig
+++ b/drivers/usb/cdns3/Kconfig
@@ -1,6 +1,9 @@
config USB_CDNS_SUPPORT
tristate "Cadence USB Support"
- depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA
+ depends on USB_SUPPORT && HAS_DMA
+ depends on USB || USB_GADGET
+ depends on USB if !USB_GADGET
+ depends on USB_GADGET if !USB
select USB_XHCI_PLATFORM if USB_XHCI_HCD
select USB_ROLE_SWITCH
help
@@ -8,44 +11,49 @@ config USB_CDNS_SUPPORT
dual-role controller.
It supports: dual-role switch, Host-only, and Peripheral-only.
-config USB_CDNS_HOST
- bool
-
if USB_CDNS_SUPPORT
config USB_CDNS3
- tristate "Cadence USB3 Dual-Role Controller"
+ tristate "Cadence USB dual-role controller (USBSS and USBSSP)"
depends on USB_CDNS_SUPPORT
help
- Say Y here if your system has a Cadence USB3 dual-role controller.
- It supports: dual-role switch, Host-only, and Peripheral-only.
+ Say Y or M here if your system has an on-chip Cadence USB
+ dual-role controller. This covers both USBSS (USB 3.0) and
+ USBSSP (SuperSpeed Plus) IP; the driver detects the variant at
+ runtime.
- If you choose to build this driver is a dynamically linked
- as module, the module will be called cdns3.ko.
-endif
+ The core driver (core, DRD, generic platform binding for the
+ "cdns,usb3" device tree compatible, optional host and gadget)
+ builds as one module named cdns.ko when built as a loadable
+ module.
+
+ It supports: dual-role switch, Host-only, and Peripheral-only.
if USB_CDNS3
-config USB_CDNS3_GADGET
- bool "Cadence USB3 device controller"
- depends on USB_GADGET=y || USB_GADGET=USB_CDNS3
+config USB_CDNS3_HOST
+ bool "Cadence USB host controller (xHCI)"
+ depends on USB=y || USB=USB_CDNS3
help
- Say Y here to enable device controller functionality of the
- Cadence USBSS-DEV driver.
+ Say Y here to enable host controller functionality for Cadence
+ USBSS and USBSSP dual-role controllers.
- This controller supports FF, HS and SS mode. It doesn't support
- LS and SSP mode.
+ The host controller is xHCI compliant and uses the standard
+ xHCI driver.
-config USB_CDNS3_HOST
- bool "Cadence USB3 host controller"
- depends on USB=y || USB=USB_CDNS3
- select USB_CDNS_HOST
+config USB_CDNS3_GADGET
+ bool "Cadence USB device controller (USBSS and USBSSP)"
+ depends on USB_GADGET=y || USB_GADGET=USB_CDNS3
help
- Say Y here to enable host controller functionality of the
- Cadence driver.
+ Say Y here to include Cadence USB device (gadget) support for
+ both USBSS (USB 3.0) and USBSSP (SuperSpeed Plus) IP in the
+ cdns.ko module. The implementation is selected at runtime from
+ the detected controller version.
- Host controller is compliant with XHCI so it will use
- standard XHCI driver.
+ USBSS gadget supports FF, HS and SS mode (not LS or SSP).
+ USBSSP gadget supports FF, HS, SS and SSP mode (not LS).
+
+comment "Platform glue driver support"
config USB_CDNS3_PCI_WRAP
tristate "Cadence USB3 support on PCIe-based platforms"
@@ -58,6 +66,17 @@ config USB_CDNS3_PCI_WRAP
If you choose to build this driver as module it will
be dynamically linked and module will be called cdns3-pci.ko
+config USB_CDNSP_PCI
+ tristate "Cadence USBSSP support on PCIe-based platforms"
+ depends on USB_PCI && ACPI
+ default USB_CDNS3
+ help
+ If you're using the USBSSP Core IP with a PCIe, please say
+ 'Y' or 'M' here.
+
+ If you choose to build this driver as module it will
+ be dynamically linked and module will be called cdnsp-pci.ko
+
config USB_CDNS3_TI
tristate "Cadence USB3 support on TI platforms"
depends on ARCH_K3 || COMPILE_TEST
@@ -81,6 +100,7 @@ config USB_CDNS3_IMX
config USB_CDNS3_STARFIVE
tristate "Cadence USB3 support on StarFive SoC platforms"
depends on ARCH_STARFIVE || COMPILE_TEST
+ default USB_CDNS3
help
Say 'Y' or 'M' here if you are building for StarFive SoCs
platforms that contain Cadence USB3 controller core.
@@ -89,45 +109,7 @@ config USB_CDNS3_STARFIVE
If you choose to build this driver as module it will
be dynamically linked and module will be called cdns3-starfive.ko
-endif
-
-if USB_CDNS_SUPPORT
-
-config USB_CDNSP_PCI
- tristate "Cadence CDNSP Dual-Role Controller"
- depends on USB_CDNS_SUPPORT && USB_PCI && ACPI
- help
- Say Y here if your system has a Cadence CDNSP dual-role controller.
- It supports: dual-role switch Host-only, and Peripheral-only.
-
- If you choose to build this driver is a dynamically linked
- module, the module will be called cdnsp.ko.
-endif
-
-if USB_CDNSP_PCI
-
-config USB_CDNSP_GADGET
- bool "Cadence CDNSP device controller"
- depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI
- help
- Say Y here to enable device controller functionality of the
- Cadence CDNSP-DEV driver.
-
- Cadence CDNSP Device Controller in device mode is
- very similar to XHCI controller. Therefore some algorithms
- used has been taken from host driver.
- This controller supports FF, HS, SS and SSP mode.
- It doesn't support LS.
-
-config USB_CDNSP_HOST
- bool "Cadence CDNSP host controller"
- depends on USB=y || USB=USB_CDNSP_PCI
- select USB_CDNS_HOST
- help
- Say Y here to enable host controller functionality of the
- Cadence driver.
- Host controller is compliant with XHCI so it uses
- standard XHCI driver.
+endif # USB_CDNS3
-endif
+endif # USB_CDNS_SUPPORT
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index 48dfae75b5aa..b2e4ba6a49a3 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -3,42 +3,28 @@
CFLAGS_cdns3-trace.o := -I$(src)
CFLAGS_cdnsp-trace.o := -I$(src)
-cdns-usb-common-y := core.o drd.o
-cdns3-y := cdns3-plat.o
+obj-$(CONFIG_USB_CDNS3) += cdns.o
-ifeq ($(CONFIG_USB),m)
-obj-m += cdns-usb-common.o
-obj-m += cdns3.o
-else
-obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o
-obj-$(CONFIG_USB_CDNS3) += cdns3.o
-endif
+cdns-y := core.o drd.o cdns3-plat.o
+cdns-$(CONFIG_USB_CDNS3_HOST) += host.o
-cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o
-cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o
+ifneq ($(CONFIG_USB_CDNS3_GADGET),)
+cdns-y += cdns3-gadget.o cdns3-ep0.o \
+ cdnsp-ring.o cdnsp-gadget.o \
+ cdnsp-mem.o cdnsp-ep0.o
+endif
+ifneq ($(CONFIG_TRACING),)
ifneq ($(CONFIG_USB_CDNS3_GADGET),)
-cdns3-$(CONFIG_TRACING) += cdns3-trace.o
+cdns-y += cdns3-trace.o cdnsp-trace.o
+endif
endif
+##
+# Platform-specific glue layers (PCI wrappers, SoC integration)
+##
obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o
+obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-pci.o
obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o
obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o
obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o
-
-cdnsp-udc-pci-y := cdnsp-pci.o
-
-ifdef CONFIG_USB_CDNSP_PCI
-ifeq ($(CONFIG_USB),m)
-obj-m += cdnsp-udc-pci.o
-else
-obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o
-endif
-endif
-
-cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \
- cdnsp-mem.o cdnsp-ep0.o
-
-ifneq ($(CONFIG_USB_CDNSP_GADGET),)
-cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o
-endif
diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c
index 1db8db1b7cc3..42311c1bfada 100644
--- a/drivers/usb/cdns3/cdns3-gadget.c
+++ b/drivers/usb/cdns3/cdns3-gadget.c
@@ -3522,3 +3522,4 @@ int cdns3_gadget_init(struct cdns *cdns)
return 0;
}
+EXPORT_SYMBOL_GPL(cdns3_gadget_init);
diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c
index 94e9706a1806..bb5405460035 100644
--- a/drivers/usb/cdns3/cdns3-plat.c
+++ b/drivers/usb/cdns3/cdns3-plat.c
@@ -21,6 +21,7 @@
#include "core.h"
#include "gadget-export.h"
+#include "host-export.h"
#include "drd.h"
static int set_phy_power_on(struct cdns *cdns)
@@ -44,6 +45,19 @@ static void set_phy_power_off(struct cdns *cdns)
phy_power_off(cdns->usb2_phy);
}
+static int cdns3_plat_gadget_init(struct cdns *cdns)
+{
+ if (cdns->version < CDNSP_CONTROLLER_V2)
+ return cdns3_gadget_init(cdns);
+ else
+ return cdnsp_gadget_init(cdns);
+}
+
+static int cdns3_plat_host_init(struct cdns *cdns)
+{
+ return cdns_host_init(cdns);
+}
+
/**
* cdns3_plat_probe - probe for cdns3 core device
* @pdev: Pointer to cdns3 core platform device
@@ -64,6 +78,14 @@ static int cdns3_plat_probe(struct platform_device *pdev)
cdns->dev = dev;
cdns->pdata = dev_get_platdata(dev);
+ if (cdns->pdata && cdns->pdata->override_apb_timeout)
+ cdns->override_apb_timeout = cdns->pdata->override_apb_timeout;
+
+ if (device_is_compatible(dev, "cdns,cdnsp")) {
+ cdns->no_drd = true;
+ cdns->version = CDNSP_CONTROLLER_V2;
+ dev_dbg(dev, "No DRD support\n");
+ }
platform_set_drvdata(pdev, cdns);
@@ -97,21 +119,22 @@ static int cdns3_plat_probe(struct platform_device *pdev)
cdns->dev_regs = regs;
- cdns->otg_irq = platform_get_irq_byname(pdev, "otg");
- if (cdns->otg_irq < 0)
- return dev_err_probe(dev, cdns->otg_irq,
- "Failed to get otg IRQ\n");
-
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg");
- if (!res) {
- dev_err(dev, "couldn't get otg resource\n");
- return -ENXIO;
+ if (!cdns->no_drd) {
+ cdns->otg_irq = platform_get_irq_byname(pdev, "otg");
+ if (cdns->otg_irq < 0)
+ return dev_err_probe(dev, cdns->otg_irq,
+ "Failed to get otg IRQ\n");
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg");
+ if (!res) {
+ dev_err(dev, "couldn't get otg resource\n");
+ return -ENXIO;
+ }
+ cdns->otg_res = *res;
}
cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable");
- cdns->otg_res = *res;
-
cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
if (cdns->wakeup_irq == -EPROBE_DEFER)
return cdns->wakeup_irq;
@@ -143,12 +166,16 @@ static int cdns3_plat_probe(struct platform_device *pdev)
if (ret)
goto err_phy_power_on;
- cdns->gadget_init = cdns3_gadget_init;
-
ret = cdns_init(cdns);
if (ret)
goto err_cdns_init;
+ cdns->gadget_init = cdns3_plat_gadget_init;
+ cdns->host_init = cdns3_plat_host_init;
+ ret = cdns_core_init_role(cdns);
+ if (ret)
+ goto err_cdns_init_role;
+
device_set_wakeup_capable(dev, true);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
@@ -166,6 +193,9 @@ static int cdns3_plat_probe(struct platform_device *pdev)
return 0;
+err_cdns_init_role:
+ if (cdns->role_sw)
+ usb_role_switch_unregister(cdns->role_sw);
err_cdns_init:
set_phy_power_off(cdns);
err_phy_power_on:
@@ -321,6 +351,7 @@ static const struct dev_pm_ops cdns3_pm_ops = {
#ifdef CONFIG_OF
static const struct of_device_id of_cdns3_match[] = {
{ .compatible = "cdns,usb3" },
+ { .compatible = "cdns,cdnsp" },
{ },
};
MODULE_DEVICE_TABLE(of, of_cdns3_match);
@@ -339,6 +370,3 @@ static struct platform_driver cdns3_driver = {
module_platform_driver(cdns3_driver);
MODULE_ALIAS("platform:cdns3");
-MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c
index 6b3815f8a6e5..a5275c2fb43b 100644
--- a/drivers/usb/cdns3/cdnsp-gadget.c
+++ b/drivers/usb/cdns3/cdnsp-gadget.c
@@ -124,20 +124,28 @@ void cdnsp_set_link_state(struct cdnsp_device *pdev,
}
static void cdnsp_disable_port(struct cdnsp_device *pdev,
- __le32 __iomem *port_regs)
+ struct cdnsp_port *port)
{
- u32 temp = cdnsp_port_state_to_neutral(readl(port_regs));
+ u32 temp;
+
+ if (!port->exist)
+ return;
- writel(temp | PORT_PED, port_regs);
+ temp = cdnsp_port_state_to_neutral(readl(&port->regs->portsc));
+ writel(temp | PORT_PED, &port->regs->portsc);
}
static void cdnsp_clear_port_change_bit(struct cdnsp_device *pdev,
- __le32 __iomem *port_regs)
+ struct cdnsp_port *port)
{
- u32 portsc = readl(port_regs);
+ u32 portsc;
+
+ if (!port->exist)
+ return;
+ portsc = readl(&port->regs->portsc);
writel(cdnsp_port_state_to_neutral(portsc) |
- (portsc & PORT_CHANGE_BITS), port_regs);
+ (portsc & PORT_CHANGE_BITS), &port->regs->portsc);
}
static void cdnsp_set_apb_timeout_value(struct cdnsp_device *pdev)
@@ -944,7 +952,7 @@ void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *pdev,
struct usb_request *req,
int enable)
{
- if (pdev->active_port != &pdev->usb2_port || !pdev->gadget.lpm_capable)
+ if (pdev->active_port == &pdev->usb3_port || !pdev->gadget.lpm_capable)
return;
trace_cdnsp_lpm(enable);
@@ -1310,20 +1318,26 @@ static int cdnsp_run(struct cdnsp_device *pdev,
break;
}
- if (speed >= USB_SPEED_SUPER) {
+ if (pdev->usb3_port.exist && speed >= USB_SPEED_SUPER) {
writel(temp, &pdev->port3x_regs->mode_addr);
cdnsp_set_link_state(pdev, &pdev->usb3_port.regs->portsc,
XDEV_RXDETECT);
} else {
- cdnsp_disable_port(pdev, &pdev->usb3_port.regs->portsc);
+ cdnsp_disable_port(pdev, &pdev->usb3_port);
}
- cdnsp_set_link_state(pdev, &pdev->usb2_port.regs->portsc,
- XDEV_RXDETECT);
+ if (pdev->usb2_port.exist) {
+ cdnsp_set_link_state(pdev, &pdev->usb2_port.regs->portsc,
+ XDEV_RXDETECT);
+ writel(PORT_REG6_L1_L0_HW_EN | fs_speed, &pdev->port20_regs->port_reg6);
+ }
+
+ if (pdev->eusb_port.exist)
+ cdnsp_set_link_state(pdev, &pdev->eusb_port.regs->portsc,
+ XDEV_RXDETECT);
cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
- writel(PORT_REG6_L1_L0_HW_EN | fs_speed, &pdev->port20_regs->port_reg6);
ret = cdnsp_start(pdev);
if (ret) {
@@ -1469,8 +1483,10 @@ static void cdnsp_stop(struct cdnsp_device *pdev)
cdnsp_ep_dequeue(&pdev->eps[0], req);
}
- cdnsp_disable_port(pdev, &pdev->usb2_port.regs->portsc);
- cdnsp_disable_port(pdev, &pdev->usb3_port.regs->portsc);
+ cdnsp_disable_port(pdev, &pdev->usb2_port);
+ cdnsp_disable_port(pdev, &pdev->usb3_port);
+ cdnsp_disable_port(pdev, &pdev->eusb_port);
+
cdnsp_disable_slot(pdev);
cdnsp_halt(pdev);
@@ -1479,8 +1495,9 @@ static void cdnsp_stop(struct cdnsp_device *pdev)
temp = readl(&pdev->ir_set->irq_pending);
writel(IMAN_IE_CLEAR(temp), &pdev->ir_set->irq_pending);
- cdnsp_clear_port_change_bit(pdev, &pdev->usb2_port.regs->portsc);
- cdnsp_clear_port_change_bit(pdev, &pdev->usb3_port.regs->portsc);
+ cdnsp_clear_port_change_bit(pdev, &pdev->usb2_port);
+ cdnsp_clear_port_change_bit(pdev, &pdev->eusb_port);
+ cdnsp_clear_port_change_bit(pdev, &pdev->usb3_port);
/* Clear interrupt line */
temp = readl(&pdev->ir_set->irq_pending);
@@ -2075,3 +2092,4 @@ int cdnsp_gadget_init(struct cdns *cdns)
return 0;
}
+EXPORT_SYMBOL_GPL(cdnsp_gadget_init);
diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h
index a91cca509db0..c44bca348a41 100644
--- a/drivers/usb/cdns3/cdnsp-gadget.h
+++ b/drivers/usb/cdns3/cdnsp-gadget.h
@@ -1474,6 +1474,7 @@ struct cdnsp_device {
unsigned int link_state;
struct cdnsp_port usb2_port;
+ struct cdnsp_port eusb_port;
struct cdnsp_port usb3_port;
struct cdnsp_port *active_port;
u16 test_mode;
diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c
index a2a1b21f2ef8..5d8cdc91927d 100644
--- a/drivers/usb/cdns3/cdnsp-mem.c
+++ b/drivers/usb/cdns3/cdnsp-mem.c
@@ -1088,11 +1088,9 @@ void cdnsp_mem_cleanup(struct cdnsp_device *pdev)
pdev->dcbaa, pdev->dcbaa->dma);
pdev->dcbaa = NULL;
-
- pdev->usb2_port.exist = 0;
- pdev->usb3_port.exist = 0;
- pdev->usb2_port.port_num = 0;
- pdev->usb3_port.port_num = 0;
+ memset(&pdev->usb2_port, 0, sizeof(struct cdnsp_port));
+ memset(&pdev->eusb_port, 0, sizeof(struct cdnsp_port));
+ memset(&pdev->usb3_port, 0, sizeof(struct cdnsp_port));
pdev->active_port = NULL;
}
@@ -1133,6 +1131,18 @@ static void cdnsp_add_in_port(struct cdnsp_device *pdev,
port_offset = CDNSP_EXT_PORT_OFF(temp);
port_count = CDNSP_EXT_PORT_COUNT(temp);
+ if (port == &pdev->eusb_port) {
+ /*
+ * If controller has usb2 + eusb port then eusb is as
+ * second port
+ */
+ if (port_count == 2)
+ port_offset++;
+
+ if (port_count == 1 && pdev->usb2_port.exist)
+ return;
+ }
+
trace_cdnsp_port_info(addr, port_offset, port_count, port->maj_rev);
port->port_num = port_offset;
@@ -1152,13 +1162,10 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev)
base = &pdev->cap_regs->hc_capbase;
offset = cdnsp_find_next_ext_cap(base, 0,
EXT_CAP_CFG_DEV_20PORT_CAP_ID);
- pdev->port20_regs = base + offset;
-
- offset = cdnsp_find_next_ext_cap(base, 0, D_XEC_CFG_3XPORT_CAP);
- pdev->port3x_regs = base + offset;
+ if (offset)
+ pdev->port20_regs = base + offset;
offset = 0;
- base = &pdev->cap_regs->hc_capbase;
/* Driver expects max 2 extended protocol capability. */
for (i = 0; i < 2; i++) {
@@ -1173,26 +1180,46 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev)
cdnsp_add_in_port(pdev, &pdev->usb3_port,
base + offset);
- if (CDNSP_EXT_PORT_MAJOR(temp) == 0x02 &&
- !pdev->usb2_port.port_num)
- cdnsp_add_in_port(pdev, &pdev->usb2_port,
- base + offset);
+ if (CDNSP_EXT_PORT_MAJOR(temp) == 0x02) {
+ if (!pdev->usb2_port.port_num && pdev->port20_regs)
+ cdnsp_add_in_port(pdev, &pdev->usb2_port,
+ base + offset);
+
+ if (!pdev->eusb_port.port_num)
+ cdnsp_add_in_port(pdev, &pdev->eusb_port,
+ base + offset);
+ }
}
- if (!pdev->usb2_port.exist || !pdev->usb3_port.exist) {
- dev_err(pdev->dev, "Error: Only one port detected\n");
+ if (!pdev->usb2_port.exist && !pdev->eusb_port.exist &&
+ !pdev->usb3_port.exist) {
+ dev_err(pdev->dev, "Error: No port detected\n");
return -ENODEV;
}
- trace_cdnsp_init("Found USB 2.0 ports and USB 3.0 ports.");
+ if (pdev->usb2_port.exist) {
+ pdev->usb2_port.regs = (struct cdnsp_port_regs __iomem *)
+ (&pdev->op_regs->port_reg_base + NUM_PORT_REGS *
+ (pdev->usb2_port.port_num - 1));
+ trace_cdnsp_init("Found USB 2.0 port.");
+ }
- pdev->usb2_port.regs = (struct cdnsp_port_regs __iomem *)
- (&pdev->op_regs->port_reg_base + NUM_PORT_REGS *
- (pdev->usb2_port.port_num - 1));
+ if (pdev->eusb_port.exist) {
+ pdev->eusb_port.regs = (struct cdnsp_port_regs __iomem *)
+ (&pdev->op_regs->port_reg_base + NUM_PORT_REGS *
+ (pdev->eusb_port.port_num - 1));
+ trace_cdnsp_init("Found eUSB 2.0 port.");
+ }
+
+ if (pdev->usb3_port.exist) {
+ offset = cdnsp_find_next_ext_cap(base, 0, D_XEC_CFG_3XPORT_CAP);
+ pdev->port3x_regs = base + offset;
- pdev->usb3_port.regs = (struct cdnsp_port_regs __iomem *)
- (&pdev->op_regs->port_reg_base + NUM_PORT_REGS *
- (pdev->usb3_port.port_num - 1));
+ pdev->usb3_port.regs = (struct cdnsp_port_regs __iomem *)
+ (&pdev->op_regs->port_reg_base + NUM_PORT_REGS *
+ (pdev->usb3_port.port_num - 1));
+ trace_cdnsp_init("Found USB 3.x port.");
+ }
return 0;
}
diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c
index 566d94e49102..c38a3dc7111a 100644
--- a/drivers/usb/cdns3/cdnsp-pci.c
+++ b/drivers/usb/cdns3/cdnsp-pci.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Cadence PCI Glue driver.
+ * Cadence USBSSP PCI Glue driver.
*
* Copyright (C) 2019 Cadence.
*
@@ -16,26 +16,45 @@
#include <linux/pci.h>
#include "core.h"
-#include "gadget-export.h"
+struct cdnsp_wrap {
+ struct platform_device *plat_dev;
+ struct property_entry prop[3];
+ struct resource dev_res[6];
+ int devfn;
+};
+
+#define RES_IRQ_HOST_ID 0
+#define RES_IRQ_PERIPHERAL_ID 1
+#define RES_IRQ_OTG_ID 2
+#define RES_HOST_ID 3
+#define RES_DEV_ID 4
+#define RES_DRD_ID 5
+/* DRD PCI configuration - 64-bit addressing */
+/* First PCI function */
#define PCI_BAR_HOST 0
-#define PCI_BAR_OTG 0
#define PCI_BAR_DEV 2
+/* Second PCI function */
+#define PCI_BAR_OTG 0
+/* Device only PCI configuration - 32-bit addressing */
+/* First PCI function */
+#define PCI_BAR_ONLY_DEV 1
#define PCI_DEV_FN_HOST_DEVICE 0
#define PCI_DEV_FN_OTG 1
#define PCI_DRIVER_NAME "cdns-pci-usbssp"
-#define PLAT_DRIVER_NAME "cdns-usbssp"
+#define PLAT_DRIVER_NAME "cdns-usb3"
-#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20
+#define PCI_DEVICE_ID_CDNS_UDC_USBSSP 0x0400
+#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20
static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
{
/*
* Gets the second function.
- * Platform has two function. The fist keeps resources for
- * Host/Device while the secon keeps resources for DRD/OTG.
+ * Platform has two function. The first keeps resources for
+ * Host/Device while the second keeps resources for DRD/OTG.
*/
if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP)
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL);
@@ -48,11 +67,13 @@ static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
static int cdnsp_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
- struct device *dev = &pdev->dev;
- struct pci_dev *func;
+ struct platform_device_info plat_info;
+ static struct cdns3_platform_data pdata;
+ struct cdnsp_wrap *wrap;
struct resource *res;
- struct cdns *cdnsp;
- int ret;
+ struct pci_dev *func;
+ bool no_drd = false;
+ int ret = 0;
/*
* For GADGET/HOST PCI (devfn) function number is 0,
@@ -62,11 +83,14 @@ static int cdnsp_pci_probe(struct pci_dev *pdev,
pdev->devfn != PCI_DEV_FN_OTG))
return -EINVAL;
+ if (pdev->device == PCI_DEVICE_ID_CDNS_UDC_USBSSP)
+ no_drd = true;
+
func = cdnsp_get_second_fun(pdev);
- if (!func)
+ if (!func && !no_drd)
return -EINVAL;
- if (func->class == PCI_CLASS_SERIAL_USB_XHCI ||
+ if ((func && func->class == PCI_CLASS_SERIAL_USB_XHCI) ||
pdev->class == PCI_CLASS_SERIAL_USB_XHCI) {
ret = -EINVAL;
goto put_pci;
@@ -79,147 +103,125 @@ static int cdnsp_pci_probe(struct pci_dev *pdev,
}
pci_set_master(pdev);
- if (pci_is_enabled(func)) {
- cdnsp = pci_get_drvdata(func);
+
+ if (func && pci_is_enabled(func)) {
+ wrap = pci_get_drvdata(func);
} else {
- cdnsp = kzalloc_obj(*cdnsp);
- if (!cdnsp) {
+ wrap = kzalloc_obj(*wrap);
+ if (!wrap) {
ret = -ENOMEM;
goto put_pci;
}
}
- /* For GADGET device function number is 0. */
- if (pdev->devfn == 0) {
- resource_size_t rsrc_start, rsrc_len;
-
- /* Function 0: host(BAR_0) + device(BAR_1).*/
- dev_dbg(dev, "Initialize resources\n");
- rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV);
- rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV);
- res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev");
- if (!res) {
- dev_dbg(dev, "controller already in use\n");
- ret = -EBUSY;
- goto free_cdnsp;
- }
-
- cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len);
- if (!cdnsp->dev_regs) {
- dev_dbg(dev, "error mapping memory\n");
- ret = -EFAULT;
- goto free_cdnsp;
- }
-
- cdnsp->dev_irq = pdev->irq;
- dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n",
- &rsrc_start);
-
- res = &cdnsp->xhci_res[0];
- res->start = pci_resource_start(pdev, PCI_BAR_HOST);
- res->end = pci_resource_end(pdev, PCI_BAR_HOST);
- res->name = "xhci";
- res->flags = IORESOURCE_MEM;
- dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n",
- &res->start);
-
- /* Interrupt for XHCI, */
- res = &cdnsp->xhci_res[1];
- res->start = pdev->irq;
- res->name = "host";
- res->flags = IORESOURCE_IRQ;
+ res = wrap->dev_res;
+
+ if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) {
+ int bar_dev = no_drd ? PCI_BAR_ONLY_DEV : PCI_BAR_DEV;
+
+ /* Function 0: host(BAR_0) + device(BAR_2). */
+ dev_dbg(&pdev->dev, "Initialize Device resources\n");
+ res[RES_DEV_ID].start = pci_resource_start(pdev, bar_dev);
+ res[RES_DEV_ID].end = pci_resource_end(pdev, bar_dev);
+ res[RES_DEV_ID].name = "dev";
+ res[RES_DEV_ID].flags = IORESOURCE_MEM;
+ dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n",
+ &res[RES_DEV_ID].start);
+
+ res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST);
+ res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST);
+ res[RES_HOST_ID].name = "xhci";
+ res[RES_HOST_ID].flags = IORESOURCE_MEM;
+ dev_dbg(&pdev->dev, "USBSSP-XHCI physical base addr: %pa\n",
+ &res[RES_HOST_ID].start);
+
+ /* Interrupt for XHCI */
+ wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq;
+ wrap->dev_res[RES_IRQ_HOST_ID].name = "host";
+ wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ;
+
+ /* Interrupt for device. It's the same as for HOST. */
+ wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq;
+ wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral";
+ wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ;
} else {
- res = &cdnsp->otg_res;
- res->start = pci_resource_start(pdev, PCI_BAR_OTG);
- res->end = pci_resource_end(pdev, PCI_BAR_OTG);
- res->name = "otg";
- res->flags = IORESOURCE_MEM;
- dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n",
- &res->start);
+ res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG);
+ res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG);
+ res[RES_DRD_ID].name = "otg";
+ res[RES_DRD_ID].flags = IORESOURCE_MEM;
+ dev_dbg(&pdev->dev, "CDNSP-DRD physical base addr: %pa\n",
+ &res[RES_DRD_ID].start);
/* Interrupt for OTG/DRD. */
- cdnsp->otg_irq = pdev->irq;
+ wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq;
+ wrap->dev_res[RES_IRQ_OTG_ID].name = "otg";
+ wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ;
}
- /*
- * Cadence PCI based platform require some longer timeout for APB
- * to fixes domain clock synchronization issue after resuming
- * controller from L1 state.
- */
- cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
- pci_set_drvdata(pdev, cdnsp);
-
- if (pci_is_enabled(func)) {
- cdnsp->dev = dev;
- cdnsp->gadget_init = cdnsp_gadget_init;
+ if (no_drd || pci_is_enabled(func)) {
+ u8 idx = 0;
- ret = cdns_init(cdnsp);
- if (ret)
- goto free_cdnsp;
- }
-
- device_wakeup_enable(&pdev->dev);
- if (pci_dev_run_wake(pdev))
- pm_runtime_put_noidle(&pdev->dev);
+ /* set up platform device info */
+ pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
- return 0;
+ if (no_drd) {
+ wrap->prop[idx++] = PROPERTY_ENTRY_STRING("compatible",
+ "cdns,cdnsp");
+ wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "peripheral");
+ } else {
+ wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "otg");
+ wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("usb-role-switch");
+ }
-free_cdnsp:
- if (!pci_is_enabled(func))
- kfree(cdnsp);
+ memset(&plat_info, 0, sizeof(plat_info));
+ plat_info.parent = &pdev->dev;
+ plat_info.fwnode = pdev->dev.fwnode;
+ plat_info.name = PLAT_DRIVER_NAME;
+ plat_info.id = pdev->devfn;
+ plat_info.res = wrap->dev_res;
+ plat_info.num_res = ARRAY_SIZE(wrap->dev_res);
+ plat_info.dma_mask = pdev->dma_mask;
+ plat_info.data = &pdata;
+ plat_info.size_data = sizeof(pdata);
+ plat_info.properties = wrap->prop;
+ wrap->devfn = pdev->devfn;
+ /* register platform device */
+ wrap->plat_dev = platform_device_register_full(&plat_info);
+ if (IS_ERR(wrap->plat_dev)) {
+ ret = PTR_ERR(wrap->plat_dev);
+ kfree(wrap);
+ goto put_pci;
+ }
+ }
+ pci_set_drvdata(pdev, wrap);
put_pci:
pci_dev_put(func);
-
return ret;
}
static void cdnsp_pci_remove(struct pci_dev *pdev)
{
- struct cdns *cdnsp;
+ struct cdnsp_wrap *wrap;
struct pci_dev *func;
func = cdnsp_get_second_fun(pdev);
- cdnsp = (struct cdns *)pci_get_drvdata(pdev);
+ wrap = pci_get_drvdata(pdev);
- if (pci_dev_run_wake(pdev))
- pm_runtime_get_noresume(&pdev->dev);
+ if (wrap->devfn == pdev->devfn)
+ platform_device_unregister(wrap->plat_dev);
- if (pci_is_enabled(func)) {
- cdns_remove(cdnsp);
- } else {
- kfree(cdnsp);
- }
+ if (!func || !pci_is_enabled(func))
+ kfree(wrap);
pci_dev_put(func);
}
-static int __maybe_unused cdnsp_pci_suspend(struct device *dev)
-{
- struct cdns *cdns = dev_get_drvdata(dev);
-
- return cdns_suspend(cdns);
-}
-
-static int __maybe_unused cdnsp_pci_resume(struct device *dev)
-{
- struct cdns *cdns = dev_get_drvdata(dev);
- unsigned long flags;
- int ret;
-
- spin_lock_irqsave(&cdns->lock, flags);
- ret = cdns_resume(cdns);
- spin_unlock_irqrestore(&cdns->lock, flags);
- cdns_set_active(cdns, 1);
-
- return ret;
-}
-
-static const struct dev_pm_ops cdnsp_pci_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume)
-};
-
static const struct pci_device_id cdnsp_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP),
+ .class = PCI_CLASS_SERIAL_USB_DEVICE },
+ { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP),
+ .class = PCI_CLASS_SERIAL_USB_CDNS },
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
.class = PCI_CLASS_SERIAL_USB_DEVICE },
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
@@ -230,13 +232,10 @@ static const struct pci_device_id cdnsp_pci_ids[] = {
};
static struct pci_driver cdnsp_pci_driver = {
- .name = "cdnsp-pci",
+ .name = PCI_DRIVER_NAME,
.id_table = cdnsp_pci_ids,
.probe = cdnsp_pci_probe,
.remove = cdnsp_pci_remove,
- .driver = {
- .pm = &cdnsp_pci_pm_ops,
- }
};
module_pci_driver(cdnsp_pci_driver);
@@ -245,4 +244,4 @@ MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids);
MODULE_ALIAS("pci:cdnsp");
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("Cadence CDNSP PCI driver");
+MODULE_DESCRIPTION("Cadence CDNSP PCI wrapper");
diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c
index 0758f171f73e..715658c981ff 100644
--- a/drivers/usb/cdns3/cdnsp-ring.c
+++ b/drivers/usb/cdns3/cdnsp-ring.c
@@ -259,7 +259,7 @@ static bool cdnsp_room_on_ring(struct cdnsp_device *pdev,
*/
static void cdnsp_force_l0_go(struct cdnsp_device *pdev)
{
- if (pdev->active_port == &pdev->usb2_port && pdev->gadget.lpm_capable)
+ if (pdev->active_port != &pdev->usb3_port && pdev->gadget.lpm_capable)
cdnsp_set_link_state(pdev, &pdev->active_port->regs->portsc, XDEV_U0);
}
@@ -763,6 +763,8 @@ static int cdnsp_update_port_id(struct cdnsp_device *pdev, u32 port_id)
if (port_id == pdev->usb2_port.port_num) {
port = &pdev->usb2_port;
+ } else if (port_id == pdev->eusb_port.port_num) {
+ port = &pdev->eusb_port;
} else if (port_id == pdev->usb3_port.port_num) {
port = &pdev->usb3_port;
} else {
@@ -779,7 +781,8 @@ static int cdnsp_update_port_id(struct cdnsp_device *pdev, u32 port_id)
cdnsp_enable_slot(pdev);
}
- if (port_id == pdev->usb2_port.port_num)
+ if ((pdev->usb2_port.exist && port_id == pdev->usb2_port.port_num) ||
+ (pdev->eusb_port.exist && port_id == pdev->eusb_port.port_num))
cdnsp_set_usb2_hardware_lpm(pdev, NULL, 1);
else
writel(PORT_U1_TIMEOUT(1) | PORT_U2_TIMEOUT(1),
@@ -808,7 +811,7 @@ static void cdnsp_handle_port_status(struct cdnsp_device *pdev,
port_regs = pdev->active_port->regs;
- if (port_id == pdev->usb2_port.port_num)
+ if (port_id == pdev->usb2_port.port_num || port_id == pdev->eusb_port.port_num)
port2 = true;
new_event:
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index f0e32227c0b7..504bdf13ea80 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -21,7 +21,6 @@
#include <linux/pm_runtime.h>
#include "core.h"
-#include "host-export.h"
#include "drd.h"
static int cdns_idle_init(struct cdns *cdns);
@@ -71,7 +70,8 @@ static void cdns_role_stop(struct cdns *cdns)
static void cdns_exit_roles(struct cdns *cdns)
{
cdns_role_stop(cdns);
- cdns_drd_exit(cdns);
+ if (!cdns->no_drd)
+ cdns_drd_exit(cdns);
}
/**
@@ -80,7 +80,7 @@ static void cdns_exit_roles(struct cdns *cdns)
*
* Returns 0 on success otherwise negative errno
*/
-static int cdns_core_init_role(struct cdns *cdns)
+int cdns_core_init_role(struct cdns *cdns)
{
struct device *dev = cdns->dev;
enum usb_dr_mode best_dr_mode;
@@ -96,23 +96,13 @@ static int cdns_core_init_role(struct cdns *cdns)
* can be restricted later depending on strap pin configuration.
*/
if (dr_mode == USB_DR_MODE_UNKNOWN) {
- if (cdns->version == CDNSP_CONTROLLER_V2) {
- if (IS_ENABLED(CONFIG_USB_CDNSP_HOST) &&
- IS_ENABLED(CONFIG_USB_CDNSP_GADGET))
- dr_mode = USB_DR_MODE_OTG;
- else if (IS_ENABLED(CONFIG_USB_CDNSP_HOST))
- dr_mode = USB_DR_MODE_HOST;
- else if (IS_ENABLED(CONFIG_USB_CDNSP_GADGET))
- dr_mode = USB_DR_MODE_PERIPHERAL;
- } else {
- if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
- IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
- dr_mode = USB_DR_MODE_OTG;
- else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
- dr_mode = USB_DR_MODE_HOST;
- else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
- dr_mode = USB_DR_MODE_PERIPHERAL;
- }
+ if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
+ IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
+ dr_mode = USB_DR_MODE_OTG;
+ else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
+ dr_mode = USB_DR_MODE_HOST;
+ else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
+ dr_mode = USB_DR_MODE_PERIPHERAL;
}
/*
@@ -137,11 +127,8 @@ static int cdns_core_init_role(struct cdns *cdns)
dr_mode = best_dr_mode;
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
- if ((cdns->version == CDNSP_CONTROLLER_V2 &&
- IS_ENABLED(CONFIG_USB_CDNSP_HOST)) ||
- (cdns->version < CDNSP_CONTROLLER_V2 &&
- IS_ENABLED(CONFIG_USB_CDNS3_HOST)))
- ret = cdns_host_init(cdns);
+ if (cdns->host_init)
+ ret = cdns->host_init(cdns);
else
ret = -ENXIO;
@@ -197,11 +184,14 @@ static int cdns_core_init_role(struct cdns *cdns)
goto err;
}
+ dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
+
return 0;
err:
cdns_exit_roles(cdns);
return ret;
}
+EXPORT_SYMBOL_GPL(cdns_core_init_role);
/**
* cdns_hw_role_state_machine - role switch state machine based on hw events.
@@ -469,14 +459,8 @@ int cdns_init(struct cdns *cdns)
if (ret)
goto init_failed;
- ret = cdns_core_init_role(cdns);
- if (ret)
- goto init_failed;
-
spin_lock_init(&cdns->lock);
- dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
-
return 0;
init_failed:
cdns_drd_exit(cdns);
@@ -576,5 +560,5 @@ EXPORT_SYMBOL_GPL(cdns_set_active);
MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
-MODULE_DESCRIPTION("Cadence USBSS and USBSSP DRD Driver");
+MODULE_DESCRIPTION("Cadence USBSS/USBSSP DRD driver (core, DRD, platform, optional host/gadget)");
MODULE_LICENSE("GPL");
diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
index 801be9e61340..8c492fda924c 100644
--- a/drivers/usb/cdns3/core.h
+++ b/drivers/usb/cdns3/core.h
@@ -45,6 +45,7 @@ struct cdns3_platform_data {
unsigned long quirks;
#define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0)
#define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1)
+ u32 override_apb_timeout; /* 0 = use default (e.g. for PCI) */
};
/**
@@ -82,6 +83,10 @@ struct cdns3_platform_data {
* @override_apb_timeout: hold value of APB timeout. For value 0 the default
* value in CHICKEN_BITS_3 will be preserved.
* @gadget_init: pointer to gadget initialization function
+ * @host_init: pointer to host initialization function
+ * @no_drd: DRD register block is inaccessible. The controller is hardwired to
+ * single role (host or device) or the logic for role switching is
+ * missing.
*/
struct cdns {
struct device *dev;
@@ -120,13 +125,15 @@ struct cdns {
spinlock_t lock;
struct xhci_plat_priv *xhci_plat_data;
u32 override_apb_timeout;
-
int (*gadget_init)(struct cdns *cdns);
+ int (*host_init)(struct cdns *cdns);
+ bool no_drd;
};
int cdns_hw_role_switch(struct cdns *cdns);
int cdns_init(struct cdns *cdns);
int cdns_remove(struct cdns *cdns);
+int cdns_core_init_role(struct cdns *cdns);
#ifdef CONFIG_PM_SLEEP
int cdns_resume(struct cdns *cdns);
diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
index 84fb38a5723a..d2bb682e4552 100644
--- a/drivers/usb/cdns3/drd.c
+++ b/drivers/usb/cdns3/drd.c
@@ -87,6 +87,9 @@ int cdns_get_id(struct cdns *cdns)
{
int id;
+ if (cdns->no_drd)
+ return 0;
+
id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
dev_dbg(cdns->dev, "OTG ID: %d", id);
@@ -107,7 +110,7 @@ void cdns_clear_vbus(struct cdns *cdns)
{
u32 reg;
- if (cdns->version != CDNSP_CONTROLLER_V2)
+ if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd)
return;
reg = readl(&cdns->otg_cdnsp_regs->override);
@@ -120,7 +123,7 @@ void cdns_set_vbus(struct cdns *cdns)
{
u32 reg;
- if (cdns->version != CDNSP_CONTROLLER_V2)
+ if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd)
return;
reg = readl(&cdns->otg_cdnsp_regs->override);
@@ -179,7 +182,10 @@ static void cdns_otg_enable_irq(struct cdns *cdns)
int cdns_drd_host_on(struct cdns *cdns)
{
u32 val, ready_bit;
- int ret;
+ int ret = 0;
+
+ if (cdns->no_drd)
+ goto phy_set;
/* Enable host mode. */
writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
@@ -197,6 +203,7 @@ int cdns_drd_host_on(struct cdns *cdns)
if (ret)
dev_err(cdns->dev, "timeout waiting for xhci_ready\n");
+phy_set:
phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_HOST);
phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_HOST);
return ret;
@@ -210,6 +217,9 @@ void cdns_drd_host_off(struct cdns *cdns)
{
u32 val;
+ if (cdns->no_drd)
+ goto phy_set;
+
writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP |
OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF,
&cdns->otg_regs->cmd);
@@ -218,6 +228,8 @@ void cdns_drd_host_off(struct cdns *cdns)
readl_poll_timeout_atomic(&cdns->otg_regs->state, val,
!(val & OTGSTATE_HOST_STATE_MASK),
1, 2000000);
+
+phy_set:
phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID);
phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID);
}
@@ -234,6 +246,9 @@ int cdns_drd_gadget_on(struct cdns *cdns)
u32 ready_bit;
int ret, val;
+ if (cdns->no_drd)
+ goto phy_set;
+
/* switch OTG core */
writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd);
@@ -251,6 +266,7 @@ int cdns_drd_gadget_on(struct cdns *cdns)
return ret;
}
+phy_set:
phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_DEVICE);
phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE);
return 0;
@@ -265,6 +281,9 @@ void cdns_drd_gadget_off(struct cdns *cdns)
{
u32 val;
+ if (cdns->no_drd)
+ goto phy_set;
+
/*
* Driver should wait at least 10us after disabling Device
* before turning-off Device (DEV_BUS_DROP).
@@ -277,6 +296,8 @@ void cdns_drd_gadget_off(struct cdns *cdns)
readl_poll_timeout_atomic(&cdns->otg_regs->state, val,
!(val & OTGSTATE_DEV_STATE_MASK),
1, 2000000);
+
+phy_set:
phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID);
phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID);
}
@@ -392,6 +413,18 @@ int cdns_drd_init(struct cdns *cdns)
u32 state, reg;
int ret;
+ if (cdns->no_drd) {
+ cdns->dr_mode = usb_get_dr_mode(cdns->dev);
+
+ if (cdns->dr_mode != USB_DR_MODE_HOST &&
+ cdns->dr_mode != USB_DR_MODE_PERIPHERAL) {
+ dev_err(cdns->dev, "Incorrect dr_mode\n");
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res);
if (IS_ERR(regs))
return PTR_ERR(regs);
@@ -492,6 +525,9 @@ int cdns_drd_init(struct cdns *cdns)
int cdns_drd_exit(struct cdns *cdns)
{
+ if (cdns->no_drd)
+ return 0;
+
cdns_otg_disable_irq(cdns);
return 0;
@@ -500,6 +536,9 @@ int cdns_drd_exit(struct cdns *cdns)
/* Indicate the cdns3 core was power lost before */
bool cdns_power_is_lost(struct cdns *cdns)
{
+ if (cdns->no_drd)
+ return false;
+
if (cdns->version == CDNS3_CONTROLLER_V0) {
if (!(readl(&cdns->otg_v0_regs->simulate) & BIT(0)))
return true;
diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h
index c37b6269b001..60c3177db62c 100644
--- a/drivers/usb/cdns3/gadget-export.h
+++ b/drivers/usb/cdns3/gadget-export.h
@@ -10,9 +10,10 @@
#ifndef __LINUX_CDNS3_GADGET_EXPORT
#define __LINUX_CDNS3_GADGET_EXPORT
-#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET)
+#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET)
int cdnsp_gadget_init(struct cdns *cdns);
+int cdns3_gadget_init(struct cdns *cdns);
#else
static inline int cdnsp_gadget_init(struct cdns *cdns)
@@ -20,13 +21,6 @@ static inline int cdnsp_gadget_init(struct cdns *cdns)
return -ENXIO;
}
-#endif /* CONFIG_USB_CDNSP_GADGET */
-
-#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET)
-
-int cdns3_gadget_init(struct cdns *cdns);
-#else
-
static inline int cdns3_gadget_init(struct cdns *cdns)
{
return -ENXIO;
diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
index cf92173ecf00..34fd1f1ad59d 100644
--- a/drivers/usb/cdns3/host-export.h
+++ b/drivers/usb/cdns3/host-export.h
@@ -9,7 +9,7 @@
#ifndef __LINUX_CDNS3_HOST_EXPORT
#define __LINUX_CDNS3_HOST_EXPORT
-#if IS_ENABLED(CONFIG_USB_CDNS_HOST)
+#if IS_ENABLED(CONFIG_USB_CDNS3_HOST)
int cdns_host_init(struct cdns *cdns);
@@ -22,6 +22,6 @@ static inline int cdns_host_init(struct cdns *cdns)
static inline void cdns_host_exit(struct cdns *cdns) { }
-#endif /* USB_CDNS_HOST */
+#endif /* CONFIG_USB_CDNS3_HOST */
#endif /* __LINUX_CDNS3_HOST_EXPORT */
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 2ab3db3c1015..07563be0013f 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -1187,19 +1187,16 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ci->role = ci_get_role(ci);
if (!ci_otg_is_fsm_mode(ci)) {
- /* only update vbus status for peripheral */
- if (ci->role == CI_ROLE_GADGET) {
- /* Pull down DP for possible charger detection */
- hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
- ci_handle_vbus_change(ci);
- }
-
ret = ci_role_start(ci, ci->role);
if (ret) {
dev_err(dev, "can't start %s role\n",
ci_role(ci)->name);
goto stop;
}
+
+ /* only update vbus status for peripheral */
+ if (ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_change(ci);
}
ret = devm_request_irq(dev, ci->irq, ci_irq_handler, IRQF_SHARED,
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index f2de86d0ce40..d52f89489893 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -1835,6 +1835,20 @@ static const struct usb_ep_ops usb_ep_ops = {
* GADGET block
*****************************************************************************/
+static void ci_udc_enable_vbus_irq(struct ci_hdrc *ci, bool enable)
+{
+ u32 reg = OTGSC_BSVIS;
+
+ if (!ci->is_otg)
+ return;
+
+ if (enable)
+ reg |= OTGSC_BSVIE;
+
+ /* Clear pending BSVIS and enable/disable BSVIE */
+ hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, reg);
+}
+
static int ci_udc_get_frame(struct usb_gadget *_gadget)
{
struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
@@ -2030,6 +2044,8 @@ static int init_eps(struct ci_hdrc *ci)
{
int retval = 0, i, j;
+ memset(ci->ci_hw_ep, 0, sizeof(ci->ci_hw_ep));
+
for (i = 0; i < ci->hw_ep_max/2; i++)
for (j = RX; j <= TX; j++) {
int k = i + j * ci->hw_ep_max/2;
@@ -2275,6 +2291,8 @@ static int udc_start(struct ci_hdrc *ci)
struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps;
int retval = 0;
+ memset(&ci->gadget, 0, sizeof(ci->gadget));
+
ci->gadget.ops = &usb_gadget_ops;
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->gadget.max_speed = USB_SPEED_HIGH;
@@ -2313,10 +2331,15 @@ static int udc_start(struct ci_hdrc *ci)
ci->gadget.ep0 = &ci->ep0in->ep;
+ if (ci->platdata->pins_device)
+ pinctrl_select_state(ci->platdata->pctl,
+ ci->platdata->pins_device);
+
retval = usb_add_gadget_udc(dev, &ci->gadget);
if (retval)
goto destroy_eps;
+ ci_udc_enable_vbus_irq(ci, true);
return retval;
destroy_eps:
@@ -2328,48 +2351,20 @@ free_qh_pool:
return retval;
}
-/*
- * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC
- *
- * No interrupts active, the IRQ has been released
+/**
+ * udc_stop: deinitialize gadget role
+ * @ci: chipidea controller
*/
-void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
+static void udc_stop(struct ci_hdrc *ci)
{
- if (!ci->roles[CI_ROLE_GADGET])
- return;
-
+ ci_udc_enable_vbus_irq(ci, false);
usb_del_gadget_udc(&ci->gadget);
+ ci->vbus_active = 0;
destroy_eps(ci);
dma_pool_destroy(ci->td_pool);
dma_pool_destroy(ci->qh_pool);
-}
-
-static int udc_id_switch_for_device(struct ci_hdrc *ci)
-{
- if (ci->platdata->pins_device)
- pinctrl_select_state(ci->platdata->pctl,
- ci->platdata->pins_device);
-
- if (ci->is_otg)
- /* Clear and enable BSV irq */
- hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
- OTGSC_BSVIS | OTGSC_BSVIE);
-
- return 0;
-}
-
-static void udc_id_switch_for_host(struct ci_hdrc *ci)
-{
- /*
- * host doesn't care B_SESSION_VALID event
- * so clear and disable BSV irq
- */
- if (ci->is_otg)
- hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
-
- ci->vbus_active = 0;
if (ci->platdata->pins_device && ci->platdata->pins_default)
pinctrl_select_state(ci->platdata->pctl,
@@ -2395,9 +2390,7 @@ static void udc_suspend(struct ci_hdrc *ci)
static void udc_resume(struct ci_hdrc *ci, bool power_lost)
{
if (power_lost) {
- if (ci->is_otg)
- hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
- OTGSC_BSVIS | OTGSC_BSVIE);
+ ci_udc_enable_vbus_irq(ci, true);
if (ci->vbus_active)
usb_gadget_vbus_disconnect(&ci->gadget);
} else if (ci->vbus_active && ci->driver &&
@@ -2420,7 +2413,6 @@ static void udc_resume(struct ci_hdrc *ci, bool power_lost)
int ci_hdrc_gadget_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
- int ret;
if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC))
return -ENXIO;
@@ -2429,8 +2421,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
if (!rdrv)
return -ENOMEM;
- rdrv->start = udc_id_switch_for_device;
- rdrv->stop = udc_id_switch_for_host;
+ rdrv->start = udc_start;
+ rdrv->stop = udc_stop;
#ifdef CONFIG_PM_SLEEP
rdrv->suspend = udc_suspend;
rdrv->resume = udc_resume;
@@ -2438,9 +2430,22 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
rdrv->irq = udc_irq;
rdrv->name = "gadget";
- ret = udc_start(ci);
- if (!ret)
- ci->roles[CI_ROLE_GADGET] = rdrv;
+ ci->roles[CI_ROLE_GADGET] = rdrv;
- return ret;
+ /* Pull down DP for possible charger detection */
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+ return 0;
+}
+
+/*
+ * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC
+ *
+ * No interrupts active, the IRQ has been released
+ */
+void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
+{
+ struct device *dev = &ci->gadget.dev;
+
+ if (ci->roles[CI_ROLE_GADGET] && device_is_registered(dev))
+ udc_stop(ci);
}
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index ddf0b5963859..49ab02f25872 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -797,6 +797,9 @@ static void acm_port_shutdown(struct tty_port *port)
"ctrl polling restart failed after port close\n");
/* port_shutdown() cleared DTR/RTS; restore them */
acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS);
+ if (acm_submit_read_urbs(acm, GFP_KERNEL))
+ dev_dbg(&acm->control->dev,
+ "read urb restart failed after port close\n");
}
}
@@ -1564,6 +1567,9 @@ skip_countries:
if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL))
dev_warn(&intf->dev,
"failed to start persistent ctrl polling\n");
+ if (acm_submit_read_urbs(acm, GFP_KERNEL))
+ dev_warn(&intf->dev,
+ "failed to start persistent bulk read polling\n");
}
return 0;
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index b181b43a35dc..8e8a5f59b319 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -446,7 +446,8 @@ rh_string(int id, struct usb_hcd const *hcd, u8 *data, unsigned len)
/* Root hub control transfers execute synchronously */
-static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
+static int rh_call_control(struct usb_hcd *hcd,
+ struct urb *urb, gfp_t mem_flags)
{
struct usb_ctrlrequest *cmd;
u16 typeReq, wValue, wIndex, wLength;
@@ -481,8 +482,8 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
* tbuf should be at least as big as the
* USB hub descriptor.
*/
- tbuf_size = max_t(u16, sizeof(struct usb_hub_descriptor), wLength);
- tbuf = kzalloc(tbuf_size, GFP_KERNEL);
+ tbuf_size = max_t(u16, sizeof(struct usb_hub_descriptor), wLength);
+ tbuf = kzalloc(tbuf_size, mem_flags);
if (!tbuf) {
status = -ENOMEM;
goto err_alloc;
@@ -807,12 +808,13 @@ static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
return retval;
}
-static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
+static int rh_urb_enqueue(struct usb_hcd *hcd,
+ struct urb *urb, gfp_t mem_flags)
{
if (usb_endpoint_xfer_int(&urb->ep->desc))
return rh_queue_status (hcd, urb);
if (usb_endpoint_xfer_control(&urb->ep->desc))
- return rh_call_control (hcd, urb);
+ return rh_call_control(hcd, urb, mem_flags);
return -EINVAL;
}
@@ -1533,7 +1535,7 @@ int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
*/
if (is_root_hub(urb->dev)) {
- status = rh_urb_enqueue(hcd, urb);
+ status = rh_urb_enqueue(hcd, urb, mem_flags);
} else {
status = map_urb_for_dma(hcd, urb, mem_flags);
if (likely(status == 0)) {
diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c
index 5c7538d498dd..f380275ac696 100644
--- a/drivers/usb/dwc2/hcd_intr.c
+++ b/drivers/usb/dwc2/hcd_intr.c
@@ -515,18 +515,20 @@ void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg,
u32 pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT;
if (chan->ep_type != USB_ENDPOINT_XFER_CONTROL) {
- if (WARN(!chan || !chan->qh,
- "chan->qh must be specified for non-control eps\n"))
+ if (!chan->qh) {
+ dev_err(hsotg->dev, "chan->qh must be specified for non-control eps\n");
return;
+ }
if (pid == TSIZ_SC_MC_PID_DATA0)
chan->qh->data_toggle = DWC2_HC_PID_DATA0;
else
chan->qh->data_toggle = DWC2_HC_PID_DATA1;
} else {
- if (WARN(!qtd,
- "qtd must be specified for control eps\n"))
+ if (!qtd) {
+ dev_err(hsotg->dev, "qtd must be specified for control eps\n");
return;
+ }
if (pid == TSIZ_SC_MC_PID_DATA0)
qtd->data_toggle = DWC2_HC_PID_DATA0;
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 65213896de99..517aa7f1486d 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -425,8 +425,7 @@ static void dwc3_ref_clk_period(struct dwc3 *dwc)
}
reg = dwc3_readl(dwc, DWC3_GUCTL);
- reg &= ~DWC3_GUCTL_REFCLKPER_MASK;
- reg |= FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, period);
+ FIELD_MODIFY(DWC3_GUCTL_REFCLKPER_MASK, &reg, period);
dwc3_writel(dwc, DWC3_GUCTL, reg);
if (DWC3_VER_IS_PRIOR(DWC3, 250A))
@@ -456,12 +455,9 @@ static void dwc3_ref_clk_period(struct dwc3 *dwc)
decr = 480000000 / rate;
reg = dwc3_readl(dwc, DWC3_GFLADJ);
- reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK
- & ~DWC3_GFLADJ_240MHZDECR
- & ~DWC3_GFLADJ_240MHZDECR_PLS1;
- reg |= FIELD_PREP(DWC3_GFLADJ_REFCLK_FLADJ_MASK, fladj)
- | FIELD_PREP(DWC3_GFLADJ_240MHZDECR, decr >> 1)
- | FIELD_PREP(DWC3_GFLADJ_240MHZDECR_PLS1, decr & 1);
+ FIELD_MODIFY(DWC3_GFLADJ_REFCLK_FLADJ_MASK, &reg, fladj);
+ FIELD_MODIFY(DWC3_GFLADJ_240MHZDECR, &reg, decr >> 1);
+ FIELD_MODIFY(DWC3_GFLADJ_240MHZDECR_PLS1, &reg, decr & 1);
if (dwc->gfladj_refclk_lpm_sel)
reg |= DWC3_GFLADJ_REFCLK_LPM_SEL;
@@ -525,7 +521,7 @@ static void dwc3_free_event_buffers(struct dwc3 *dwc)
}
/**
- * dwc3_alloc_event_buffers - Allocates @num event buffers of size @length
+ * dwc3_alloc_event_buffers - Allocate one event buffer of size @length
* @dwc: pointer to our controller context structure
* @length: size of event buffer
*
diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c
index 4ca567ec01d0..60ee4cc99b28 100644
--- a/drivers/usb/dwc3/dwc3-google.c
+++ b/drivers/usb/dwc3/dwc3-google.c
@@ -104,9 +104,8 @@ static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state)
regmap_read(google->usb_cfg_regmap,
google->host_cfg_offset + HOST_CFG1_OFFSET, &reg);
- reg &= ~HOST_CFG1_PM_POWER_STATE_REQUEST;
- reg |= (FIELD_PREP(HOST_CFG1_PM_POWER_STATE_REQUEST, state) |
- HOST_CFG1_PME_EN);
+ FIELD_MODIFY(HOST_CFG1_PM_POWER_STATE_REQUEST, &reg, state);
+ reg |= HOST_CFG1_PME_EN;
regmap_write(google->usb_cfg_regmap,
google->host_cfg_offset + HOST_CFG1_OFFSET, reg);
diff --git a/drivers/usb/dwc3/dwc3-octeon.c b/drivers/usb/dwc3/dwc3-octeon.c
index 42bfc14ae0c4..2201f0f34abb 100644
--- a/drivers/usb/dwc3/dwc3-octeon.c
+++ b/drivers/usb/dwc3/dwc3-octeon.c
@@ -296,8 +296,7 @@ static int dwc3_octeon_setup(struct dwc3_octeon *octeon,
return div;
}
val = dwc3_octeon_readq(uctl_ctl_reg);
- val &= ~USBDRD_UCTL_CTL_H_CLKDIV_SEL;
- val |= FIELD_PREP(USBDRD_UCTL_CTL_H_CLKDIV_SEL, div);
+ FIELD_MODIFY(USBDRD_UCTL_CTL_H_CLKDIV_SEL, &val, div);
val |= USBDRD_UCTL_CTL_H_CLK_EN;
dwc3_octeon_writeq(uctl_ctl_reg, val);
val = dwc3_octeon_readq(uctl_ctl_reg);
@@ -314,14 +313,11 @@ static int dwc3_octeon_setup(struct dwc3_octeon *octeon,
/* Step 5a: Reference clock configuration. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val &= ~USBDRD_UCTL_CTL_REF_CLK_DIV2;
- val &= ~USBDRD_UCTL_CTL_REF_CLK_SEL;
- val |= FIELD_PREP(USBDRD_UCTL_CTL_REF_CLK_SEL, ref_clk_sel);
+ FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_SEL, &val, ref_clk_sel);
- val &= ~USBDRD_UCTL_CTL_REF_CLK_FSEL;
- val |= FIELD_PREP(USBDRD_UCTL_CTL_REF_CLK_FSEL, ref_clk_fsel);
+ FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_FSEL, &val, ref_clk_fsel);
- val &= ~USBDRD_UCTL_CTL_MPLL_MULTIPLIER;
- val |= FIELD_PREP(USBDRD_UCTL_CTL_MPLL_MULTIPLIER, mpll_mul);
+ FIELD_MODIFY(USBDRD_UCTL_CTL_MPLL_MULTIPLIER, &val, mpll_mul);
/* Step 5b: Configure and enable spread-spectrum for SuperSpeed. */
val |= USBDRD_UCTL_CTL_SSC_EN;
diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c
index 7fc6696b7694..75f9c831b21a 100644
--- a/drivers/usb/gadget/udc/aspeed_udc.c
+++ b/drivers/usb/gadget/udc/aspeed_udc.c
@@ -694,7 +694,7 @@ static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
struct ast_udc_dev *udc = ep->udc;
struct ast_udc_request *req;
unsigned long flags;
- int rc = 0;
+ int rc = -EINVAL;
spin_lock_irqsave(&udc->lock, flags);
@@ -704,14 +704,11 @@ static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
list_del_init(&req->queue);
ast_udc_done(ep, req, -ESHUTDOWN);
_req->status = -ECONNRESET;
+ rc = 0;
break;
}
}
- /* dequeue request not found */
- if (&req->req != _req)
- rc = -EINVAL;
-
spin_unlock_irqrestore(&udc->lock, flags);
return rc;
diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index e8861eaad907..60340ff9edbf 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -712,6 +712,9 @@ static int usb_gadget_connect_locked(struct usb_gadget *gadget)
goto out;
}
+ if (gadget->connected)
+ goto out;
+
if (gadget->deactivated || !gadget->udc->allow_connect || !gadget->udc->started) {
/*
* If the gadget isn't usable (because it is deactivated,
@@ -885,8 +888,10 @@ int usb_gadget_activate(struct usb_gadget *gadget)
* If gadget has been connected before deactivation, or became connected
* while it was being deactivated, we call usb_gadget_connect().
*/
- if (gadget->connected)
+ if (gadget->connected) {
+ gadget->connected = false;
ret = usb_gadget_connect_locked(gadget);
+ }
unlock:
mutex_unlock(&gadget->udc->connect_lock);
diff --git a/drivers/usb/gadget/udc/goku_udc.c b/drivers/usb/gadget/udc/goku_udc.c
index db42a5e3e805..ac2a984c2f87 100644
--- a/drivers/usb/gadget/udc/goku_udc.c
+++ b/drivers/usb/gadget/udc/goku_udc.c
@@ -1616,7 +1616,8 @@ pm_next:
if (stat & INT_USBRESET) { /* hub reset done */
ACK(INT_USBRESET);
INFO(dev, "USB reset done, gadget %s\n",
- dev->driver->driver.name);
+ dev->driver ? dev->driver->driver.name :
+ "<not bound>");
}
// and INT_ERR on some endpoint's crc/bitstuff/... problem
}
diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c
index b3d58d7c3a77..594d67193763 100644
--- a/drivers/usb/gadget/udc/pxa25x_udc.c
+++ b/drivers/usb/gadget/udc/pxa25x_udc.c
@@ -12,7 +12,7 @@
/* #define VERBOSE_DEBUG */
#include <linux/device.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ioport.h>
@@ -261,24 +261,12 @@ static void nuke (struct pxa25x_ep *, int status);
/* one GPIO should control a D+ pullup, so host sees this device (or not) */
static void pullup_off(void)
{
- struct pxa2xx_udc_mach_info *mach = the_controller->mach;
- int off_level = mach->gpio_pullup_inverted;
-
- if (gpio_is_valid(mach->gpio_pullup))
- gpio_set_value(mach->gpio_pullup, off_level);
- else if (mach->udc_command)
- mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT);
+ gpiod_set_value(the_controller->pullup_gpio, 0);
}
static void pullup_on(void)
{
- struct pxa2xx_udc_mach_info *mach = the_controller->mach;
- int on_level = !mach->gpio_pullup_inverted;
-
- if (gpio_is_valid(mach->gpio_pullup))
- gpio_set_value(mach->gpio_pullup, on_level);
- else if (mach->udc_command)
- mach->udc_command(PXA2XX_UDC_CMD_CONNECT);
+ gpiod_set_value(the_controller->pullup_gpio, 1);
}
#if defined(CONFIG_CPU_BIG_ENDIAN)
@@ -1190,8 +1178,7 @@ static int pxa25x_udc_pullup(struct usb_gadget *_gadget, int is_active)
udc = container_of(_gadget, struct pxa25x_udc, gadget);
- /* not all boards support pullup control */
- if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command)
+ if (!udc->pullup_gpio)
return -EOPNOTSUPP;
udc->pullup = (is_active != 0);
@@ -2343,19 +2330,17 @@ static int pxa25x_udc_probe(struct platform_device *pdev)
/* other non-static parts of init */
dev->dev = &pdev->dev;
- dev->mach = dev_get_platdata(&pdev->dev);
dev->transceiver = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
- if (gpio_is_valid(dev->mach->gpio_pullup)) {
- retval = devm_gpio_request_one(&pdev->dev, dev->mach->gpio_pullup,
- GPIOF_OUT_INIT_LOW, "pca25x_udc GPIO PULLUP");
- if (retval) {
- dev_dbg(&pdev->dev,
- "can't get pullup gpio %d, err: %d\n",
- dev->mach->gpio_pullup, retval);
- goto err;
- }
+ dev->pullup_gpio = devm_gpiod_get_index_optional(&pdev->dev, "pullup", 0,
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(dev->pullup_gpio)) {
+ dev_dbg(&pdev->dev,
+ "can't get pullup gpio err: %ld\n",
+ PTR_ERR(dev->pullup_gpio));
+ retval = PTR_ERR(dev->pullup_gpio);
+ goto err;
}
timer_setup(&dev->timer, udc_watchdog, 0);
@@ -2439,7 +2424,7 @@ static int pxa25x_udc_suspend(struct platform_device *dev, pm_message_t state)
struct pxa25x_udc *udc = platform_get_drvdata(dev);
unsigned long flags;
- if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command)
+ if (!udc->pullup_gpio)
WARNING("USB host won't detect disconnect!\n");
udc->suspended = 1;
diff --git a/drivers/usb/gadget/udc/pxa25x_udc.h b/drivers/usb/gadget/udc/pxa25x_udc.h
index 6ab6047edc83..3452cf54286c 100644
--- a/drivers/usb/gadget/udc/pxa25x_udc.h
+++ b/drivers/usb/gadget/udc/pxa25x_udc.h
@@ -112,7 +112,7 @@ struct pxa25x_udc {
struct device *dev;
struct clk *clk;
- struct pxa2xx_udc_mach_info *mach;
+ struct gpio_desc *pullup_gpio;
struct usb_phy *transceiver;
u64 dma_mask;
struct pxa25x_ep ep [PXA_UDC_NUM_ENDPOINTS];
diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c
index 1abea0d48c35..640f81988c04 100644
--- a/drivers/usb/gadget/udc/pxa27x_udc.c
+++ b/drivers/usb/gadget/udc/pxa27x_udc.c
@@ -17,7 +17,6 @@
#include <linux/proc_fs.h>
#include <linux/clk.h>
#include <linux/irq.h>
-#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/slab.h>
#include <linux/string_choices.h>
@@ -1423,14 +1422,7 @@ static const struct usb_ep_ops pxa_ep_ops = {
*/
static void dplus_pullup(struct pxa_udc *udc, int on)
{
- if (udc->gpiod) {
- gpiod_set_value(udc->gpiod, on);
- } else if (udc->udc_command) {
- if (on)
- udc->udc_command(PXA2XX_UDC_CMD_CONNECT);
- else
- udc->udc_command(PXA2XX_UDC_CMD_DISCONNECT);
- }
+ gpiod_set_value(udc->gpiod, on);
udc->pullup_on = on;
}
@@ -1521,7 +1513,7 @@ static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active)
struct pxa_udc *udc = to_gadget_udc(_gadget);
int ret;
- if (!udc->gpiod && !udc->udc_command)
+ if (!udc->gpiod)
return -EOPNOTSUPP;
dplus_pullup(udc, is_active);
@@ -2380,26 +2372,11 @@ MODULE_DEVICE_TABLE(of, udc_pxa_dt_ids);
static int pxa_udc_probe(struct platform_device *pdev)
{
struct pxa_udc *udc = &memory;
- int retval = 0, gpio;
- struct pxa2xx_udc_mach_info *mach = dev_get_platdata(&pdev->dev);
-
- if (mach) {
- gpio = mach->gpio_pullup;
- if (gpio_is_valid(gpio)) {
- retval = devm_gpio_request_one(&pdev->dev, gpio,
- GPIOF_OUT_INIT_LOW,
- "USB D+ pullup");
- if (retval)
- return retval;
- udc->gpiod = gpio_to_desc(mach->gpio_pullup);
-
- if (mach->gpio_pullup_inverted ^ gpiod_is_active_low(udc->gpiod))
- gpiod_toggle_active_low(udc->gpiod);
- }
- udc->udc_command = mach->udc_command;
- } else {
- udc->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_ASIS);
- }
+ int retval = 0;
+
+ udc->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_ASIS);
+ if (IS_ERR(udc->gpiod))
+ return PTR_ERR(udc->gpiod);
udc->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(udc->regs))
diff --git a/drivers/usb/gadget/udc/pxa27x_udc.h b/drivers/usb/gadget/udc/pxa27x_udc.h
index 31bf79ce931c..2c28b691010a 100644
--- a/drivers/usb/gadget/udc/pxa27x_udc.h
+++ b/drivers/usb/gadget/udc/pxa27x_udc.h
@@ -426,7 +426,6 @@ struct udc_stats {
* @usb_gadget: udc gadget structure
* @driver: bound gadget (zero, g_ether, g_mass_storage, ...)
* @dev: device
- * @udc_command: machine specific function to activate D+ pullup
* @gpiod: gpio descriptor of gpio for D+ pullup (or NULL if none)
* @transceiver: external transceiver to handle vbus sense and D+ pullup
* @ep0state: control endpoint state machine state
@@ -452,7 +451,6 @@ struct pxa_udc {
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
struct device *dev;
- void (*udc_command)(int);
struct gpio_desc *gpiod;
struct usb_phy *transceiver;
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 0a277a07cf70..b3b1ec696bf5 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -42,6 +42,12 @@ config USB_XHCI_PCI
depends on USB_PCI
default y
+config USB_XHCI_PCI_PROM21
+ tristate
+ depends on USB_XHCI_PCI
+ default USB_XHCI_PCI if SENSORS_PROM21_XHCI != n
+ select AUXILIARY_BUS
+
config USB_XHCI_PCI_RENESAS
tristate "Support for additional Renesas xHCI controller with firmware"
depends on USB_XHCI_PCI
@@ -71,7 +77,7 @@ config USB_XHCI_HISTB
config USB_XHCI_MTK
tristate "xHCI support for MediaTek SoCs"
select MFD_SYSCON
- depends on (MIPS && SOC_MT7621) || ARCH_MEDIATEK || COMPILE_TEST
+ depends on (MIPS && SOC_MT7621) || ARCH_MEDIATEK || ARCH_AIROHA || COMPILE_TEST
help
Say 'Y' to enable the support for the xHCI host controller
found in MediaTek SoCs.
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index a07e7ba9cd53..174580c1281a 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
obj-$(CONFIG_USB_FHCI_HCD) += fhci.o
obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
+obj-$(CONFIG_USB_XHCI_PCI_PROM21) += xhci-pci-prom21.o
obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c
index 0e17c988d36a..73e76d0e6973 100644
--- a/drivers/usb/host/max3421-hcd.c
+++ b/drivers/usb/host/max3421-hcd.c
@@ -1685,6 +1685,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index,
case ClearHubFeature:
break;
case ClearPortFeature:
+ if (index != 1)
+ goto error;
switch (value) {
case USB_PORT_FEAT_SUSPEND:
break;
@@ -1694,6 +1696,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index,
!pdata->vbus_active_level);
fallthrough;
default:
+ if (value >= 32)
+ goto error;
max3421_hcd->port_status &= ~(1 << value);
}
break;
@@ -1726,6 +1730,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index,
break;
case SetPortFeature:
+ if (index != 1)
+ goto error;
switch (value) {
case USB_PORT_FEAT_LINK_STATE:
case USB_PORT_FEAT_U1_TIMEOUT:
@@ -1747,6 +1753,8 @@ max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index,
max3421_reset_port(hcd);
fallthrough;
default:
+ if (value >= 32)
+ goto error;
if ((max3421_hcd->port_status & USB_PORT_STAT_POWER)
!= 0)
max3421_hcd->port_status |= (1 << value);
diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c
index 7e6f7d72f03e..b1cabf5582fa 100644
--- a/drivers/usb/host/xhci-dbgcap.c
+++ b/drivers/usb/host/xhci-dbgcap.c
@@ -275,7 +275,6 @@ xhci_dbc_queue_trb(struct xhci_ring *ring, u32 field1,
trace_xhci_dbc_gadget_ep_queue(ring, &trb->generic,
xhci_trb_virt_to_dma(ring->enq_seg,
ring->enqueue));
- ring->num_trbs_free--;
next = ++(ring->enqueue);
if (TRB_TYPE_LINK_LE32(next->link.control)) {
next->link.control ^= cpu_to_le32(TRB_CYCLE);
@@ -296,7 +295,7 @@ static int xhci_dbc_queue_bulk_tx(struct dbc_ep *dep,
num_trbs = count_trbs(req->dma, req->length);
WARN_ON(num_trbs != 1);
- if (ring->num_trbs_free < num_trbs)
+ if (xhci_num_trbs_free(ring) <= num_trbs)
return -EBUSY;
addr = req->dma;
@@ -629,18 +628,36 @@ static void xhci_dbc_mem_cleanup(struct xhci_dbc *dbc)
dbc->ring_evt = NULL;
}
+static int xhci_dbc_enable_dce(struct xhci_dbc *dbc, bool enable)
+{
+ u32 done_state = 0;
+ u32 ctrl = 0;
+
+ if (enable) {
+ ctrl = readl(&dbc->regs->control);
+ ctrl |= DBC_CTRL_DBC_ENABLE | DBC_CTRL_PORT_ENABLE;
+ done_state = DBC_CTRL_DBC_ENABLE;
+ }
+
+ writel(ctrl, &dbc->regs->control);
+ return xhci_handshake(&dbc->regs->control, DBC_CTRL_DBC_ENABLE,
+ done_state, 1000);
+}
+
+static void xhci_dbc_set_state(struct xhci_dbc *dbc, enum dbc_state new_state)
+{
+ dbc->state_timestamp = jiffies;
+ dbc->state = new_state;
+}
+
static int xhci_do_dbc_start(struct xhci_dbc *dbc)
{
int ret;
- u32 ctrl;
if (dbc->state != DS_DISABLED)
return -EINVAL;
- writel(0, &dbc->regs->control);
- ret = xhci_handshake(&dbc->regs->control,
- DBC_CTRL_DBC_ENABLE,
- 0, 1000);
+ ret = xhci_dbc_enable_dce(dbc, false);
if (ret)
return ret;
@@ -648,27 +665,11 @@ static int xhci_do_dbc_start(struct xhci_dbc *dbc)
if (ret)
return ret;
- ctrl = readl(&dbc->regs->control);
- writel(ctrl | DBC_CTRL_DBC_ENABLE | DBC_CTRL_PORT_ENABLE,
- &dbc->regs->control);
- ret = xhci_handshake(&dbc->regs->control,
- DBC_CTRL_DBC_ENABLE,
- DBC_CTRL_DBC_ENABLE, 1000);
+ ret = xhci_dbc_enable_dce(dbc, true);
if (ret)
return ret;
- dbc->state = DS_ENABLED;
-
- return 0;
-}
-
-static int xhci_do_dbc_stop(struct xhci_dbc *dbc)
-{
- if (dbc->state == DS_DISABLED)
- return -EINVAL;
-
- writel(0, &dbc->regs->control);
- dbc->state = DS_DISABLED;
+ xhci_dbc_set_state(dbc, DS_ENABLED);
return 0;
}
@@ -684,29 +685,37 @@ static int xhci_dbc_start(struct xhci_dbc *dbc)
spin_lock_irqsave(&dbc->lock, flags);
ret = xhci_do_dbc_start(dbc);
+ if (ret)
+ goto err_unlock;
+
spin_unlock_irqrestore(&dbc->lock, flags);
- if (ret) {
- pm_runtime_put(dbc->dev); /* note this was self.controller */
- return ret;
- }
+ mod_delayed_work(system_percpu_wq, &dbc->event_work,
+ msecs_to_jiffies(dbc->poll_interval));
+
+ return 0;
- return mod_delayed_work(system_percpu_wq, &dbc->event_work,
- msecs_to_jiffies(dbc->poll_interval));
+err_unlock:
+
+ spin_unlock_irqrestore(&dbc->lock, flags);
+ pm_runtime_put(dbc->dev); /* note this was self.controller */
+
+ return ret;
}
static void xhci_dbc_stop(struct xhci_dbc *dbc)
{
- int ret;
unsigned long flags;
WARN_ON(!dbc);
+ spin_lock(&dbc->lock);
+
switch (dbc->state) {
case DS_DISABLED:
+ spin_unlock(&dbc->lock);
return;
case DS_CONFIGURED:
- spin_lock(&dbc->lock);
xhci_dbc_flush_requests(dbc);
spin_unlock(&dbc->lock);
@@ -714,19 +723,20 @@ static void xhci_dbc_stop(struct xhci_dbc *dbc)
dbc->driver->disconnect(dbc);
break;
default:
+ spin_unlock(&dbc->lock);
break;
}
cancel_delayed_work_sync(&dbc->event_work);
spin_lock_irqsave(&dbc->lock, flags);
- ret = xhci_do_dbc_stop(dbc);
+ writel(0, &dbc->regs->control);
+ xhci_dbc_set_state(dbc, DS_DISABLED);
spin_unlock_irqrestore(&dbc->lock, flags);
- if (ret)
- return;
xhci_dbc_mem_cleanup(dbc);
- pm_runtime_put_sync(dbc->dev); /* note, was self.controller */
+
+ pm_runtime_put(dbc->dev); /* note, was self.controller */
}
static void
@@ -796,7 +806,6 @@ static void dbc_handle_xfer_event(struct xhci_dbc *dbc, union xhci_trb *event)
}
if (r->status == -COMP_STALL_ERROR) {
dev_warn(dbc->dev, "Give back stale stalled req\n");
- ring->num_trbs_free++;
xhci_dbc_giveback(r, 0);
}
}
@@ -861,7 +870,6 @@ static void dbc_handle_xfer_event(struct xhci_dbc *dbc, union xhci_trb *event)
break;
}
- ring->num_trbs_free++;
req->actual = req->length - remain_length;
xhci_dbc_giveback(req, status);
}
@@ -893,23 +901,49 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc)
return EVT_ERR;
case DS_ENABLED:
portsc = readl(&dbc->regs->portsc);
+ ctrl = readl(&dbc->regs->control);
+
if (portsc & DBC_PORTSC_CONN_STATUS) {
- dbc->state = DS_CONNECTED;
+ xhci_dbc_set_state(dbc, DS_CONNECTED);
dev_info(dbc->dev, "DbC connected\n");
+ } else if (!(ctrl & DBC_CTRL_DBC_ENABLE)) {
+ dev_err(dbc->dev, "unexpected DbC disable, xHC reset?\n");
}
return EVT_DONE;
case DS_CONNECTED:
ctrl = readl(&dbc->regs->control);
+ portsc = readl(&dbc->regs->portsc);
if (ctrl & DBC_CTRL_DBC_RUN) {
- dbc->state = DS_CONFIGURED;
+ xhci_dbc_set_state(dbc, DS_CONFIGURED);
dev_info(dbc->dev, "DbC configured\n");
- portsc = readl(&dbc->regs->portsc);
writel(portsc, &dbc->regs->portsc);
ret = EVT_GSER;
break;
}
+ /* Connection lost */
+ if (!(portsc & DBC_PORTSC_CONN_STATUS)) {
+ /* covers DCE == 0 as it also sets CONN_STATUS to 0 */
+ dev_warn(dbc->dev, "DbC connection lost mid enumeration\n");
+ xhci_dbc_set_state(dbc, DS_ENABLED);
+
+ return EVT_DONE;
+ }
+
+ /* Enumeration timeout */
+ if (time_is_before_jiffies(dbc->state_timestamp +
+ msecs_to_jiffies(DBC_ENUMERATION_TIMEOUT))) {
+ dev_err(dbc->dev, "DbC enumeration timeout, re-enabling DbC\n");
+ dev_dbg(dbc->dev, "dcctrl %x, dcportsc %x\n", ctrl, portsc);
+
+ /* Toggle DCE to retry enumeration */
+ ret = xhci_dbc_enable_dce(dbc, false);
+ udelay(100);
+ ret = xhci_dbc_enable_dce(dbc, true);
+ xhci_dbc_set_state(dbc, DS_ENABLED);
+ }
+
return EVT_DONE;
case DS_CONFIGURED:
/* Handle cable unplug event: */
@@ -917,7 +951,7 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc)
if (!(portsc & DBC_PORTSC_PORT_ENABLED) &&
!(portsc & DBC_PORTSC_CONN_STATUS)) {
dev_info(dbc->dev, "DbC cable unplugged\n");
- dbc->state = DS_ENABLED;
+ xhci_dbc_set_state(dbc, DS_ENABLED);
xhci_dbc_flush_requests(dbc);
xhci_dbc_reinit_ep_rings(dbc);
return EVT_DISC;
@@ -927,7 +961,7 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc)
if (portsc & DBC_PORTSC_RESET_CHANGE) {
dev_info(dbc->dev, "DbC port reset\n");
writel(portsc, &dbc->regs->portsc);
- dbc->state = DS_ENABLED;
+ xhci_dbc_set_state(dbc, DS_ENABLED);
xhci_dbc_flush_requests(dbc);
xhci_dbc_reinit_ep_rings(dbc);
return EVT_DISC;
@@ -1075,12 +1109,17 @@ static ssize_t dbc_store(struct device *dev,
xhci = hcd_to_xhci(dev_get_drvdata(dev));
dbc = xhci->dbc;
- if (sysfs_streq(buf, "enable"))
+ if (sysfs_streq(buf, "enable")) {
+ mutex_lock(&dbc->enable_mutex);
xhci_dbc_start(dbc);
- else if (sysfs_streq(buf, "disable"))
+ mutex_unlock(&dbc->enable_mutex);
+ } else if (sysfs_streq(buf, "disable")) {
+ mutex_lock(&dbc->enable_mutex);
xhci_dbc_stop(dbc);
- else
+ mutex_unlock(&dbc->enable_mutex);
+ } else {
return -EINVAL;
+ }
return count;
}
@@ -1446,6 +1485,7 @@ xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *
INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
spin_lock_init(&dbc->lock);
+ mutex_init(&dbc->enable_mutex);
ret = sysfs_create_groups(&dev->kobj, dbc_dev_groups);
if (ret)
@@ -1463,8 +1503,9 @@ void xhci_dbc_remove(struct xhci_dbc *dbc)
if (!dbc)
return;
/* stop hw, stop wq and call dbc->ops->stop() */
+ mutex_lock(&dbc->enable_mutex);
xhci_dbc_stop(dbc);
-
+ mutex_unlock(&dbc->enable_mutex);
/* remove sysfs files */
sysfs_remove_groups(&dbc->dev->kobj, dbc_dev_groups);
@@ -1517,6 +1558,8 @@ int xhci_dbc_suspend(struct xhci_hcd *xhci)
if (!dbc)
return 0;
+ mutex_lock(&dbc->enable_mutex);
+
switch (dbc->state) {
case DS_ENABLED:
case DS_CONNECTED:
@@ -1528,6 +1571,7 @@ int xhci_dbc_suspend(struct xhci_hcd *xhci)
}
xhci_dbc_stop(dbc);
+ mutex_unlock(&dbc->enable_mutex);
return 0;
}
@@ -1540,11 +1584,15 @@ int xhci_dbc_resume(struct xhci_hcd *xhci)
if (!dbc)
return 0;
+ mutex_lock(&dbc->enable_mutex);
+
if (dbc->resume_required) {
dbc->resume_required = 0;
xhci_dbc_start(dbc);
}
+ mutex_unlock(&dbc->enable_mutex);
+
return ret;
}
#endif /* CONFIG_PM */
diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h
index 20ae4e7617f2..df7aca8bfe99 100644
--- a/drivers/usb/host/xhci-dbgcap.h
+++ b/drivers/usb/host/xhci-dbgcap.h
@@ -113,6 +113,7 @@ struct dbc_ep {
#define DBC_POLL_INTERVAL_DEFAULT 64 /* milliseconds */
#define DBC_POLL_INTERVAL_MAX 5000 /* milliseconds */
#define DBC_XFER_INACTIVITY_TIMEOUT 10 /* milliseconds */
+#define DBC_ENUMERATION_TIMEOUT 2000 /* milliseconds */
/*
* Private structure for DbC hardware state:
*/
@@ -140,6 +141,7 @@ struct dbc_driver {
struct xhci_dbc {
spinlock_t lock; /* device access */
+ struct mutex enable_mutex;
struct device *dev;
struct xhci_hcd *xhci;
struct dbc_regs __iomem *regs;
@@ -162,6 +164,7 @@ struct xhci_dbc {
struct delayed_work event_work;
unsigned int poll_interval; /* ms */
unsigned long xfer_timestamp;
+ unsigned long state_timestamp;
unsigned resume_required:1;
struct dbc_ep eps[2];
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index bacd0ddd0d09..b0264bd8577a 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -226,9 +226,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,
USB_SSP_SUBLINK_SPEED_ST_SYM_RX);
ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
- attr &= ~USB_SSP_SUBLINK_SPEED_ST;
- attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST,
- USB_SSP_SUBLINK_SPEED_ST_SYM_TX);
+ FIELD_MODIFY(USB_SSP_SUBLINK_SPEED_ST, &attr,
+ USB_SSP_SUBLINK_SPEED_ST_SYM_TX);
ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr);
break;
case PLT_ASYM_RX:
@@ -639,7 +638,7 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd)
/*
* xhci_set_port_power() must be called with xhci->lock held.
- * It will release and re-aquire the lock while calling ACPI
+ * It will release and re-acquire the lock while calling ACPI
* method.
*/
static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port,
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 997fe90f54e5..a5e7f363922f 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -324,12 +324,6 @@ void xhci_initialize_ring_info(struct xhci_ring *ring)
* handling ring expansion, set the cycle state equal to the old ring.
*/
ring->cycle_state = 1;
-
- /*
- * Each segment has a link TRB, and leave an extra TRB for SW
- * accounting purpose
- */
- ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1;
}
EXPORT_SYMBOL_GPL(xhci_initialize_ring_info);
@@ -883,8 +877,8 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev,
/* If device ctx array still points to _this_ device, clear it */
if (dev->out_ctx &&
- xhci->dcbaa->dev_context_ptrs[slot_id] == cpu_to_le64(dev->out_ctx->dma))
- xhci->dcbaa->dev_context_ptrs[slot_id] = 0;
+ xhci->dcbaa.ctx_array[slot_id] == cpu_to_le64(dev->out_ctx->dma))
+ xhci->dcbaa.ctx_array[slot_id] = 0;
trace_xhci_free_virt_device(dev);
@@ -1022,11 +1016,11 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
dev->udev = udev;
/* Point to output device context in dcbaa. */
- xhci->dcbaa->dev_context_ptrs[slot_id] = cpu_to_le64(dev->out_ctx->dma);
+ xhci->dcbaa.ctx_array[slot_id] = cpu_to_le64(dev->out_ctx->dma);
xhci_dbg(xhci, "Set slot id %d dcbaa entry %p to 0x%llx\n",
slot_id,
- &xhci->dcbaa->dev_context_ptrs[slot_id],
- le64_to_cpu(xhci->dcbaa->dev_context_ptrs[slot_id]));
+ &xhci->dcbaa.ctx_array[slot_id],
+ le64_to_cpu(xhci->dcbaa.ctx_array[slot_id]));
trace_xhci_alloc_virt_device(dev);
@@ -1677,7 +1671,7 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
if (!xhci->scratchpad->sp_buffers)
goto fail_sp3;
- xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma);
+ xhci->dcbaa.ctx_array[0] = cpu_to_le64(xhci->scratchpad->sp_dma);
for (i = 0; i < num_sp; i++) {
dma_addr_t dma;
void *buf = dma_alloc_coherent(dev, xhci->page_size, &dma,
@@ -1933,6 +1927,7 @@ void xhci_rh_bw_cleanup(struct xhci_hcd *xhci)
void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+ struct xhci_device_context_array *dcbaa;
int i;
cancel_delayed_work_sync(&xhci->cmd_timer);
@@ -1952,8 +1947,11 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed command ring");
xhci_cleanup_command_queue(xhci);
- for (i = xhci->max_slots; i > 0; i--)
- xhci_free_virt_devices_depth_first(xhci, i);
+ if (xhci->devs) {
+ for (i = xhci->max_slots; i > 0; i--)
+ xhci_free_virt_devices_depth_first(xhci, i);
+ kfree(xhci->devs);
+ }
dma_pool_destroy(xhci->segment_pool);
xhci->segment_pool = NULL;
@@ -1978,10 +1976,12 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Freed medium stream array pool");
- if (xhci->dcbaa)
- dma_free_coherent(dev, sizeof(*xhci->dcbaa),
- xhci->dcbaa, xhci->dcbaa->dma);
- xhci->dcbaa = NULL;
+ dcbaa = &xhci->dcbaa;
+ if (dcbaa->ctx_array) {
+ dma_free_coherent(dev, array_size(sizeof(*dcbaa->ctx_array), xhci->max_slots + 1),
+ dcbaa->ctx_array, dcbaa->dma);
+ dcbaa->ctx_array = NULL;
+ }
scratchpad_free(xhci);
@@ -2008,6 +2008,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci->rh_bw = NULL;
xhci->port_caps = NULL;
xhci->interrupters = NULL;
+ xhci->devs = NULL;
xhci->usb2_rhub.bus_state.bus_suspended = 0;
xhci->usb3_rhub.bus_state.bus_suspended = 0;
@@ -2409,23 +2410,26 @@ EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter);
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
- struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
- dma_addr_t dma;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+ struct xhci_device_context_array *dcbaa = &xhci->dcbaa;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__);
- /*
- * xHCI section 5.4.6 - Device Context array must be
- * "physically contiguous and 64-byte (cache line) aligned".
- */
- xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, flags);
- if (!xhci->dcbaa)
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating internal virtual device array");
+ xhci->devs = kcalloc_node(xhci->max_slots + 1, sizeof(*xhci->devs), flags,
+ dev_to_node(dev));
+ if (!xhci->devs)
+ goto fail;
+
+ xhci->dcbaa.ctx_array =
+ dma_alloc_coherent(dev, array_size(sizeof(*dcbaa->ctx_array), xhci->max_slots + 1),
+ &dcbaa->dma, flags);
+ if (!dcbaa->ctx_array)
goto fail;
- xhci->dcbaa->dma = dma;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Device context base array address = %pad (DMA), %p (virt)",
- &xhci->dcbaa->dma, xhci->dcbaa);
+ &dcbaa->dma, dcbaa->ctx_array);
/*
* Initialize the ring segment pool. The ring must be a contiguous
diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
index 06043c7c3100..d9b865546a67 100644
--- a/drivers/usb/host/xhci-mtk.c
+++ b/drivers/usb/host/xhci-mtk.c
@@ -185,9 +185,9 @@ static void xhci_mtk_rxfifo_depth_set(struct xhci_hcd_mtk *mtk)
return;
value = readl(hcd->regs + HSCH_CFG1);
- value &= ~SCH3_RXFIFO_DEPTH_MASK;
- value |= FIELD_PREP(SCH3_RXFIFO_DEPTH_MASK,
- SCH_FIFO_TO_KB(mtk->rxfifo_depth) - 1);
+ FIELD_MODIFY(SCH3_RXFIFO_DEPTH_MASK, &value,
+ SCH_FIFO_TO_KB(mtk->rxfifo_depth) - 1);
+
writel(value, hcd->regs + HSCH_CFG1);
}
diff --git a/drivers/usb/host/xhci-pci-prom21.c b/drivers/usb/host/xhci-pci-prom21.c
new file mode 100644
index 000000000000..6486f4a09345
--- /dev/null
+++ b/drivers/usb/host/xhci-pci-prom21.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Promontory 21 xHCI host controller PCI Bus Glue.
+ *
+ * This does not add any PROM21-specific USB or xHCI operation. It exists only
+ * to publish an auxiliary device for integrated temperature sensor support.
+ *
+ * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
+#include <linux/errno.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_data/usb-xhci-prom21.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "xhci-pci.h"
+
+struct prom21_xhci_auxdev {
+ struct auxiliary_device *auxdev;
+ struct prom21_xhci_pdata pdata;
+ int id;
+};
+
+static DEFINE_IDA(prom21_xhci_auxdev_ida);
+
+static void prom21_xhci_auxdev_release(struct device *dev, void *res)
+{
+ struct prom21_xhci_auxdev *prom21_auxdev = res;
+
+ auxiliary_device_destroy(prom21_auxdev->auxdev);
+ ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id);
+}
+
+static int prom21_xhci_create_auxdev(struct pci_dev *pdev)
+{
+ struct prom21_xhci_auxdev *prom21_auxdev;
+ struct usb_hcd *hcd = pci_get_drvdata(pdev);
+ int ret;
+
+ prom21_auxdev = devres_alloc(prom21_xhci_auxdev_release,
+ sizeof(*prom21_auxdev), GFP_KERNEL);
+ if (!prom21_auxdev)
+ return -ENOMEM;
+
+ prom21_auxdev->pdata.pdev = pdev;
+ prom21_auxdev->pdata.regs = hcd->regs;
+ prom21_auxdev->pdata.rsrc_len = hcd->rsrc_len;
+
+ prom21_auxdev->id = ida_alloc(&prom21_xhci_auxdev_ida, GFP_KERNEL);
+ if (prom21_auxdev->id < 0) {
+ ret = prom21_auxdev->id;
+ goto err_free_devres;
+ }
+
+ prom21_auxdev->auxdev = auxiliary_device_create(&pdev->dev,
+ KBUILD_MODNAME, "hwmon",
+ &prom21_auxdev->pdata,
+ prom21_auxdev->id);
+ if (!prom21_auxdev->auxdev) {
+ ret = -ENOMEM;
+ goto err_free_ida;
+ }
+
+ devres_add(&pdev->dev, prom21_auxdev);
+ return 0;
+
+err_free_ida:
+ ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id);
+err_free_devres:
+ devres_free(prom21_auxdev);
+ return ret;
+}
+
+static void prom21_xhci_destroy_auxdev(struct pci_dev *pdev)
+{
+ devres_release(&pdev->dev, prom21_xhci_auxdev_release, NULL, NULL);
+}
+
+static int prom21_xhci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ int retval;
+
+ retval = xhci_pci_common_probe(dev, id);
+ if (retval)
+ return retval;
+
+ retval = prom21_xhci_create_auxdev(dev);
+ if (retval) {
+ /*
+ * The auxiliary device only provides optional temperature sensor
+ * support. Keep the xHCI controller usable if it fails.
+ */
+ dev_err(&dev->dev,
+ "failed to create PROM21 hwmon auxiliary device: %d\n",
+ retval);
+ }
+
+ return 0;
+}
+
+static void prom21_xhci_remove(struct pci_dev *dev)
+{
+ prom21_xhci_destroy_auxdev(dev);
+ xhci_pci_remove(dev);
+}
+
+static const struct pci_device_id pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) },
+ { /* end: all zeroes */ }
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver prom21_xhci_driver = {
+ .name = "xhci-pci-prom21",
+ .id_table = pci_ids,
+
+ .probe = prom21_xhci_probe,
+ .remove = prom21_xhci_remove,
+
+ .shutdown = usb_hcd_pci_shutdown,
+ .driver = {
+ .pm = pm_ptr(&usb_hcd_pci_pm_ops),
+ },
+};
+module_pci_driver(prom21_xhci_driver);
+
+MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
+MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver");
+MODULE_IMPORT_NS("xhci");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 585b2f3117b0..039c26b241d0 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -696,12 +696,23 @@ static const struct pci_device_id pci_ids_renesas[] = {
{ /* end: all zeroes */ }
};
+/* handled by xhci-pci-prom21 if enabled */
+static const struct pci_device_id pci_ids_prom21[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) },
+ { /* end: all zeroes */ }
+};
+
static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
if (IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) &&
pci_match_id(pci_ids_renesas, dev))
return -ENODEV;
+ if (IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21) &&
+ pci_match_id(pci_ids_prom21, dev))
+ return -ENODEV;
+
return xhci_pci_common_probe(dev, id);
}
diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h
index e87c7d9d76b8..11f435f94322 100644
--- a/drivers/usb/host/xhci-pci.h
+++ b/drivers/usb/host/xhci-pci.h
@@ -4,6 +4,9 @@
#ifndef XHCI_PCI_H
#define XHCI_PCI_H
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC 0x43fc
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD 0x43fd
+
int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id);
void xhci_pci_remove(struct pci_dev *dev);
diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c
index 8a993ee21c87..d747c0905827 100644
--- a/drivers/usb/host/xhci-rcar.c
+++ b/drivers/usb/host/xhci-rcar.c
@@ -32,29 +32,6 @@
MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1);
MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3);
-static void xhci_rcar_start_gen2(struct usb_hcd *hcd)
-{
- /* LCLK Select */
- writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK);
- /* USB3.0 Configuration */
- writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1);
- writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2);
- writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3);
- /* USB3.0 Polarity */
- writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL);
- writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL);
-}
-
-static int xhci_rcar_is_gen2(struct device *dev)
-{
- struct device_node *node = dev->of_node;
-
- return of_device_is_compatible(node, "renesas,xhci-r8a7790") ||
- of_device_is_compatible(node, "renesas,xhci-r8a7791") ||
- of_device_is_compatible(node, "renesas,xhci-r8a7793") ||
- of_device_is_compatible(node, "renesas,rcar-gen2-xhci");
-}
-
static void xhci_rcar_start(struct usb_hcd *hcd)
{
u32 temp;
@@ -64,8 +41,23 @@ static void xhci_rcar_start(struct usb_hcd *hcd)
temp = readl(hcd->regs + RCAR_USB3_INT_ENA);
temp |= RCAR_USB3_INT_ENA_VAL;
writel(temp, hcd->regs + RCAR_USB3_INT_ENA);
- if (xhci_rcar_is_gen2(hcd->self.controller))
- xhci_rcar_start_gen2(hcd);
+ }
+}
+
+static void xhci_rcar_gen2_start(struct usb_hcd *hcd)
+{
+ if (hcd->regs != NULL) {
+ xhci_rcar_start(hcd);
+
+ /* LCLK Select */
+ writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK);
+ /* USB3.0 Configuration */
+ writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1);
+ writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2);
+ writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3);
+ /* USB3.0 Polarity */
+ writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL);
+ writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL);
}
}
@@ -192,13 +184,16 @@ static int xhci_rcar_init_quirk(struct usb_hcd *hcd)
static int xhci_rcar_resume_quirk(struct usb_hcd *hcd)
{
+ struct xhci_plat_priv *priv;
int ret;
ret = xhci_rcar_download_firmware(hcd);
- if (!ret)
- xhci_rcar_start(hcd);
+ if (ret)
+ return ret;
- return ret;
+ priv = hcd_to_xhci_priv(hcd);
+ priv->plat_start(hcd);
+ return 0;
}
/*
@@ -213,19 +208,20 @@ static int xhci_rcar_resume_quirk(struct usb_hcd *hcd)
* long delay for the handshake of STS_HALT is neeed in xhci_suspend()
* by using the XHCI_SLOW_SUSPEND quirk.
*/
-#define SET_XHCI_PLAT_PRIV_FOR_RCAR(firmware) \
- .firmware_name = firmware, \
- .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, \
- .init_quirk = xhci_rcar_init_quirk, \
- .plat_start = xhci_rcar_start, \
- .resume_quirk = xhci_rcar_resume_quirk,
-
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = {
- SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V1)
+ .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1,
+ .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND,
+ .init_quirk = xhci_rcar_init_quirk,
+ .plat_start = xhci_rcar_gen2_start,
+ .resume_quirk = xhci_rcar_resume_quirk,
};
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = {
- SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3)
+ .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V3,
+ .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND,
+ .init_quirk = xhci_rcar_init_quirk,
+ .plat_start = xhci_rcar_start,
+ .resume_quirk = xhci_rcar_resume_quirk,
};
static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = {
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index e47e644b296e..4f98d8269625 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -340,7 +340,7 @@ static bool trb_in_td(struct xhci_td *td, dma_addr_t suspect_dma)
* Only for transfer and command rings where driver is the producer, not for
* event rings.
*/
-static unsigned int xhci_num_trbs_free(struct xhci_ring *ring)
+unsigned int xhci_num_trbs_free(struct xhci_ring *ring)
{
struct xhci_segment *enq_seg = ring->enq_seg;
union xhci_trb *enq = ring->enqueue;
@@ -609,7 +609,7 @@ static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci,
unsigned int slot_id,
unsigned int ep_index)
{
- if (slot_id == 0 || slot_id >= MAX_HC_SLOTS) {
+ if (slot_id == 0 || slot_id > xhci->max_slots) {
xhci_warn(xhci, "Invalid slot_id %u\n", slot_id);
return NULL;
}
@@ -1613,7 +1613,7 @@ static void xhci_handle_cmd_disable_slot(struct xhci_hcd *xhci, int slot_id,
/* Delete default control endpoint resources */
xhci_free_device_endpoint_resources(xhci, virt_dev, true);
if (cmd_comp_code == COMP_SUCCESS) {
- xhci->dcbaa->dev_context_ptrs[slot_id] = 0;
+ xhci->dcbaa.ctx_array[slot_id] = 0;
xhci->devs[slot_id] = NULL;
}
}
@@ -1804,7 +1804,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
struct xhci_command *cmd;
u32 cmd_type;
- if (slot_id >= MAX_HC_SLOTS) {
+ if (slot_id > xhci->max_slots) {
xhci_warn(xhci, "Invalid slot_id %u\n", slot_id);
return;
}
@@ -2395,7 +2395,6 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
u32 trb_comp_code;
bool sum_trbs_for_length = false;
u32 remaining, requested, ep_trb_len;
- int short_framestatus;
trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len));
urb_priv = td->urb->hcpriv;
@@ -2404,8 +2403,6 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
requested = frame->length;
remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len));
ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2]));
- short_framestatus = td->urb->transfer_flags & URB_SHORT_NOT_OK ?
- -EREMOTEIO : 0;
/* handle completion code */
switch (trb_comp_code) {
@@ -2413,15 +2410,12 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
/* Don't overwrite status if TD had an error, see xHCI 4.9.1 */
if (td->error_mid_td)
break;
- if (remaining) {
- frame->status = short_framestatus;
+ if (remaining)
sum_trbs_for_length = true;
- break;
- }
frame->status = 0;
break;
case COMP_SHORT_PACKET:
- frame->status = short_framestatus;
+ frame->status = 0;
sum_trbs_for_length = true;
break;
case COMP_BANDWIDTH_OVERRUN_ERROR:
@@ -2456,7 +2450,7 @@ static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
break;
case COMP_STOPPED_SHORT_PACKET:
/* field normally containing residue now contains transferred */
- frame->status = short_framestatus;
+ frame->status = 0;
requested = remaining;
break;
case COMP_STOPPED_LENGTH_INVALID:
@@ -2492,26 +2486,6 @@ finish_td:
finish_td(xhci, ep, ep_ring, td, trb_comp_code);
}
-static void skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td,
- struct xhci_virt_ep *ep, int status)
-{
- struct urb_priv *urb_priv;
- struct usb_iso_packet_descriptor *frame;
- int idx;
-
- urb_priv = td->urb->hcpriv;
- idx = urb_priv->num_tds_done;
- frame = &td->urb->iso_frame_desc[idx];
-
- /* The transfer is partly done. */
- frame->status = -EXDEV;
-
- /* calc actual length */
- frame->actual_length = 0;
-
- xhci_dequeue_td(xhci, td, ep->ring, status);
-}
-
/*
* Process bulk and interrupt tds, update urb status and actual_length.
*/
@@ -2542,6 +2516,7 @@ static void process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
td->status = 0;
break;
case COMP_SHORT_PACKET:
+ ep->err_count = 0;
td->status = 0;
break;
case COMP_STOPPED_SHORT_PACKET:
@@ -2854,7 +2829,11 @@ static int handle_tx_event(struct xhci_hcd *xhci,
if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID)
return 0;
- skip_isoc_td(xhci, td, ep, status);
+ /*
+ * TD was missed, skip it. Core already initialized frame->status
+ * to -EXDEV and frame->actual_length to 0, nothing more to do.
+ */
+ xhci_dequeue_td(xhci, td, ep_ring, 0);
if (!list_empty(&ep_ring->td_list)) {
if (ring_xrun_event) {
@@ -4353,6 +4332,7 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
u32 field3, u32 field4, bool command_must_succeed)
{
int reserved_trbs = xhci->cmd_ring_reserved_trbs;
+ struct usb_hcd *hcd = xhci_to_hcd(xhci);
int ret;
if ((xhci->xhc_state & XHCI_STATE_DYING) ||
@@ -4362,6 +4342,11 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
return -ESHUTDOWN;
}
+ if (!HCD_HW_ACCESSIBLE(hcd)) {
+ xhci_warn(xhci, "Can't queue command, xHC not accessible\n");
+ return -ESHUTDOWN;
+ }
+
if (!command_must_succeed)
reserved_trbs++;
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index a54f5b57f205..6922cc5496c1 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -102,17 +102,10 @@ int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us)
*/
void xhci_quiesce(struct xhci_hcd *xhci)
{
- u32 halted;
u32 cmd;
- u32 mask;
-
- mask = ~(XHCI_IRQS);
- halted = readl(&xhci->op_regs->status) & STS_HALT;
- if (!halted)
- mask &= ~CMD_RUN;
cmd = readl(&xhci->op_regs->command);
- cmd &= mask;
+ cmd &= ~(CMD_RUN | XHCI_IRQS);
writel(cmd, &xhci->op_regs->command);
}
@@ -558,7 +551,7 @@ static void xhci_init(struct usb_hcd *hcd)
xhci_set_cmd_ring_deq(xhci);
/* Set Device Context Base Address Array pointer */
- xhci_write_64(xhci, xhci->dcbaa->dma, &xhci->op_regs->dcbaa_ptr);
+ xhci_write_64(xhci, xhci->dcbaa.dma, &xhci->op_regs->dcbaa_ptr);
/* Set Doorbell array pointer */
xhci_set_doorbell_ptr(xhci);
@@ -999,6 +992,10 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
/* step 1: stop endpoint */
/* skipped assuming that port suspend has done */
+ /* Check if command ring is empty */
+ if (!list_empty(&xhci->cmd_list))
+ xhci_warn(xhci, "Suspending and stopping xHC with pending command!\n");
+
/* step 2: clear Run/Stop bit */
command = readl(&xhci->op_regs->command);
command &= ~CMD_RUN;
@@ -4460,9 +4457,9 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
xhci_dbg_trace(xhci, trace_xhci_dbg_address,
"Slot ID %d dcbaa entry @%p = %#016llx",
udev->slot_id,
- &xhci->dcbaa->dev_context_ptrs[udev->slot_id],
+ &xhci->dcbaa.ctx_array[udev->slot_id],
(unsigned long long)
- le64_to_cpu(xhci->dcbaa->dev_context_ptrs[udev->slot_id]));
+ le64_to_cpu(xhci->dcbaa.ctx_array[udev->slot_id]));
xhci_dbg_trace(xhci, trace_xhci_dbg_address,
"Output Context DMA address = %#08llx",
(unsigned long long)virt_dev->out_ctx->dma);
@@ -5460,7 +5457,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
if (xhci->hci_version > 0x100)
xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2);
- xhci->max_slots = HCS_MAX_SLOTS(hcs_params1);
+ xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS);
xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS);
/* xhci-plat or xhci-pci might have set max_interrupters already */
if (!xhci->max_interrupters)
@@ -5533,8 +5530,6 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
init_completion(&xhci->cmd_ring_stop_completion);
xhci_hcd_page_size(xhci);
- memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs));
-
/* Allocate xHCI data structures */
retval = xhci_mem_init(xhci, GFP_KERNEL);
if (retval)
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index aeecd301f207..d02046a573e4 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -33,8 +33,11 @@
/* xHCI PCI Configuration Registers */
#define XHCI_SBRN_OFFSET (0x60)
-/* Max number of USB devices for any host controller - limit in section 6.1 */
-#define MAX_HC_SLOTS 256
+/*
+ * Max number of Devices Slots. xHCI specification section 5.3.3
+ * Valid values are in the range of 1 to 255.
+ */
+#define MAX_HC_SLOTS 255
/*
* Max Number of Ports. xHCI specification section 5.3.3
* Valid values are in the range of 1 to 255.
@@ -792,18 +795,18 @@ struct xhci_tt_bw_info {
/**
* struct xhci_device_context_array
- * @dev_context_ptr array of 64-bit DMA addresses for device contexts
+ * @ctx_array: Pointer to an array of addresses. The array size depends on Max
+ * Slots read from HCSPARAMS1.
+ * @dma: DMA address to @ctx_array
+ *
+ * Device Context Base Address Array (DCBAA) - Section 6.1.
+ * ctx_array[0]: Scratchpad Buffer Array Base Address
+ * ctx_array[1-MaxSlots]: Device Context Base Address
*/
struct xhci_device_context_array {
- /* 64-bit device addresses; we only write 32-bit addresses */
- __le64 dev_context_ptrs[MAX_HC_SLOTS];
- /* private xHCD pointers */
+ __le64 *ctx_array;
dma_addr_t dma;
};
-/*
- * TODO: change this to be dynamically sized at HC mem init time since the HC
- * might not be able to handle the maximum number of devices possible.
- */
struct xhci_transfer_event {
@@ -1376,7 +1379,6 @@ struct xhci_ring {
u32 cycle_state;
unsigned int stream_id;
unsigned int num_segs;
- unsigned int num_trbs_free; /* used only by xhci DbC */
unsigned int bounce_buf_len;
enum xhci_ring_type type;
u32 old_trb_comp_code;
@@ -1532,7 +1534,7 @@ struct xhci_hcd {
/* optional reset controller */
struct reset_control *reset;
/* data structures */
- struct xhci_device_context_array *dcbaa;
+ struct xhci_device_context_array dcbaa;
struct xhci_interrupter **interrupters;
struct xhci_ring *cmd_ring;
unsigned int cmd_ring_state;
@@ -1552,7 +1554,7 @@ struct xhci_hcd {
/* these are not thread safe so use mutex */
struct mutex mutex;
/* Internal mirror of the HW's dcbaa */
- struct xhci_virt_device *devs[MAX_HC_SLOTS];
+ struct xhci_virt_device **devs;
/* For keeping track of bandwidth domains per roothub. */
struct xhci_root_port_bw_info *rh_bw;
@@ -1955,6 +1957,7 @@ void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
void xhci_cleanup_command_queue(struct xhci_hcd *xhci);
void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring);
unsigned int count_trbs(u64 addr, u64 len);
+unsigned int xhci_num_trbs_free(struct xhci_ring *ring);
int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
int suspend, gfp_t gfp_flags);
void xhci_process_cancelled_tds(struct xhci_virt_ep *ep);
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 0b56b773dbdf..91b62acb537b 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -145,7 +145,7 @@ config USB_APPLEDISPLAY
Displays over USB. This driver provides a sysfs interface.
config USB_QCOM_EUD
- tristate "QCOM Embedded USB Debugger(EUD) Driver"
+ tristate "Qualcomm Embedded USB Debugger(EUD) Driver"
depends on ARCH_QCOM || COMPILE_TEST
select QCOM_SCM
select USB_ROLE_SWITCH
diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c
index 7cdbdfe07a76..11508ed4df25 100644
--- a/drivers/usb/misc/onboard_usb_dev.c
+++ b/drivers/usb/misc/onboard_usb_dev.c
@@ -665,6 +665,7 @@ static const struct usb_device_id onboard_dev_id_table[] = {
{ USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 HUB */
{ USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 HUB */
{ USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 HUB */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0625) }, /* Genesys Logic GL3590 USB 3.2 HUB */
{ USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 HUB */
{ USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 HUB */
{ USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 HUB */
diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h
index ac1aa3e122ad..3523f8f8a149 100644
--- a/drivers/usb/misc/onboard_usb_dev.h
+++ b/drivers/usb/misc/onboard_usb_dev.h
@@ -101,6 +101,13 @@ static const struct onboard_dev_pdata genesys_gl852g_data = {
.is_hub = true,
};
+static const struct onboard_dev_pdata genesys_gl3590_data = {
+ .reset_us = 50,
+ .num_supplies = 2,
+ .supply_names = { "vdd", "vdd12" },
+ .is_hub = true,
+};
+
static const struct onboard_dev_pdata usb_a_conn_data = {
.num_supplies = 1,
.supply_names = { "vbus" },
@@ -146,6 +153,7 @@ static const struct of_device_id onboard_dev_match[] = {
{ .compatible = "usb5e3,608", .data = &genesys_gl850g_data, },
{ .compatible = "usb5e3,610", .data = &genesys_gl852g_data, },
{ .compatible = "usb5e3,620", .data = &genesys_gl852g_data, },
+ { .compatible = "usb5e3,625", .data = &genesys_gl3590_data, },
{ .compatible = "usb5e3,626", .data = &genesys_gl852g_data, },
{ .compatible = "usbbda,179", .data = &realtek_rtl8188etv_data, },
{ .compatible = "usbbda,411", .data = &realtek_rts5411_data, },
diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c
index 7c0778631bea..fb0693742f01 100644
--- a/drivers/usb/misc/usb251xb.c
+++ b/drivers/usb/misc/usb251xb.c
@@ -746,15 +746,15 @@ static int usb251xb_i2c_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume);
static const struct i2c_device_id usb251xb_id[] = {
- { "usb2422" },
- { "usb2512b" },
- { "usb2512bi" },
- { "usb2513b" },
- { "usb2513bi" },
- { "usb2514b" },
- { "usb2514bi" },
- { "usb2517" },
- { "usb2517i" },
+ { .name = "usb2422" },
+ { .name = "usb2512b" },
+ { .name = "usb2512bi" },
+ { .name = "usb2513b" },
+ { .name = "usb2513bi" },
+ { .name = "usb2514b" },
+ { .name = "usb2514bi" },
+ { .name = "usb2517" },
+ { .name = "usb2517i" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, usb251xb_id);
diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c
index 322e59381b78..759770a13260 100644
--- a/drivers/usb/misc/usb3503.c
+++ b/drivers/usb/misc/usb3503.c
@@ -390,7 +390,7 @@ static SIMPLE_DEV_PM_OPS(usb3503_platform_pm_ops, usb3503_platform_suspend,
usb3503_platform_resume);
static const struct i2c_device_id usb3503_id[] = {
- { USB3503_I2C_NAME },
+ { .name = USB3503_I2C_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, usb3503_id);
diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c
index c9a2fb3518ae..2ae9656715e0 100644
--- a/drivers/usb/misc/usb4604.c
+++ b/drivers/usb/misc/usb4604.c
@@ -135,7 +135,7 @@ static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend,
usb4604_i2c_resume);
static const struct i2c_device_id usb4604_id[] = {
- { "usb4604" },
+ { .name = "usb4604" },
{ }
};
MODULE_DEVICE_TABLE(i2c, usb4604_id);
diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c
index 2940f0c84e1b..73cc70e958de 100644
--- a/drivers/usb/phy/phy-isp1301.c
+++ b/drivers/usb/phy/phy-isp1301.c
@@ -25,7 +25,7 @@ struct isp1301 {
#define phy_to_isp(p) (container_of((p), struct isp1301, phy))
static const struct i2c_device_id isp1301_id[] = {
- { "isp1301" },
+ { .name = "isp1301" },
{ }
};
MODULE_DEVICE_TABLE(i2c, isp1301_id);
diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c
index d974da43fba3..3a574b6ee0c4 100644
--- a/drivers/usb/serial/ark3116.c
+++ b/drivers/usb/serial/ark3116.c
@@ -28,7 +28,6 @@
#include <linux/usb/serial.h>
#include <linux/serial.h>
#include <linux/serial_reg.h>
-#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c
index 7bbd9523d4e9..cfff292631aa 100644
--- a/drivers/usb/serial/belkin_sa.c
+++ b/drivers/usb/serial/belkin_sa.c
@@ -23,11 +23,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include "belkin_sa.h"
diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c
index 4e8ceb23c27d..57e20eb02a46 100644
--- a/drivers/usb/serial/cyberjack.c
+++ b/drivers/usb/serial/cyberjack.c
@@ -28,11 +28,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c
index bcf302e88ca4..78fb1389b34c 100644
--- a/drivers/usb/serial/cypress_m8.c
+++ b/drivers/usb/serial/cypress_m8.c
@@ -25,7 +25,6 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -35,7 +34,6 @@
#include <linux/serial.h>
#include <linux/kfifo.h>
#include <linux/delay.h>
-#include <linux/uaccess.h>
#include <linux/unaligned.h>
#include "cypress_m8.h"
diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c
index c481208255eb..6899aebfd6ae 100644
--- a/drivers/usb/serial/digi_acceleport.c
+++ b/drivers/usb/serial/digi_acceleport.c
@@ -15,11 +15,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/wait.h>
#include <linux/sched/signal.h>
diff --git a/drivers/usb/serial/empeg.c b/drivers/usb/serial/empeg.c
index aedcf7ebd269..4937575caff1 100644
--- a/drivers/usb/serial/empeg.c
+++ b/drivers/usb/serial/empeg.c
@@ -16,11 +16,8 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c
index 9262a2ac97f5..dcceb95d7d99 100644
--- a/drivers/usb/serial/f81232.c
+++ b/drivers/usb/serial/f81232.c
@@ -11,13 +11,10 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/module.h>
-#include <linux/moduleparam.h>
#include <linux/mutex.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial_reg.h>
diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c
index 685930ac8383..0037767e4bbf 100644
--- a/drivers/usb/serial/f81534.c
+++ b/drivers/usb/serial/f81534.c
@@ -28,7 +28,6 @@
#include <linux/usb/serial.h>
#include <linux/serial_reg.h>
#include <linux/module.h>
-#include <linux/uaccess.h>
/* Serial Port register Address */
#define F81534_UART_BASE_ADDRESS 0x1200
diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c
index af14548fa03d..88dd32da82c2 100644
--- a/drivers/usb/serial/ftdi_sio.c
+++ b/drivers/usb/serial/ftdi_sio.c
@@ -31,7 +31,6 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c
index 7205483a0115..8020149f5658 100644
--- a/drivers/usb/serial/garmin_gps.c
+++ b/drivers/usb/serial/garmin_gps.c
@@ -15,12 +15,9 @@
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
-#include <linux/atomic.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c
index 757f0a586ddb..6eaf74930aa3 100644
--- a/drivers/usb/serial/generic.c
+++ b/drivers/usb/serial/generic.c
@@ -17,7 +17,6 @@
#include <linux/moduleparam.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
#include <linux/kfifo.h>
#include <linux/serial.h>
diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c
index 3f5889145e51..34ccf7820537 100644
--- a/drivers/usb/serial/io_edgeport.c
+++ b/drivers/usb/serial/io_edgeport.c
@@ -25,12 +25,12 @@
*
*/
+#include <linux/atomic.h>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c
index d48819d785ef..07c0eff3bef4 100644
--- a/drivers/usb/serial/io_ti.c
+++ b/drivers/usb/serial/io_ti.c
@@ -18,7 +18,6 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
@@ -28,7 +27,6 @@
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/firmware.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c
index 3c6a9b9b9c2b..c01bdbcb3f55 100644
--- a/drivers/usb/serial/ipaq.c
+++ b/drivers/usb/serial/ipaq.c
@@ -10,11 +10,8 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/ipw.c b/drivers/usb/serial/ipw.c
index 83709d678b3a..5be153f2c68d 100644
--- a/drivers/usb/serial/ipw.c
+++ b/drivers/usb/serial/ipw.c
@@ -36,12 +36,10 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
#include "usb-wwan.h"
#define DRIVER_AUTHOR "Roelf Diedericks"
diff --git a/drivers/usb/serial/ir-usb.c b/drivers/usb/serial/ir-usb.c
index 12e928d25ba1..aac84539730d 100644
--- a/drivers/usb/serial/ir-usb.c
+++ b/drivers/usb/serial/ir-usb.c
@@ -25,11 +25,9 @@
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/usb/irda.h>
diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c
index 0ca111b111c7..3b1f7546fb5c 100644
--- a/drivers/usb/serial/iuu_phoenix.c
+++ b/drivers/usb/serial/iuu_phoenix.c
@@ -14,13 +14,11 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include "iuu_phoenix.h"
diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c
index 28b80607cebd..4d3746c7a94e 100644
--- a/drivers/usb/serial/keyspan.c
+++ b/drivers/usb/serial/keyspan.c
@@ -29,11 +29,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/usb/ezusb.h>
diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c
index 5036600dd334..3b99f9676c35 100644
--- a/drivers/usb/serial/keyspan_pda.c
+++ b/drivers/usb/serial/keyspan_pda.c
@@ -15,12 +15,10 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/usb/ezusb.h>
diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c
index e72a0b45a707..b74c88fec03a 100644
--- a/drivers/usb/serial/kl5kusb105.c
+++ b/drivers/usb/serial/kl5kusb105.c
@@ -35,10 +35,8 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
-#include <linux/uaccess.h>
#include <linux/unaligned.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c
index 6126afd67a7b..4a622cf8e6ae 100644
--- a/drivers/usb/serial/kobil_sct.c
+++ b/drivers/usb/serial/kobil_sct.c
@@ -23,11 +23,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/ioctl.h>
diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c
index 163161881d2d..a6de18917ec9 100644
--- a/drivers/usb/serial/mct_u232.c
+++ b/drivers/usb/serial/mct_u232.c
@@ -21,11 +21,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/unaligned.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c
index bc834cc48550..35473544f1c8 100644
--- a/drivers/usb/serial/metro-usb.c
+++ b/drivers/usb/serial/metro-usb.c
@@ -13,11 +13,8 @@
#include <linux/usb.h>
#include <linux/errno.h>
#include <linux/slab.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
-#include <linux/moduleparam.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb/serial.h>
#define DRIVER_DESC "Metrologic Instruments Inc. - USB-POS driver"
diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c
index 94459408e7fb..104f43a8f4d8 100644
--- a/drivers/usb/serial/mos7720.c
+++ b/drivers/usb/serial/mos7720.c
@@ -17,11 +17,12 @@
* Copyright (C) 2000 Inside Out Networks, All rights reserved.
* Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com>
*/
+
+#include <linux/atomic.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c
index c0d0c24b074b..ef803e74ab4b 100644
--- a/drivers/usb/serial/mos7840.c
+++ b/drivers/usb/serial/mos7840.c
@@ -13,7 +13,6 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/serial.h>
diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c
index c9b9928c473a..e3c5a1b97542 100644
--- a/drivers/usb/serial/mxuport.c
+++ b/drivers/usb/serial/mxuport.c
@@ -20,9 +20,7 @@
#include <linux/serial_reg.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/unaligned.h>
@@ -146,11 +144,6 @@
#define MX_WAIT_FOR_LOW_WATER 0x0040
#define MX_WAIT_FOR_SEND_NEXT 0x0080
-#define MX_UPORT_2_PORT BIT(0)
-#define MX_UPORT_4_PORT BIT(1)
-#define MX_UPORT_8_PORT BIT(2)
-#define MX_UPORT_16_PORT BIT(3)
-
/* This structure holds all of the local port information */
struct mxuport_port {
u8 mcr_state; /* Last MCR state */
@@ -159,26 +152,31 @@ struct mxuport_port {
spinlock_t spinlock; /* Protects msr_state */
};
+/* Encode number of ports (2..16 or undefined) */
+#define MX_PORTS_MASK GENMASK(3, 0)
+#define MX_PORTS_OFFSET 1
+#define MX_PORTS(n) (((n) - MX_PORTS_OFFSET) & MX_PORTS_MASK)
+
/* Table of devices that work with this driver */
static const struct usb_device_id mxuport_idtable[] = {
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1250_PID),
- .driver_info = MX_UPORT_2_PORT },
+ .driver_info = MX_PORTS(2) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1251_PID),
- .driver_info = MX_UPORT_2_PORT },
+ .driver_info = MX_PORTS(2) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1410_PID),
- .driver_info = MX_UPORT_4_PORT },
+ .driver_info = MX_PORTS(4) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1450_PID),
- .driver_info = MX_UPORT_4_PORT },
+ .driver_info = MX_PORTS(4) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1451_PID),
- .driver_info = MX_UPORT_4_PORT },
+ .driver_info = MX_PORTS(4) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1618_PID),
- .driver_info = MX_UPORT_8_PORT },
+ .driver_info = MX_PORTS(8) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1658_PID),
- .driver_info = MX_UPORT_8_PORT },
+ .driver_info = MX_PORTS(8) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1613_PID),
- .driver_info = MX_UPORT_16_PORT },
+ .driver_info = MX_PORTS(16) },
{ USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1653_PID),
- .driver_info = MX_UPORT_16_PORT },
+ .driver_info = MX_PORTS(16) },
{} /* Terminating entry */
};
@@ -942,14 +940,8 @@ static int mxuport_calc_num_ports(struct usb_serial *serial,
int num_ports;
int i;
- if (features & MX_UPORT_2_PORT) {
- num_ports = 2;
- } else if (features & MX_UPORT_4_PORT) {
- num_ports = 4;
- } else if (features & MX_UPORT_8_PORT) {
- num_ports = 8;
- } else if (features & MX_UPORT_16_PORT) {
- num_ports = 16;
+ if (features & MX_PORTS_MASK) {
+ num_ports = (features & MX_PORTS_MASK) + MX_PORTS_OFFSET;
} else {
dev_warn(&serial->interface->dev,
"unknown device, assuming two ports\n");
diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c
index b59982ed8b25..363b8432cc61 100644
--- a/drivers/usb/serial/omninet.c
+++ b/drivers/usb/serial/omninet.c
@@ -14,10 +14,8 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c
index e2bed477ad57..152dfc901cd8 100644
--- a/drivers/usb/serial/opticon.c
+++ b/drivers/usb/serial/opticon.c
@@ -10,14 +10,12 @@
#include <linux/kernel.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/slab.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
#define CONTROL_RTS 0x02
#define RESEND_CTS_STATE 0x03
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index a34e79cfd5b6..4c4009b8a46d 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -32,7 +32,6 @@
#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/tty.h>
-#include <linux/tty_flip.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/bitops.h>
diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c
index 3ef5b5d8ce1a..c6364e8ff71f 100644
--- a/drivers/usb/serial/oti6858.c
+++ b/drivers/usb/serial/oti6858.c
@@ -38,15 +38,12 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/module.h>
-#include <linux/moduleparam.h>
#include <linux/spinlock.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
#include <linux/kfifo.h>
#include "oti6858.h"
diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c
index 50dc838e8115..0bcbdcea52af 100644
--- a/drivers/usb/serial/pl2303.c
+++ b/drivers/usb/serial/pl2303.c
@@ -15,13 +15,10 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/module.h>
-#include <linux/moduleparam.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/unaligned.h>
diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c
index 1a930dc668e4..105c0a0eae86 100644
--- a/drivers/usb/serial/qcserial.c
+++ b/drivers/usb/serial/qcserial.c
@@ -8,7 +8,6 @@
*/
#include <linux/tty.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c
index b05c655b3cc4..dc2b39810a26 100644
--- a/drivers/usb/serial/quatech2.c
+++ b/drivers/usb/serial/quatech2.c
@@ -13,14 +13,12 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/serial.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial_reg.h>
-#include <linux/uaccess.h>
/* default urb timeout for usb operations */
#define QT2_USB_TIMEOUT USB_CTRL_SET_TIMEOUT
diff --git a/drivers/usb/serial/safe_serial.c b/drivers/usb/serial/safe_serial.c
index d267a31dcccf..55e59e341732 100644
--- a/drivers/usb/serial/safe_serial.c
+++ b/drivers/usb/serial/safe_serial.c
@@ -64,11 +64,9 @@
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c
index 2fbb48f19efd..c11d64bf08fb 100644
--- a/drivers/usb/serial/spcp8x5.c
+++ b/drivers/usb/serial/spcp8x5.c
@@ -14,8 +14,6 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/usb.h>
diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c
index da6316410b77..b0d51558b73c 100644
--- a/drivers/usb/serial/ssu100.c
+++ b/drivers/usb/serial/ssu100.c
@@ -9,14 +9,12 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/serial.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial_reg.h>
-#include <linux/uaccess.h>
#define QT_OPEN_CLOSE_CHANNEL 0xca
#define QT_SET_GET_DEVICE 0xc2
diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c
index 7a5aa39172a8..8823671a9c7a 100644
--- a/drivers/usb/serial/symbolserial.c
+++ b/drivers/usb/serial/symbolserial.c
@@ -10,12 +10,10 @@
#include <linux/kernel.h>
#include <linux/tty.h>
#include <linux/slab.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x05e0, 0x0600) },
diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c
index b3591d6d7645..b3f00e315815 100644
--- a/drivers/usb/serial/ti_usb_3410_5052.c
+++ b/drivers/usb/serial/ti_usb_3410_5052.c
@@ -18,7 +18,6 @@
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
@@ -26,7 +25,6 @@
#include <linux/serial.h>
#include <linux/kfifo.h>
#include <linux/mutex.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
index 0e072fd87c3d..17edc057a311 100644
--- a/drivers/usb/serial/usb-serial.c
+++ b/drivers/usb/serial/usb-serial.c
@@ -22,14 +22,11 @@
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
-#include <linux/moduleparam.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/list.h>
-#include <linux/uaccess.h>
#include <linux/serial.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c
index e752ffa4dc62..a183cc4515ed 100644
--- a/drivers/usb/serial/usb_wwan.c
+++ b/drivers/usb/serial/usb_wwan.c
@@ -27,7 +27,6 @@
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/bitops.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c
index 062a38fe0c1c..5706c46c49c2 100644
--- a/drivers/usb/serial/visor.c
+++ b/drivers/usb/serial/visor.c
@@ -15,12 +15,8 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
-#include <linux/moduleparam.h>
#include <linux/spinlock.h>
-#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/usb/cdc.h>
diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c
index 6e2b52b2b5f8..a2d83f69e00e 100644
--- a/drivers/usb/serial/whiteheat.c
+++ b/drivers/usb/serial/whiteheat.c
@@ -16,13 +16,9 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
-#include <linux/tty_driver.h>
-#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
-#include <linux/uaccess.h>
-#include <asm/termbits.h>
#include <linux/usb.h>
#include <linux/serial_reg.h>
#include <linux/serial.h>
diff --git a/drivers/usb/serial/wishbone-serial.c b/drivers/usb/serial/wishbone-serial.c
index 670d573f6b63..4a1fed4cb57b 100644
--- a/drivers/usb/serial/wishbone-serial.c
+++ b/drivers/usb/serial/wishbone-serial.c
@@ -11,7 +11,6 @@
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
#define GSI_VENDOR_OPENCLOSE 0xB0
diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c
index 9fc4082de770..352c765d8803 100644
--- a/drivers/usb/serial/xr_serial.c
+++ b/drivers/usb/serial/xr_serial.c
@@ -16,6 +16,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/tty.h>
+#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/serial.h>
diff --git a/drivers/usb/serial/xsens_mt.c b/drivers/usb/serial/xsens_mt.c
index 382b3698c1d5..151a6e893ba2 100644
--- a/drivers/usb/serial/xsens_mt.c
+++ b/drivers/usb/serial/xsens_mt.c
@@ -10,7 +10,6 @@
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
-#include <linux/uaccess.h>
#define XSENS_VID 0x2639
diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c
index 2e8ae1d2faf9..604868ebf422 100644
--- a/drivers/usb/typec/anx7411.c
+++ b/drivers/usb/typec/anx7411.c
@@ -1577,8 +1577,8 @@ static void anx7411_i2c_remove(struct i2c_client *client)
}
static const struct i2c_device_id anx7411_id[] = {
- { "anx7411" },
- {}
+ { .name = "anx7411" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, anx7411_id);
diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c
index c54e42c7e6a1..bea0c1deec94 100644
--- a/drivers/usb/typec/mux/fsa4480.c
+++ b/drivers/usb/typec/mux/fsa4480.c
@@ -336,7 +336,7 @@ static void fsa4480_remove(struct i2c_client *client)
}
static const struct i2c_device_id fsa4480_table[] = {
- { "fsa4480" },
+ { .name = "fsa4480" },
{ }
};
MODULE_DEVICE_TABLE(i2c, fsa4480_table);
diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c
index 1698428654ab..219a32da1348 100644
--- a/drivers/usb/typec/mux/intel_pmc_mux.c
+++ b/drivers/usb/typec/mux/intel_pmc_mux.c
@@ -151,13 +151,14 @@ struct pmc_usb {
u8 num_ports;
struct device *dev;
struct intel_scu_ipc_dev *ipc;
- struct pmc_usb_port *port;
struct acpi_device *iom_adev;
void __iomem *iom_base;
u32 iom_port_status_offset;
u8 iom_port_status_size;
struct dentry *dentry;
+
+ struct pmc_usb_port port[] __counted_by(num_ports);
};
static struct dentry *pmc_mux_debugfs_root;
@@ -731,27 +732,25 @@ static int pmc_usb_probe(struct platform_device *pdev)
{
struct fwnode_handle *fwnode = NULL;
struct pmc_usb *pmc;
+ u8 num_ports = 0;
int i = 0;
int ret;
- pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL);
- if (!pmc)
- return -ENOMEM;
-
device_for_each_child_node(&pdev->dev, fwnode)
- pmc->num_ports++;
+ num_ports++;
/* The IOM microcontroller has a limitation of max 4 ports. */
- if (pmc->num_ports > 4) {
+ if (num_ports > 4) {
dev_err(&pdev->dev, "driver limited to 4 ports\n");
return -ERANGE;
}
- pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports,
- sizeof(struct pmc_usb_port), GFP_KERNEL);
- if (!pmc->port)
+ pmc = devm_kzalloc(&pdev->dev, struct_size(pmc, port, num_ports), GFP_KERNEL);
+ if (!pmc)
return -ENOMEM;
+ pmc->num_ports = num_ports;
+
pmc->ipc = devm_intel_scu_ipc_dev_get(&pdev->dev);
if (!pmc->ipc)
return -EPROBE_DEFER;
diff --git a/drivers/usb/typec/mux/it5205.c b/drivers/usb/typec/mux/it5205.c
index 4357cc67a867..5e1a120b2e3b 100644
--- a/drivers/usb/typec/mux/it5205.c
+++ b/drivers/usb/typec/mux/it5205.c
@@ -266,7 +266,7 @@ static void it5205_remove(struct i2c_client *client)
}
static const struct i2c_device_id it5205_table[] = {
- { "it5205" },
+ { .name = "it5205" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, it5205_table);
diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c
index b57b6c9c40fe..d1fa26ff442c 100644
--- a/drivers/usb/typec/mux/nb7vpq904m.c
+++ b/drivers/usb/typec/mux/nb7vpq904m.c
@@ -499,7 +499,7 @@ static void nb7vpq904m_remove(struct i2c_client *client)
}
static const struct i2c_device_id nb7vpq904m_table[] = {
- { "nb7vpq904m" },
+ { .name = "nb7vpq904m" },
{ }
};
MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table);
diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c
index 8eeec135dcdb..985683fe49e9 100644
--- a/drivers/usb/typec/mux/pi3usb30532.c
+++ b/drivers/usb/typec/mux/pi3usb30532.c
@@ -169,7 +169,7 @@ static void pi3usb30532_remove(struct i2c_client *client)
}
static const struct i2c_device_id pi3usb30532_table[] = {
- { "pi3usb30532" },
+ { .name = "pi3usb30532" },
{ }
};
MODULE_DEVICE_TABLE(i2c, pi3usb30532_table);
diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c
index 1256252eceed..f52443638ee2 100644
--- a/drivers/usb/typec/mux/ps883x.c
+++ b/drivers/usb/typec/mux/ps883x.c
@@ -61,19 +61,110 @@ struct ps883x_retimer {
struct mutex lock; /* protect non-concurrent retimer & switch */
enum typec_orientation orientation;
- u8 cfg0;
- u8 cfg1;
- u8 cfg2;
+ bool in_reset;
};
+static int ps883x_enable_vregs(struct ps883x_retimer *retimer)
+{
+ struct device *dev = &retimer->client->dev;
+ int ret;
+
+ ret = regulator_enable(retimer->vdd33_supply);
+ if (ret) {
+ dev_err(dev, "cannot enable VDD 3.3V regulator: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_enable(retimer->vdd33_cap_supply);
+ if (ret) {
+ dev_err(dev, "cannot enable VDD 3.3V CAP regulator: %d\n", ret);
+ goto err_vdd33_disable;
+ }
+
+ usleep_range(4000, 10000);
+
+ ret = regulator_enable(retimer->vdd_supply);
+ if (ret) {
+ dev_err(dev, "cannot enable VDD regulator: %d\n", ret);
+ goto err_vdd33_cap_disable;
+ }
+
+ ret = regulator_enable(retimer->vddar_supply);
+ if (ret) {
+ dev_err(dev, "cannot enable VDD AR regulator: %d\n", ret);
+ goto err_vdd_disable;
+ }
+
+ ret = regulator_enable(retimer->vddat_supply);
+ if (ret) {
+ dev_err(dev, "cannot enable VDD AT regulator: %d\n", ret);
+ goto err_vddar_disable;
+ }
+
+ ret = regulator_enable(retimer->vddio_supply);
+ if (ret) {
+ dev_err(dev, "cannot enable VDD IO regulator: %d\n", ret);
+ goto err_vddat_disable;
+ }
+
+ return 0;
+
+err_vddat_disable:
+ regulator_disable(retimer->vddat_supply);
+err_vddar_disable:
+ regulator_disable(retimer->vddar_supply);
+err_vdd_disable:
+ regulator_disable(retimer->vdd_supply);
+err_vdd33_cap_disable:
+ regulator_disable(retimer->vdd33_cap_supply);
+err_vdd33_disable:
+ regulator_disable(retimer->vdd33_supply);
+
+ return ret;
+}
+
+static void ps883x_disable_vregs(struct ps883x_retimer *retimer)
+{
+ regulator_disable(retimer->vddio_supply);
+ regulator_disable(retimer->vddat_supply);
+ regulator_disable(retimer->vddar_supply);
+ regulator_disable(retimer->vdd_supply);
+ regulator_disable(retimer->vdd33_cap_supply);
+ regulator_disable(retimer->vdd33_supply);
+}
+
+static void ps883x_reset(struct ps883x_retimer *retimer)
+{
+ if (retimer->in_reset)
+ return;
+
+ gpiod_set_value(retimer->reset_gpio, 1);
+ ps883x_disable_vregs(retimer);
+ retimer->in_reset = true;
+}
+
static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0,
- int cfg1, int cfg2)
+ int cfg1, int cfg2, bool reset)
{
struct device *dev = &retimer->client->dev;
int ret;
- if (retimer->cfg0 == cfg0 && retimer->cfg1 == cfg1 && retimer->cfg2 == cfg2)
+ if (reset) {
+ ps883x_reset(retimer);
+
return 0;
+ } else if (retimer->in_reset) {
+ ret = ps883x_enable_vregs(retimer);
+ if (ret)
+ return ret;
+
+ gpiod_set_value(retimer->reset_gpio, 0);
+
+ /* firmware initialization delay */
+ msleep(60);
+
+ retimer->in_reset = false;
+ }
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0);
if (ret) {
@@ -93,10 +184,6 @@ static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0,
return ret;
}
- retimer->cfg0 = cfg0;
- retimer->cfg1 = cfg1;
- retimer->cfg2 = cfg2;
-
return 0;
}
@@ -107,6 +194,7 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state
int cfg0 = CONN_STATUS_0_CONNECTION_PRESENT;
int cfg1 = 0x00;
int cfg2 = 0x00;
+ bool reset = false;
if (retimer->orientation == TYPEC_ORIENTATION_REVERSE)
cfg0 |= CONN_STATUS_0_ORIENTATION_REVERSED;
@@ -148,9 +236,13 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state
}
} else {
switch (state->mode) {
+ /* SAFE can be transient or point to an actual disconnect */
case TYPEC_STATE_SAFE:
+ reset = retimer->orientation == TYPEC_ORIENTATION_NONE;
+ break;
/* USB2 pins don't even go through this chip */
case TYPEC_MODE_USB2:
+ reset = true;
break;
case TYPEC_STATE_USB:
case TYPEC_MODE_USB3:
@@ -171,7 +263,7 @@ static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state
}
}
- return ps883x_configure(retimer, cfg0, cfg1, cfg2);
+ return ps883x_configure(retimer, cfg0, cfg1, cfg2, reset);
}
static int ps883x_sw_set(struct typec_switch_dev *sw,
@@ -184,11 +276,19 @@ static int ps883x_sw_set(struct typec_switch_dev *sw,
if (ret)
return ret;
- mutex_lock(&retimer->lock);
+ guard(mutex)(&retimer->lock);
if (retimer->orientation != orientation) {
retimer->orientation = orientation;
+ /*
+ * Orientation notifications usually come prior to mode switch
+ * events. If the retimer is already in reset, we still want to
+ * cache the new orientation value for the subsequent ps883x_set().
+ */
+ if (retimer->in_reset)
+ return 0;
+
ret = regmap_assign_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0,
CONN_STATUS_0_ORIENTATION_REVERSED,
orientation == TYPEC_ORIENTATION_REVERSE);
@@ -196,8 +296,6 @@ static int ps883x_sw_set(struct typec_switch_dev *sw,
dev_err(&retimer->client->dev, "failed to set orientation: %d\n", ret);
}
- mutex_unlock(&retimer->lock);
-
return ret;
}
@@ -222,75 +320,6 @@ static int ps883x_retimer_set(struct typec_retimer *rtmr,
return typec_mux_set(retimer->typec_mux, &mux_state);
}
-static int ps883x_enable_vregs(struct ps883x_retimer *retimer)
-{
- struct device *dev = &retimer->client->dev;
- int ret;
-
- ret = regulator_enable(retimer->vdd33_supply);
- if (ret) {
- dev_err(dev, "cannot enable VDD 3.3V regulator: %d\n", ret);
- return ret;
- }
-
- ret = regulator_enable(retimer->vdd33_cap_supply);
- if (ret) {
- dev_err(dev, "cannot enable VDD 3.3V CAP regulator: %d\n", ret);
- goto err_vdd33_disable;
- }
-
- usleep_range(4000, 10000);
-
- ret = regulator_enable(retimer->vdd_supply);
- if (ret) {
- dev_err(dev, "cannot enable VDD regulator: %d\n", ret);
- goto err_vdd33_cap_disable;
- }
-
- ret = regulator_enable(retimer->vddar_supply);
- if (ret) {
- dev_err(dev, "cannot enable VDD AR regulator: %d\n", ret);
- goto err_vdd_disable;
- }
-
- ret = regulator_enable(retimer->vddat_supply);
- if (ret) {
- dev_err(dev, "cannot enable VDD AT regulator: %d\n", ret);
- goto err_vddar_disable;
- }
-
- ret = regulator_enable(retimer->vddio_supply);
- if (ret) {
- dev_err(dev, "cannot enable VDD IO regulator: %d\n", ret);
- goto err_vddat_disable;
- }
-
- return 0;
-
-err_vddat_disable:
- regulator_disable(retimer->vddat_supply);
-err_vddar_disable:
- regulator_disable(retimer->vddar_supply);
-err_vdd_disable:
- regulator_disable(retimer->vdd_supply);
-err_vdd33_cap_disable:
- regulator_disable(retimer->vdd33_cap_supply);
-err_vdd33_disable:
- regulator_disable(retimer->vdd33_supply);
-
- return ret;
-}
-
-static void ps883x_disable_vregs(struct ps883x_retimer *retimer)
-{
- regulator_disable(retimer->vddio_supply);
- regulator_disable(retimer->vddat_supply);
- regulator_disable(retimer->vddar_supply);
- regulator_disable(retimer->vdd_supply);
- regulator_disable(retimer->vdd33_cap_supply);
- regulator_disable(retimer->vdd33_supply);
-}
-
static int ps883x_get_vregs(struct ps883x_retimer *retimer)
{
struct device *dev = &retimer->client->dev;
@@ -422,6 +451,9 @@ static int ps883x_retimer_probe(struct i2c_client *client)
}
}
+ /* Keep the retimer in reset until a Type-C notification comes */
+ ps883x_reset(retimer);
+
sw_desc.drvdata = retimer;
sw_desc.fwnode = dev_fwnode(dev);
sw_desc.set = ps883x_sw_set;
diff --git a/drivers/usb/typec/mux/ptn36502.c b/drivers/usb/typec/mux/ptn36502.c
index 129d9d24b932..afd16775dbaf 100644
--- a/drivers/usb/typec/mux/ptn36502.c
+++ b/drivers/usb/typec/mux/ptn36502.c
@@ -404,7 +404,7 @@ static void ptn36502_remove(struct i2c_client *client)
}
static const struct i2c_device_id ptn36502_table[] = {
- { "ptn36502" },
+ { .name = "ptn36502" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ptn36502_table);
diff --git a/drivers/usb/typec/mux/wcd939x-usbss.c b/drivers/usb/typec/mux/wcd939x-usbss.c
index d46c353dfaf2..73db3aa3cec4 100644
--- a/drivers/usb/typec/mux/wcd939x-usbss.c
+++ b/drivers/usb/typec/mux/wcd939x-usbss.c
@@ -753,7 +753,7 @@ static void wcd939x_usbss_remove(struct i2c_client *client)
}
static const struct i2c_device_id wcd939x_usbss_table[] = {
- { "wcd9390-usbss" },
+ { .name = "wcd9390-usbss" },
{ }
};
MODULE_DEVICE_TABLE(i2c, wcd939x_usbss_table);
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
index 9ab1277b7ed1..3319f6a2b0c9 100644
--- a/drivers/usb/typec/tcpm/fusb302.c
+++ b/drivers/usb/typec/tcpm/fusb302.c
@@ -1841,8 +1841,8 @@ static const struct of_device_id fusb302_dt_match[] __maybe_unused = {
MODULE_DEVICE_TABLE(of, fusb302_dt_match);
static const struct i2c_device_id fusb302_i2c_device_id[] = {
- { "typec_fusb302" },
- {}
+ { .name = "typec_fusb302" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, fusb302_i2c_device_id);
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c
index 3766790c1548..35320f89dad2 100644
--- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c
@@ -4,19 +4,14 @@
*/
#include <linux/err.h>
-#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
-#include <linux/regulator/consumer.h>
#include <linux/slab.h>
-#include <linux/usb/role.h>
#include <linux/usb/tcpm.h>
-#include <linux/usb/typec_mux.h>
#include <drm/bridge/aux-bridge.h>
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c
index 8051eaa46991..429bd42a0e62 100644
--- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c
@@ -9,6 +9,7 @@
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -704,6 +705,7 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev,
struct device *dev = &pdev->dev;
struct pmic_typec_port_irq_data *irq_data;
struct pmic_typec_port *pmic_typec_port;
+ struct fwnode_handle *connector;
int i, ret, irq;
pmic_typec_port = devm_kzalloc(dev, sizeof(*pmic_typec_port), GFP_KERNEL);
@@ -720,7 +722,15 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev,
mutex_init(&pmic_typec_port->vbus_lock);
- pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus");
+ connector = device_get_named_child_node(dev, "connector");
+ if (!connector)
+ return -EINVAL;
+
+ pmic_typec_port->vdd_vbus = devm_of_regulator_get_optional(dev,
+ to_of_node(connector),
+ "vbus");
+ if (pmic_typec_port->vdd_vbus == ERR_PTR(-ENODEV))
+ pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus");
if (IS_ERR(pmic_typec_port->vdd_vbus))
return PTR_ERR(pmic_typec_port->vdd_vbus);
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index 0148b8f50412..7ac7000b2d13 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -141,13 +141,10 @@ static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
}
if (vconn_pres) {
- if (polarity == TYPEC_POLARITY_CC2) {
- reg &= ~TCPC_ROLE_CTRL_CC1;
- reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_OPEN);
- } else {
- reg &= ~TCPC_ROLE_CTRL_CC2;
- reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_OPEN);
- }
+ if (polarity == TYPEC_POLARITY_CC2)
+ FIELD_MODIFY(TCPC_ROLE_CTRL_CC1, &reg, TCPC_ROLE_CTRL_CC_OPEN);
+ else
+ FIELD_MODIFY(TCPC_ROLE_CTRL_CC2, &reg, TCPC_ROLE_CTRL_CC_OPEN);
}
ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
@@ -1020,7 +1017,7 @@ static int tcpci_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume);
static const struct i2c_device_id tcpci_id[] = {
- { "tcpci" },
+ { .name = "tcpci" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tcpci_id);
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
index 7324139d51c8..998693a61839 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
+++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
@@ -579,7 +579,7 @@ static int max_tcpci_suspend(struct device *dev)
static SIMPLE_DEV_PM_OPS(max_tcpci_pm_ops, max_tcpci_suspend, max_tcpci_resume);
static const struct i2c_device_id max_tcpci_id[] = {
- { "maxtcpc" },
+ { .name = "maxtcpc" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max_tcpci_id);
diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c
index 4b3e4e22a82e..a8726da6fc71 100644
--- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c
+++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c
@@ -373,10 +373,10 @@ static const struct rt1711h_chip_info rt1715 = {
};
static const struct i2c_device_id rt1711h_id[] = {
- { "et7304", (kernel_ulong_t)&rt1715 },
- { "rt1711h", (kernel_ulong_t)&rt1711h },
- { "rt1715", (kernel_ulong_t)&rt1715 },
- {}
+ { .name = "et7304", .driver_data = (kernel_ulong_t)&rt1715 },
+ { .name = "rt1711h", .driver_data = (kernel_ulong_t)&rt1711h },
+ { .name = "rt1715", .driver_data = (kernel_ulong_t)&rt1715 },
+ { }
};
MODULE_DEVICE_TABLE(i2c, rt1711h_id);
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index d0b769333bd9..d5ee0af9058b 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -2028,7 +2028,7 @@ static const struct of_device_id tps6598x_of_match[] = {
MODULE_DEVICE_TABLE(of, tps6598x_of_match);
static const struct i2c_device_id tps6598x_id[] = {
- { "tps6598x", (kernel_ulong_t)&tps6598x_data },
+ { .name = "tps6598x", .driver_data = (kernel_ulong_t)&tps6598x_data },
{ }
};
MODULE_DEVICE_TABLE(i2c, tps6598x_id);
diff --git a/drivers/usb/typec/ucsi/cros_ec_ucsi.c b/drivers/usb/typec/ucsi/cros_ec_ucsi.c
index 251aa7251ce6..c192d42d449e 100644
--- a/drivers/usb/typec/ucsi/cros_ec_ucsi.c
+++ b/drivers/usb/typec/ucsi/cros_ec_ucsi.c
@@ -114,12 +114,14 @@ static int cros_ucsi_async_control(struct ucsi *ucsi, u64 cmd)
}
static int cros_ucsi_sync_control(struct ucsi *ucsi, u64 cmd, u32 *cci,
- void *data, size_t size)
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size)
{
struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi);
int ret;
- ret = ucsi_sync_control_common(ucsi, cmd, cci, data, size);
+ ret = ucsi_sync_control_common(ucsi, cmd, cci, data, size, msg_out,
+ msg_out_size);
switch (ret) {
case -EBUSY:
/* EC may return -EBUSY if CCI.busy is set.
diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c
index a4b9a6b51649..ff33a5e7c6b0 100644
--- a/drivers/usb/typec/ucsi/debugfs.c
+++ b/drivers/usb/typec/ucsi/debugfs.c
@@ -40,6 +40,11 @@ static int ucsi_cmd(void *data, u64 val)
case UCSI_READ_POWER_LEVEL:
ret = ucsi_send_command(ucsi, val, NULL, 0);
break;
+ case UCSI_SET_PDOS:
+ ret = ucsi_write_message_out_command(ucsi, val, NULL, 0,
+ ucsi->debugfs->message_out,
+ UCSI_COMMAND_DATA_LEN(val));
+ break;
case UCSI_GET_CAPABILITY:
case UCSI_GET_CONNECTOR_CAPABILITY:
case UCSI_GET_ALTERNATE_MODES:
@@ -110,6 +115,30 @@ static int ucsi_vbus_volt_show(struct seq_file *m, void *v)
}
DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt);
+static ssize_t ucsi_message_out_write(struct file *file,
+ const char __user *data, size_t count, loff_t *ppos)
+{
+ struct ucsi *ucsi = file->private_data;
+ int ret;
+
+ char *buf __free(kfree) = memdup_user_nul(data, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = hex2bin(ucsi->debugfs->message_out, buf,
+ min(count / 2, sizeof(ucsi->debugfs->message_out)));
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static const struct file_operations ucsi_message_out_fops = {
+ .open = simple_open,
+ .write = ucsi_message_out_write,
+ .llseek = generic_file_llseek,
+};
+
void ucsi_debugfs_register(struct ucsi *ucsi)
{
ucsi->debugfs = kzalloc_obj(*ucsi->debugfs);
@@ -122,6 +151,8 @@ void ucsi_debugfs_register(struct ucsi *ucsi)
debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops);
debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops);
debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops);
+ debugfs_create_file("message_out", 0200, ucsi->debugfs->dentry, ucsi,
+ &ucsi_message_out_fops);
}
void ucsi_debugfs_unregister(struct ucsi *ucsi)
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 61cb24ed820f..92166a3725b1 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -63,7 +63,8 @@ void ucsi_notify_common(struct ucsi *ucsi, u32 cci)
EXPORT_SYMBOL_GPL(ucsi_notify_common);
int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci,
- void *data, size_t size)
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size)
{
bool ack = UCSI_COMMAND(command) == UCSI_ACK_CC_CI;
int ret;
@@ -75,6 +76,17 @@ int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci,
reinit_completion(&ucsi->complete);
+ if (msg_out && msg_out_size) {
+ if (!ucsi->ops->write_message_out) {
+ ret = -EOPNOTSUPP;
+ goto out_clear_bit;
+ }
+
+ ret = ucsi->ops->write_message_out(ucsi, msg_out, msg_out_size);
+ if (ret)
+ goto out_clear_bit;
+ }
+
ret = ucsi->ops->async_control(ucsi, command);
if (ret)
goto out_clear_bit;
@@ -110,11 +122,12 @@ static int ucsi_acknowledge(struct ucsi *ucsi, bool conn_ack)
ctrl |= UCSI_ACK_CONNECTOR_CHANGE;
}
- return ucsi->ops->sync_control(ucsi, ctrl, NULL, NULL, 0);
+ return ucsi->ops->sync_control(ucsi, ctrl, NULL, NULL, 0, NULL, 0);
}
static int ucsi_run_command(struct ucsi *ucsi, u64 command, u32 *cci,
- void *data, size_t size, bool conn_ack)
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size, bool conn_ack)
{
int ret, err;
@@ -123,10 +136,12 @@ static int ucsi_run_command(struct ucsi *ucsi, u64 command, u32 *cci,
if (size > UCSI_MAX_DATA_LENGTH(ucsi))
return -EINVAL;
- ret = ucsi->ops->sync_control(ucsi, command, cci, data, size);
+ ret = ucsi->ops->sync_control(ucsi, command, cci, data, size,
+ msg_out, msg_out_size);
if (*cci & UCSI_CCI_BUSY)
- return ucsi_run_command(ucsi, UCSI_CANCEL, cci, NULL, 0, false) ?: -EBUSY;
+ return ucsi_run_command(ucsi, UCSI_CANCEL, cci,
+ NULL, 0, NULL, 0, false) ?: -EBUSY;
if (ret)
return ret;
@@ -158,7 +173,8 @@ static int ucsi_read_error(struct ucsi *ucsi, u8 connector_num)
int ret;
command = UCSI_GET_ERROR_STATUS | UCSI_CONNECTOR_NUMBER(connector_num);
- ret = ucsi_run_command(ucsi, command, &cci, &error, sizeof(error), false);
+ ret = ucsi_run_command(ucsi, command, &cci, &error,
+ sizeof(error), NULL, 0, false);
if (ret < 0)
return ret;
@@ -208,7 +224,8 @@ static int ucsi_read_error(struct ucsi *ucsi, u8 connector_num)
}
static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd,
- void *data, size_t size, bool conn_ack)
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size, bool conn_ack)
{
u8 connector_num;
u32 cci;
@@ -236,7 +253,8 @@ static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd,
mutex_lock(&ucsi->ppm_lock);
- ret = ucsi_run_command(ucsi, cmd, &cci, data, size, conn_ack);
+ ret = ucsi_run_command(ucsi, cmd, &cci, data, size,
+ msg_out, msg_out_size, conn_ack);
if (cci & UCSI_CCI_ERROR)
ret = ucsi_read_error(ucsi, connector_num);
@@ -250,10 +268,23 @@ static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd,
int ucsi_send_command(struct ucsi *ucsi, u64 command,
void *data, size_t size)
{
- return ucsi_send_command_common(ucsi, command, data, size, false);
+ return ucsi_send_command_common(ucsi, command, data,
+ size, NULL, 0, false);
}
EXPORT_SYMBOL_GPL(ucsi_send_command);
+int ucsi_write_message_out_command(struct ucsi *ucsi, u64 command,
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size)
+{
+ if (msg_out_size > UCSI_MAX_MSG_OUT_DATA_LEN(ucsi))
+ return -EINVAL;
+
+ return ucsi_send_command_common(ucsi, command, data,
+ size, msg_out, msg_out_size, false);
+}
+EXPORT_SYMBOL_GPL(ucsi_write_message_out_command);
+
/* -------------------------------------------------------------------------- */
struct ucsi_work {
@@ -681,7 +712,8 @@ static int ucsi_get_connector_status(struct ucsi_connector *con, bool conn_ack)
UCSI_MAX_DATA_LENGTH(con->ucsi));
int ret;
- ret = ucsi_send_command_common(con->ucsi, command, &con->status, size, conn_ack);
+ ret = ucsi_send_command_common(con->ucsi, command, &con->status, size,
+ NULL, 0, conn_ack);
return ret < 0 ? ret : 0;
}
@@ -1658,6 +1690,7 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)
INIT_WORK(&con->work, ucsi_handle_connector_change);
init_completion(&con->complete);
mutex_init(&con->lock);
+ lockdep_set_class(&con->lock, &con->lock_key);
INIT_LIST_HEAD(&con->partner_tasks);
con->ucsi = ucsi;
@@ -1903,6 +1936,9 @@ static int ucsi_init(struct ucsi *ucsi)
goto err_reset;
}
+ for (i = 0; i < ucsi->cap.num_connectors; i++)
+ lockdep_register_key(&connector[i].lock_key);
+
/* Register all connectors */
for (i = 0; i < ucsi->cap.num_connectors; i++) {
connector[i].num = i + 1;
@@ -1932,6 +1968,9 @@ static int ucsi_init(struct ucsi *ucsi)
return 0;
err_unregister:
+ for (i = 0; i < ucsi->cap.num_connectors; i++)
+ lockdep_unregister_key(&connector[i].lock_key);
+
for (con = connector; con->port; con++) {
if (con->wq)
destroy_workqueue(con->wq);
@@ -2182,6 +2221,7 @@ void ucsi_unregister(struct ucsi *ucsi)
usb_power_delivery_unregister(ucsi->connector[i].pd);
ucsi->connector[i].pd = NULL;
typec_unregister_port(ucsi->connector[i].port);
+ lockdep_unregister_key(&ucsi->connector[i].lock_key);
}
kfree(ucsi->connector);
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index cff9ddc2ae21..325ed1e5ca80 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -65,6 +65,7 @@ struct dentry;
* @read_cci: Read CCI register
* @poll_cci: Read CCI register while polling with notifications disabled
* @read_message_in: Read message data from UCSI
+ * @write_message_out: Write message data to UCSI
* @sync_control: Blocking control operation
* @async_control: Non-blocking control operation
* @update_altmodes: Squashes duplicate DP altmodes
@@ -82,8 +83,9 @@ struct ucsi_operations {
int (*read_cci)(struct ucsi *ucsi, u32 *cci);
int (*poll_cci)(struct ucsi *ucsi, u32 *cci);
int (*read_message_in)(struct ucsi *ucsi, void *val, size_t val_len);
+ int (*write_message_out)(struct ucsi *ucsi, void *data, size_t data_len);
int (*sync_control)(struct ucsi *ucsi, u64 command, u32 *cci,
- void *data, size_t size);
+ void *data, size_t size, void *msg_out, size_t msg_out_size);
int (*async_control)(struct ucsi *ucsi, u64 command);
bool (*update_altmodes)(struct ucsi *ucsi, u8 recipient,
struct ucsi_altmode *orig,
@@ -136,6 +138,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
#define UCSI_GET_PD_MESSAGE 0x15
#define UCSI_GET_CAM_CS 0x18
#define UCSI_SET_SINK_PATH 0x1c
+#define UCSI_SET_PDOS 0x1d
#define UCSI_READ_POWER_LEVEL 0x1e
#define UCSI_SET_USB 0x21
#define UCSI_GET_LPM_PPM_INFO 0x22
@@ -213,6 +216,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
#define UCSI_GET_PD_MESSAGE_TYPE_IDENTITY 4
#define UCSI_GET_PD_MESSAGE_TYPE_REVISION 5
+/* Data length bits */
+#define UCSI_COMMAND_DATA_LEN(_cmd_) (((_cmd_) >> 8) & GENMASK(7, 0))
+
/* -------------------------------------------------------------------------- */
/* Error information returned by PPM in response to GET_ERROR_STATUS command. */
@@ -453,6 +459,8 @@ struct ucsi_bitfield {
/* -------------------------------------------------------------------------- */
+#define MESSAGE_OUT_MAX_LEN 256
+
struct ucsi_debugfs_entry {
u64 command;
struct ucsi_data {
@@ -460,6 +468,7 @@ struct ucsi_debugfs_entry {
u64 high;
} response;
int status;
+ u8 message_out[MESSAGE_OUT_MAX_LEN];
struct dentry *dentry;
};
@@ -503,6 +512,9 @@ struct ucsi {
};
#define UCSI_MAX_DATA_LENGTH(u) (((u)->version < UCSI_VERSION_2_0) ? 0x10 : 0xff)
+#define UCSI_MAX_MSG_OUT_DATA_LEN(u) \
+ (((u)->version >= UCSI_VERSION_3_0) ? 255 : \
+ (((u)->version >= UCSI_VERSION_2_0) ? 256 : 16))
#define UCSI_MAX_SVID 5
#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)
@@ -517,6 +529,7 @@ struct ucsi_connector {
struct ucsi *ucsi;
struct mutex lock; /* port lock */
+ struct lock_class_key lock_key;
struct work_struct work;
struct completion complete;
struct workqueue_struct *wq;
@@ -564,13 +577,17 @@ struct ucsi_connector {
int ucsi_send_command(struct ucsi *ucsi, u64 command,
void *retval, size_t size);
+int ucsi_write_message_out_command(struct ucsi *ucsi, u64 command,
+ void *retval, size_t size,
+ void *msg_out, size_t msg_out_size);
void ucsi_altmode_update_active(struct ucsi_connector *con);
int ucsi_resume(struct ucsi *ucsi);
void ucsi_notify_common(struct ucsi *ucsi, u32 cci);
int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci,
- void *data, size_t size);
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size);
#if IS_ENABLED(CONFIG_POWER_SUPPLY)
int ucsi_register_port_psy(struct ucsi_connector *con);
diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c
index 6b92f296e985..60b12961e1a4 100644
--- a/drivers/usb/typec/ucsi/ucsi_acpi.c
+++ b/drivers/usb/typec/ucsi/ucsi_acpi.c
@@ -86,6 +86,21 @@ static int ucsi_acpi_read_message_in(struct ucsi *ucsi, void *val, size_t val_le
return 0;
}
+static int ucsi_acpi_write_message_out(struct ucsi *ucsi, void *data, size_t data_len)
+{
+ struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
+
+ if (!data || !data_len)
+ return -EINVAL;
+
+ if (ucsi->version <= UCSI_VERSION_1_2)
+ memcpy(ua->base + UCSI_MESSAGE_OUT, data, data_len);
+ else
+ memcpy(ua->base + UCSIv2_MESSAGE_OUT, data, data_len);
+
+ return 0;
+}
+
static int ucsi_acpi_async_control(struct ucsi *ucsi, u64 command)
{
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
@@ -101,19 +116,22 @@ static const struct ucsi_operations ucsi_acpi_ops = {
.read_cci = ucsi_acpi_read_cci,
.poll_cci = ucsi_acpi_poll_cci,
.read_message_in = ucsi_acpi_read_message_in,
+ .write_message_out = ucsi_acpi_write_message_out,
.sync_control = ucsi_sync_control_common,
.async_control = ucsi_acpi_async_control
};
static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci,
- void *val, size_t len)
+ void *val, size_t len, void *msg_out,
+ size_t msg_out_size)
{
u16 bogus_change = UCSI_CONSTAT_POWER_LEVEL_CHANGE |
UCSI_CONSTAT_PDOS_CHANGE;
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
int ret;
- ret = ucsi_sync_control_common(ucsi, command, cci, val, len);
+ ret = ucsi_sync_control_common(ucsi, command, cci, val, len,
+ msg_out, msg_out_size);
if (ret < 0)
return ret;
diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c
index 4463c1ae96bd..d46ca942026e 100644
--- a/drivers/usb/typec/ucsi/ucsi_ccg.c
+++ b/drivers/usb/typec/ucsi/ucsi_ccg.c
@@ -608,7 +608,8 @@ static int ucsi_ccg_async_control(struct ucsi *ucsi, u64 command)
}
static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci,
- void *data, size_t size)
+ void *data, size_t size, void *msg_out,
+ size_t msg_out_size)
{
struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi);
struct ucsi_connector *con;
@@ -630,7 +631,8 @@ static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci,
ucsi_ccg_update_set_new_cam_cmd(uc, con, &command);
}
- ret = ucsi_sync_control_common(ucsi, command, cci, data, size);
+ ret = ucsi_sync_control_common(ucsi, command, cci, data, size,
+ msg_out, msg_out_size);
switch (UCSI_COMMAND(command)) {
case UCSI_GET_CURRENT_CAM:
@@ -1530,8 +1532,8 @@ static const struct of_device_id ucsi_ccg_of_match_table[] = {
MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table);
static const struct i2c_device_id ucsi_ccg_device_id[] = {
- { "ccgx-ucsi" },
- {}
+ { .name = "ccgx-ucsi" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id);
diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
index ca749fde49bd..ad669d2f8b9c 100644
--- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
+++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
@@ -18,6 +18,7 @@
#include <linux/usb/pd_vdo.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
#include <linux/workqueue_types.h>
#include "ucsi.h"
@@ -82,6 +83,8 @@ struct gaokun_ucsi_port {
struct gaokun_ucsi *ucsi;
struct auxiliary_device *bridge;
+ struct typec_mux *typec_mux;
+
int idx;
enum gaokun_ucsi_ccx ccx;
enum gaokun_ucsi_mux mux;
@@ -226,19 +229,18 @@ static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
- /* Mode and SVID are unused; keeping them to make things clearer */
switch (port->mode) {
case USBC_DPAM_PAN_C:
case USBC_DPAM_PAN_C_REVERSE:
- port->mode = DP_PIN_ASSIGN_C; /* correct it for usb later */
+ port->mode = TYPEC_DP_STATE_C; /* correct it for usb later */
break;
case USBC_DPAM_PAN_D:
case USBC_DPAM_PAN_D_REVERSE:
- port->mode = DP_PIN_ASSIGN_D;
+ port->mode = TYPEC_DP_STATE_D;
break;
case USBC_DPAM_PAN_E:
case USBC_DPAM_PAN_E_REVERSE:
- port->mode = DP_PIN_ASSIGN_E;
+ port->mode = TYPEC_DP_STATE_E;
break;
case USBC_DPAM_PAN_NONE:
port->mode = TYPEC_STATE_SAFE;
@@ -287,18 +289,32 @@ static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
return idx;
}
-static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
+static void gaokun_ucsi_handle_usb_mode(struct gaokun_ucsi_port *port)
{
struct gaokun_ucsi *uec = port->ucsi;
- int idx = port->idx;
-
- if (idx >= uec->ucsi->cap.num_connectors) {
+ struct typec_mux_state state = {};
+ struct typec_altmode dp_alt = {};
+ int idx = port->idx, ret;
+
+ /*
+ * For every typec port on this platform, the only mode-switch is
+ * controlled by its qmp combo phy which consumes svid and mode only.
+ */
+ dp_alt.svid = port->svid;
+ state.mode = port->mode;
+ state.alt = &dp_alt;
+
+ if (idx >= uec->num_ports) {
dev_warn(uec->dev, "altmode port out of range: %d\n", idx);
return;
}
+ ret = typec_mux_set(port->typec_mux, &state);
+ if (ret)
+ dev_err(uec->dev, "failed to set mux %d\n", ret);
+
/* UCSI callback .connector_status() have set orientation */
- if (port->bridge)
+ if (port->bridge && port->svid == USB_TYPEC_DP_SID)
drm_aux_hpd_bridge_notify(&port->bridge->dev,
port->hpd_state ?
connector_status_connected :
@@ -307,7 +323,7 @@ static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
}
-static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
+static void gaokun_ucsi_usb_notify_ind(struct gaokun_ucsi *uec)
{
int idx;
@@ -320,7 +336,7 @@ static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
if (idx == GAOKUN_UCSI_NO_PORT_UPDATE)
gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */
else
- gaokun_ucsi_handle_altmode(&uec->ports[idx]);
+ gaokun_ucsi_handle_usb_mode(&uec->ports[idx]);
}
/*
@@ -352,7 +368,7 @@ static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
port = &uec->ports[idx];
if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
- gaokun_ucsi_altmode_notify_ind(uec);
+ gaokun_ucsi_usb_notify_ind(uec);
}
}
@@ -366,7 +382,7 @@ static int gaokun_ucsi_notify(struct notifier_block *nb,
switch (action) {
case EC_EVENT_USB:
gaokun_ucsi_complete_usb_ack(uec);
- gaokun_ucsi_altmode_notify_ind(uec);
+ gaokun_ucsi_usb_notify_ind(uec);
return NOTIFY_OK;
case EC_EVENT_UCSI:
@@ -429,8 +445,15 @@ static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
fwnode_handle_put(fwnode);
return PTR_ERR(ucsi_port->bridge);
}
- }
+ ucsi_port->typec_mux = fwnode_typec_mux_get(fwnode);
+ if (IS_ERR(ucsi_port->typec_mux)) {
+ fwnode_handle_put(fwnode);
+ return dev_err_probe(dev, PTR_ERR(ucsi_port->typec_mux),
+ "failed to acquire mode-switch for port: %d\n",
+ port);
+ }
+ }
for (i = 0; i < num_ports; i++) {
if (!uec->ports[i].bridge)
continue;
@@ -502,10 +525,14 @@ static int gaokun_ucsi_probe(struct auxiliary_device *adev,
static void gaokun_ucsi_remove(struct auxiliary_device *adev)
{
struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
+ int i;
disable_delayed_work_sync(&uec->work);
gaokun_ec_unregister_notify(uec->ec, &uec->nb);
ucsi_unregister(uec->ucsi);
+ for (i = 0; i < uec->num_ports; ++i)
+ typec_mux_put(uec->ports[i].typec_mux);
+
ucsi_destroy(uec->ucsi);
}
diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c
index 838ac0185082..848ed459a6de 100644
--- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c
+++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c
@@ -737,8 +737,8 @@ static const struct of_device_id __maybe_unused ucsi_stm32g0_typec_of_match[] =
MODULE_DEVICE_TABLE(of, ucsi_stm32g0_typec_of_match);
static const struct i2c_device_id ucsi_stm32g0_typec_i2c_devid[] = {
- { "stm32g0-typec" },
- {}
+ { .name = "stm32g0-typec" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, ucsi_stm32g0_typec_i2c_devid);
diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
index 0187c1c4b21a..1be18d101842 100644
--- a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
+++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
@@ -89,7 +89,8 @@ static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command)
static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi,
u64 command,
u32 *cci,
- void *data, size_t size)
+ void *data, size_t size,
+ void *msg_out, size_t msg_out_size)
{
int ret;
@@ -126,7 +127,8 @@ static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi,
return 0;
}
- ret = ucsi_sync_control_common(ucsi, command, cci, data, size);
+ ret = ucsi_sync_control_common(ucsi, command, cci,
+ data, size, msg_out, msg_out_size);
if (ret < 0)
return ret;
diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig
index b9f94e2e278d..50945b6fae1a 100644
--- a/drivers/usb/usbip/Kconfig
+++ b/drivers/usb/usbip/Kconfig
@@ -40,7 +40,7 @@ config USBIP_VHCI_HC_PORTS
config USBIP_VHCI_NR_HCS
int "Number of USB/IP virtual host controllers"
- range 1 128
+ range 1 32
default 1
depends on USBIP_VHCI_HCD
help
diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c
index b98d14c43d13..a7ede6fb3da9 100644
--- a/drivers/usb/usbip/vhci_sysfs.c
+++ b/drivers/usb/usbip/vhci_sysfs.c
@@ -59,6 +59,29 @@ static void port_show_vhci(char **out, int hub, int port, struct vhci_device *vd
*out += sprintf(*out, "\n");
}
+static ssize_t status_show_not_ready(int pdev_nr, char *out)
+{
+ char *s = out;
+ int i = 0;
+
+ for (i = 0; i < VHCI_HC_PORTS; i++) {
+ out += sprintf(out, "hs %04u %03u ",
+ (pdev_nr * VHCI_PORTS) + i,
+ VDEV_ST_NOTASSIGNED);
+ out += sprintf(out, "000 00000000 0000000000000000 0-0");
+ out += sprintf(out, "\n");
+ }
+
+ for (i = 0; i < VHCI_HC_PORTS; i++) {
+ out += sprintf(out, "ss %04u %03u ",
+ (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i,
+ VDEV_ST_NOTASSIGNED);
+ out += sprintf(out, "000 00000000 0000000000000000 0-0");
+ out += sprintf(out, "\n");
+ }
+ return out - s;
+}
+
/* Sysfs entry to show port status */
static ssize_t status_show_vhci(int pdev_nr, char *out)
{
@@ -76,6 +99,12 @@ static ssize_t status_show_vhci(int pdev_nr, char *out)
}
hcd = platform_get_drvdata(pdev);
+
+ if (!hcd) {
+ usbip_dbg_vhci_sysfs("show status error (hcd is NULL)\n");
+ return status_show_not_ready(pdev_nr, out);
+ }
+
vhci_hcd = hcd_to_vhci_hcd(hcd);
vhci = vhci_hcd->vhci;
@@ -104,29 +133,6 @@ static ssize_t status_show_vhci(int pdev_nr, char *out)
return out - s;
}
-static ssize_t status_show_not_ready(int pdev_nr, char *out)
-{
- char *s = out;
- int i = 0;
-
- for (i = 0; i < VHCI_HC_PORTS; i++) {
- out += sprintf(out, "hs %04u %03u ",
- (pdev_nr * VHCI_PORTS) + i,
- VDEV_ST_NOTASSIGNED);
- out += sprintf(out, "000 00000000 0000000000000000 0-0");
- out += sprintf(out, "\n");
- }
-
- for (i = 0; i < VHCI_HC_PORTS; i++) {
- out += sprintf(out, "ss %04u %03u ",
- (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i,
- VDEV_ST_NOTASSIGNED);
- out += sprintf(out, "000 00000000 0000000000000000 0-0");
- out += sprintf(out, "\n");
- }
- return out - s;
-}
-
static int status_name_to_id(const char *name)
{
char *c;
diff --git a/include/linux/platform_data/pxa2xx_udc.h b/include/linux/platform_data/pxa2xx_udc.h
index bc99cc6a3c5f..c1e4d03bae2c 100644
--- a/include/linux/platform_data/pxa2xx_udc.h
+++ b/include/linux/platform_data/pxa2xx_udc.h
@@ -10,21 +10,6 @@
#ifndef PXA2XX_UDC_H
#define PXA2XX_UDC_H
-struct pxa2xx_udc_mach_info {
- int (*udc_is_connected)(void); /* do we see host? */
- void (*udc_command)(int cmd);
-#define PXA2XX_UDC_CMD_CONNECT 0 /* let host see us */
-#define PXA2XX_UDC_CMD_DISCONNECT 1 /* so host won't see us */
-
- /* Boards following the design guidelines in the developer's manual,
- * with on-chip GPIOs not Lubbock's weird hardware, can have a sane
- * VBUS IRQ and omit the methods above. Store the GPIO number
- * here. Note that sometimes the signals go through inverters...
- */
- bool gpio_pullup_inverted;
- int gpio_pullup; /* high == pullup activated */
-};
-
#ifdef CONFIG_PXA27x
extern void pxa27x_clear_otgph(void);
#else
diff --git a/include/linux/platform_data/usb-xhci-prom21.h b/include/linux/platform_data/usb-xhci-prom21.h
new file mode 100644
index 000000000000..ee672ad452a8
--- /dev/null
+++ b/include/linux/platform_data/usb-xhci-prom21.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Promontory 21 xHCI auxiliary device platform data.
+ *
+ * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
+ */
+
+#ifndef _LINUX_PLATFORM_DATA_USB_XHCI_PROM21_H
+#define _LINUX_PLATFORM_DATA_USB_XHCI_PROM21_H
+
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+
+struct pci_dev;
+
+struct prom21_xhci_pdata {
+ struct pci_dev *pdev;
+ void __iomem *regs;
+ resource_size_t rsrc_len;
+};
+
+#endif
diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h
index 0ba112175bb3..feb1af175cfd 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -13,6 +13,7 @@
#include <linux/types.h>
+struct config_group;
struct fwnode_handle;
struct device;
@@ -153,6 +154,9 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block,
ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
size_t block_len);
struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir);
+int tb_property_merge_dir(struct tb_property_dir *parent,
+ const struct tb_property_dir *dir,
+ bool replace);
struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid);
void tb_property_free_dir(struct tb_property_dir *dir);
int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
@@ -209,6 +213,8 @@ enum tb_link_width {
* @link_width: Width of the downstream facing link
* @link_usb4: Downstream link is USB4
* @is_unplugged: The XDomain is unplugged
+ * @removing: Set by tb_xdomain_remove() under @lock to prevent
+ * concurrent delayed work queueing
* @needs_uuid: If the XDomain does not have @remote_uuid it will be
* queried first
* @service_ids: Used to generate IDs for the services
@@ -228,6 +234,7 @@ enum tb_link_width {
* changed notification
* @bonding_possible: True if lane bonding is possible on local side
* @target_link_width: Target link width from the remote host
+ * @ntunnels: Keeps track of how many tunnels go through this XDomain
* @link: Root switch link the remote domain is connected (ICM only)
* @depth: Depth in the chain the remote domain is connected (ICM only)
*
@@ -257,6 +264,7 @@ struct tb_xdomain {
enum tb_link_width link_width;
bool link_usb4;
bool is_unplugged;
+ bool removing;
bool needs_uuid;
struct ida service_ids;
struct ida in_hopids;
@@ -273,6 +281,7 @@ struct tb_xdomain {
int properties_changed_retries;
bool bonding_possible;
u8 target_link_width;
+ atomic_t ntunnels;
u8 link;
u8 depth;
};
@@ -392,6 +401,10 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler);
* @prtcvers: Protocol version from the properties directory
* @prtcrevs: Protocol software revision from the properties directory
* @prtcstns: Protocol settings mask from the properties directory
+ * @lock: Protects this structure
+ * @local_properties: Properties owned by the service driver
+ * @remote_properties: Properties read from the remote service. These
+ * are read-only.
* @debugfs_dir: Pointer to the service debugfs directory. Always created
* when debugfs is enabled. Can be used by service drivers to
* add their own entries under the service.
@@ -399,6 +412,9 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler);
* Each domain exposes set of services it supports as collection of
* properties. For each service there will be one corresponding
* &struct tb_service. Service drivers are bound to these.
+ *
+ * Service drivers can add their own dynamic properties to
+ * @local_properties but whenever they do so @lock must be held.
*/
struct tb_service {
struct device dev;
@@ -408,6 +424,9 @@ struct tb_service {
u32 prtcvers;
u32 prtcrevs;
u32 prtcstns;
+ struct mutex lock;
+ struct tb_property_dir *local_properties;
+ struct tb_property_dir *remote_properties;
struct dentry *debugfs_dir;
};
@@ -476,16 +495,17 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
return tb_to_xdomain(svc->dev.parent);
}
+void tb_service_properties_changed(struct tb_service *svc);
+
/**
* struct tb_nhi - thunderbolt native host interface
* @lock: Must be held during ring creation/destruction. Is acquired by
* interrupt_work when dispatching interrupts to individual rings.
- * @pdev: Pointer to the PCI device
+ * @dev: Device associated with this NHI instance
* @ops: NHI specific optional ops
* @iobase: MMIO space of the NHI
* @tx_rings: All Tx rings available on this host controller
* @rx_rings: All Rx rings available on this host controller
- * @msix_ida: Used to allocate MSI-X vectors for rings
* @going_away: The host controller device is about to disappear so when
* this flag is set, avoid touching the hardware anymore.
* @iommu_dma_protection: An IOMMU will isolate external-facing ports.
@@ -493,20 +513,21 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
* MSI-X is used.
* @hop_count: Number of rings (end point hops) supported by NHI.
* @quirks: NHI specific quirks if any
+ * @domain_released: Completed when domain has been fully released
*/
struct tb_nhi {
spinlock_t lock;
- struct pci_dev *pdev;
+ struct device *dev;
const struct tb_nhi_ops *ops;
void __iomem *iobase;
struct tb_ring **tx_rings;
struct tb_ring **rx_rings;
- struct ida msix_ida;
bool going_away;
bool iommu_dma_protection;
struct work_struct interrupt_work;
u32 hop_count;
unsigned long quirks;
+ struct completion domain_released;
};
/**
@@ -535,6 +556,9 @@ struct tb_nhi {
* @start_poll: Called when ring interrupt is triggered to start
* polling. Passing %NULL keeps the ring in interrupt mode.
* @poll_data: Data passed to @start_poll
+ * @interval_nsec: Interval counter if interrupt throttling is to be
+ * used with this ring (in ns)
+ * @wait: Used to signal that the ring may be empty now
*/
struct tb_ring {
spinlock_t lock;
@@ -558,6 +582,8 @@ struct tb_ring {
u16 eof_mask;
void (*start_poll)(void *data);
void *poll_data;
+ unsigned int interval_nsec;
+ wait_queue_head_t wait;
};
/* Leave ring interrupt enabled on suspend */
@@ -609,7 +635,20 @@ struct ring_frame {
};
/* Minimum size for ring_rx */
-#define TB_FRAME_SIZE 0x100
+#define TB_FRAME_SIZE 256
+#define TB_MAX_FRAME_SIZE 4096
+
+static inline size_t tb_ring_frame_size(const struct ring_frame *frame)
+{
+ if (frame->size)
+ return frame->size;
+ return TB_MAX_FRAME_SIZE;
+}
+
+static inline size_t tb_ring_size(const struct tb_ring *ring)
+{
+ return ring->size;
+}
struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size,
unsigned int flags);
@@ -618,6 +657,7 @@ struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size,
u16 sof_mask, u16 eof_mask,
void (*start_poll)(void *), void *poll_data);
void tb_ring_start(struct tb_ring *ring);
+bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec);
void tb_ring_stop(struct tb_ring *ring);
void tb_ring_free(struct tb_ring *ring);
@@ -670,6 +710,8 @@ static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame)
struct ring_frame *tb_ring_poll(struct tb_ring *ring);
void tb_ring_poll_complete(struct tb_ring *ring);
+int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec);
+
/**
* tb_ring_dma_device() - Return device used for DMA mapping
* @ring: Ring whose DMA device is retrieved
@@ -681,12 +723,17 @@ void tb_ring_poll_complete(struct tb_ring *ring);
*/
static inline struct device *tb_ring_dma_device(struct tb_ring *ring)
{
- return &ring->nhi->pdev->dev;
+ return ring->nhi->dev;
}
bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode);
+#if IS_REACHABLE(CONFIG_CONFIGFS_FS)
+int tb_configfs_register_group(struct config_group *group);
+void tb_configfs_unregister_group(struct config_group *group);
+#endif
+
#else /* CONFIG_USB4 */
static inline bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode)