mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 13:14:02 +02:00
USB / Thunderbolt changes for 7.3-rc1
Here is the big set of USB and Thunderbolt driver updates for 7.3-rc1.
Lots of driver work for new devices and systems, and many other minor
fixes and updates. Included in here are:
- Thunderbolt subsystem driver updates and additions
- typec driver updates and additions
- usb gadget fixes all over the place, seems like people are finally
paying attention to these drivers for some reason
- xhci driver updates and fixes based on lots of reports
- usb-serial driver updates and additions
- new device ids
- other minor USB driver updates and fixes
All of these have been in linux-next for a while with no reported issues
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-----BEGIN PGP SIGNATURE-----
iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCao2lJA8cZ3JlZ0Brcm9h
aC5jb20ACgkQMUfUDdst+ynK3QCeLQdHatZ2d5sJdvssiLKREZeND4EAnj+V1K1O
PcVJzKAiC4dgSZ5SOwn1
=OJea
-----END PGP SIGNATURE-----
Merge tag 'usb-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB / Thunderbolt updates from Greg KH:
"Here is the big set of USB and Thunderbolt driver updates for 7.3-rc1.
Lots of driver work for new devices and systems, and many other minor
fixes and updates. Included in here are:
- Thunderbolt subsystem driver updates and additions
- typec driver updates and additions
- usb gadget fixes all over the place, seems like people are finally
paying attention to these drivers for some reason
- xhci driver updates and fixes based on lots of reports
- usb-serial driver updates and additions
- new device ids
- other minor USB driver updates and fixes
All of these have been in linux-next for a while with no reported issues"
* tag 'usb-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (163 commits)
usb: gadget: uvc: fix dangling pointers in uvc_function_bind() and uvc_function_unbind()
usb: typec: hd3ss3220: fix VBUS regulator error message
usb: usbfs: fix use-after-free of usb_device in usbdev_release()
usb: gadget: u_audio: Fix use-after-free on sound card disconnect
usb: dwc3: gadget: Fix use-after-free in dwc3_gadget_free_endpoints due to race condition
usb: gadget: f_tcm: keep port count until LUN teardown completes
usb: usbtest: disable dynamic ID support
usb: typec: tcpci: pass correct rx_type to tcpm_pd_receive()
USB: c67x00: fix use-after-free in c67x00_add_iso_urb()
usb: typec: ucsi: use UCSI_TIMEOUT_MS for sync command completion
usb: gadget: snps_udc_plat: clean up PHY on probe deferral
usb: gadget: f_tcm: fix deadlock in usbg_make_tpg()
usb: dwc2: gadget: Exit partial power down state when changing USB pull-up
usb: gadget: f_fs: Fix Use-After-Free in AIO error path
usb: gadget: f_fs: Prevent deadlock during ep0 read loop
usb: gadget: at91_udc: drain polled-VBUS timer/work before udc is freed
usb: gadget: midi2: remove default configfs groups on teardown
usb: gadget: uvc: Fix null pointer dereference in uvcg_video_init()
usb: typec: thunderbolt: Disable work before freeing tbt on remove
usb: xhci: Handle bogus TRB pointers in Missed Service Error events
...
This commit is contained in:
commit
f4d50813c0
|
|
@ -26,6 +26,6 @@ Description:
|
|||
initializes all non-hub devices in the "on" level. Some
|
||||
drivers may change this setting when they are bound.
|
||||
|
||||
This file is deprecated and will be removed after 2010.
|
||||
This file is deprecated.
|
||||
Use the power/control file instead; it does exactly the
|
||||
same thing.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,21 @@ Description:
|
|||
default values. If there is an advertised remote stream
|
||||
with the same name, uses its values as the default.
|
||||
|
||||
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/busy_poll
|
||||
Date: Nov 2026
|
||||
KernelVersion: v7.3
|
||||
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
Description:
|
||||
Instead of using interrupts for completing the frames in
|
||||
the TX/RX rings, busy poll them directly from the
|
||||
read(2) and write(2) calls. This burns more CPU cycles
|
||||
but provides lower latency for applications that need it.
|
||||
|
||||
This also makes poll(2) return EPOLLERR because
|
||||
interrupts do not provide wakeup anymore. Likewise a
|
||||
blocking read(2) without available data busy-spins until
|
||||
data arrives or a signal is received.
|
||||
|
||||
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/index
|
||||
Date: Sep 2026
|
||||
KernelVersion: v7.2
|
||||
|
|
|
|||
|
|
@ -5,8 +5,5 @@ Description:
|
|||
The attributes:
|
||||
|
||||
audio_buf_size - audio buffer size
|
||||
fn_cap - capture pcm device file name
|
||||
fn_cntl - control device file name
|
||||
fn_play - playback pcm device file name
|
||||
req_buf_size - ISO OUT endpoint request buffer size
|
||||
req_count - ISO OUT endpoint request count
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ Description:
|
|||
|
||||
Supported values are 0 - 15.
|
||||
More information on how besl values map to microseconds can be found in
|
||||
USB 2.0 ECN Errata for Link Power Management, section 4.10)
|
||||
USB 2.0 ECN Errata for Link Power Management, section 4.10
|
||||
|
||||
What: /sys/bus/usb/devices/.../rx_lanes
|
||||
Date: March 2018
|
||||
|
|
|
|||
|
|
@ -294,8 +294,8 @@ for the retimers::
|
|||
|
||||
This enumerates and adds the on-board retimers. Now retimer NVM can be
|
||||
upgraded in the same way than with cable connected (see previous
|
||||
section). However, the retimer is not disconnected as we are offline
|
||||
mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for
|
||||
section). However, the retimer is not disconnected as we are in offline
|
||||
mode, so after writing ``1`` to ``nvm_authenticate`` one should wait for
|
||||
5 or more seconds before running rescan again::
|
||||
|
||||
# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan
|
||||
|
|
|
|||
93
Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml
Normal file
93
Documentation/devicetree/bindings/usb/aspeed,dwc3.yaml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/aspeed,dwc3.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Aspeed SuperSpeed DWC3 USB SoC controller
|
||||
|
||||
maintainers:
|
||||
- Ryan Chen <ryan_chen@aspeedtech.com>
|
||||
|
||||
description:
|
||||
The common content of the node is defined in snps,dwc3.yaml.
|
||||
|
||||
select:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: aspeed,ast2700-xhci
|
||||
required:
|
||||
- compatible
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: aspeed,ast2700-xhci
|
||||
- const: snps,dwc3
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: Controller bus early clock
|
||||
- description: PHY reference clock
|
||||
- description: Controller suspend clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: bus_early
|
||||
- const: ref
|
||||
- const: suspend
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
phys:
|
||||
maxItems: 1
|
||||
|
||||
phy-names:
|
||||
const: usb3-phy
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- resets
|
||||
- phys
|
||||
- phy-names
|
||||
|
||||
allOf:
|
||||
- $ref: snps,dwc3.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/aspeed,ast2700-scu.h>
|
||||
#include <dt-bindings/reset/aspeed,ast2700-scu.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
bus {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
usb@12030000 {
|
||||
compatible = "aspeed,ast2700-xhci", "snps,dwc3";
|
||||
reg = <0x0 0x12030000 0x0 0x10000>;
|
||||
interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&syscon0 SCU0_CLK_GATE_PORTAUSB2CLK>,
|
||||
<&syscon0 SCU0_CLK_U2PHY_REFCLK>,
|
||||
<&syscon0 SCU0_CLK_U2PHY_CLK12M>;
|
||||
clock-names = "bus_early", "ref", "suspend";
|
||||
resets = <&syscon0 SCU0_RESET_PORTA_XHCI>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_usb3axh_default &pinctrl_usb2axh_default>;
|
||||
phys = <&uphy3a>;
|
||||
phy-names = "usb3-phy";
|
||||
dr_mode = "host";
|
||||
};
|
||||
};
|
||||
|
|
@ -50,6 +50,9 @@ properties:
|
|||
- const: otg
|
||||
- const: wakeup
|
||||
|
||||
iommus:
|
||||
maxItems: 1
|
||||
|
||||
port:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ properties:
|
|||
- if a USB DRD channel: first clock should be host and second
|
||||
one should be peripheral
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
maxItems: 4
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
|
|
@ -186,6 +190,9 @@ allOf:
|
|||
required:
|
||||
- clocks
|
||||
- clock-names
|
||||
else:
|
||||
properties:
|
||||
clock-names: false
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ properties:
|
|||
- if a USB DRD channel: first clock should be host and second
|
||||
one should be peripheral
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
maxItems: 4
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
|
|
@ -182,6 +186,7 @@ allOf:
|
|||
|
||||
else:
|
||||
properties:
|
||||
clock-names: false
|
||||
atmel,vbus-gpio: false
|
||||
atmel,oc-gpio: false
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ properties:
|
|||
- mediatek,mt8188-mtu3
|
||||
- mediatek,mt8192-mtu3
|
||||
- mediatek,mt8195-mtu3
|
||||
- mediatek,mt8196-mtu3
|
||||
- mediatek,mt8365-mtu3
|
||||
- const: mediatek,mtu3
|
||||
|
||||
|
|
@ -200,7 +201,10 @@ properties:
|
|||
103 - used by mt8195, IP0, specific 1.03;
|
||||
105 - used by mt8195, IP2, specific 1.05;
|
||||
106 - used by mt8195, IP3, specific 1.06;
|
||||
enum: [1, 2, 101, 102, 103, 105, 106]
|
||||
107 - used by mt8196, IP0, specific 1.07;
|
||||
108 - used by mt8196, IP1, specific 1.08;
|
||||
109 - used by mt8196, IP2, specific 1.09;
|
||||
enum: [1, 2, 101, 102, 103, 105, 106, 107, 108, 109]
|
||||
|
||||
mediatek,u3p-dis-msk:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ properties:
|
|||
- qcom,glymur-dwc3-mp
|
||||
- qcom,ipq4019-dwc3
|
||||
- qcom,ipq5018-dwc3
|
||||
- qcom,ipq5210-dwc3
|
||||
- qcom,ipq5332-dwc3
|
||||
- qcom,ipq5424-dwc3
|
||||
- qcom,ipq6018-dwc3
|
||||
|
|
@ -36,6 +37,7 @@ properties:
|
|||
- qcom,ipq8074-dwc3
|
||||
- qcom,ipq9574-dwc3
|
||||
- qcom,kaanapali-dwc3
|
||||
- qcom,maili-dwc3
|
||||
- qcom,milos-dwc3
|
||||
- qcom,msm8953-dwc3
|
||||
- qcom,msm8994-dwc3
|
||||
|
|
@ -60,6 +62,7 @@ properties:
|
|||
- qcom,sdx55-dwc3
|
||||
- qcom,sdx65-dwc3
|
||||
- qcom,sdx75-dwc3
|
||||
- qcom,shikra-dwc3
|
||||
- qcom,sm4250-dwc3
|
||||
- qcom,sm6115-dwc3
|
||||
- qcom,sm6125-dwc3
|
||||
|
|
@ -203,9 +206,11 @@ allOf:
|
|||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,ipq5210-dwc3
|
||||
- qcom,ipq5424-dwc3
|
||||
- qcom,ipq9574-dwc3
|
||||
- qcom,kaanapali-dwc3
|
||||
- qcom,maili-dwc3
|
||||
- qcom,msm8953-dwc3
|
||||
- qcom,msm8996-dwc3
|
||||
- qcom,msm8998-dwc3
|
||||
|
|
@ -218,6 +223,7 @@ allOf:
|
|||
- qcom,sdx55-dwc3
|
||||
- qcom,sdx65-dwc3
|
||||
- qcom,sdx75-dwc3
|
||||
- qcom,shikra-dwc3
|
||||
- qcom,sm6350-dwc3
|
||||
- qcom,sm8750-dwc3
|
||||
then:
|
||||
|
|
@ -497,6 +503,7 @@ allOf:
|
|||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,ipq5210-dwc3
|
||||
- qcom,ipq5424-dwc3
|
||||
- qcom,ipq9574-dwc3
|
||||
then:
|
||||
|
|
@ -543,6 +550,7 @@ allOf:
|
|||
- qcom,ipq4019-dwc3
|
||||
- qcom,ipq8064-dwc3
|
||||
- qcom,kaanapali-dwc3
|
||||
- qcom,maili-dwc3
|
||||
- qcom,qcs615-dwc3
|
||||
- qcom,qcs8300-dwc3
|
||||
- qcom,qdu1000-dwc3
|
||||
|
|
@ -556,6 +564,7 @@ allOf:
|
|||
- qcom,sdx55-dwc3
|
||||
- qcom,sdx65-dwc3
|
||||
- qcom,sdx75-dwc3
|
||||
- qcom,shikra-dwc3
|
||||
- qcom,sm6350-dwc3
|
||||
- qcom,sm6375-dwc3
|
||||
- qcom,sm8150-dwc3
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ properties:
|
|||
- renesas,usbhs-r9a07g044 # RZ/G2{L,LC}
|
||||
- renesas,usbhs-r9a07g054 # RZ/V2L
|
||||
- renesas,usbhs-r9a08g045 # RZ/G3S
|
||||
- renesas,usbhs-r9a08g046 # RZ/G3L
|
||||
- renesas,usbhs-r9a09g047 # RZ/G3E
|
||||
- renesas,usbhs-r9a09g056 # RZ/V2N
|
||||
- renesas,usbhs-r9a09g057 # RZ/V2H(P)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
$id: http://devicetree.org/schemas/usb/ti,tps6598x.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Texas Instruments 6598x Type-C Port Switch and Power Delivery controller
|
||||
title: Texas Instruments Type-C port switch and USB Power Delivery controllers
|
||||
|
||||
maintainers:
|
||||
- Bryan O'Donoghue <bryan.odonoghue@linaro.org>
|
||||
|
||||
description: |
|
||||
Texas Instruments 6598x Type-C Port Switch and Power Delivery controller
|
||||
Texas Instruments 6598x and 66993 Type-C Port Switch and Power Delivery
|
||||
controller.
|
||||
|
||||
A variant of this controller known as Apple CD321x or Apple ACE is also
|
||||
present on hardware with Apple SoCs such as the M1.
|
||||
|
|
@ -19,6 +20,7 @@ properties:
|
|||
compatible:
|
||||
enum:
|
||||
- ti,tps6598x
|
||||
- ti,tps66993
|
||||
- apple,cd321x
|
||||
- ti,tps25750
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,10 @@
|
|||
:校译:
|
||||
|
||||
|
||||
简易 Linux USB 驱动的致谢名单:
|
||||
Simple Linux USB 驱动项目致谢名单:
|
||||
|
||||
以下人员都为 Linux USB 驱动代码作出了贡献
|
||||
(按姓氏字母顺序排列)。我相信这份名单本应
|
||||
更长一些,但确实不容易维护。
|
||||
如需将自己加入名单,请提交补丁。
|
||||
以下人员都为 Linux USB 驱动代码作出过贡献(按姓氏字母顺序排列)。这份名
|
||||
单本该更长,只是确实不易维护;如果你也应列名其中,欢迎提交补丁把自己加上。
|
||||
|
||||
Georg Acher <acher@informatik.tu-muenchen.de>
|
||||
David Brownell <dbrownell@users.sourceforge.net>
|
||||
|
|
@ -41,123 +39,124 @@
|
|||
特别感谢:
|
||||
|
||||
Inaky Perez Gonzalez <inaky@peloncho.fis.ucm.es>
|
||||
感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd
|
||||
驱动中的大部分代码。我们从那项工作中学到了很多。
|
||||
感谢他牵头开发 Linux USB 驱动,并编写了 uusbd 驱动的大部分代码,我们
|
||||
从中学到了很多。
|
||||
|
||||
NetBSD 和 FreeBSD 的 USB 开发者们
|
||||
感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。
|
||||
|
||||
附加感谢:
|
||||
还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助
|
||||
(摘自 Inaky 驱动原始的 THANKS 文件):
|
||||
另外还要感谢:
|
||||
|
||||
以下公司曾帮助我们开发 Linux USB / UUSBD:
|
||||
以下公司和个人在硬件、支持、时间和开发工作上给予了帮助(摘自 Inaky
|
||||
驱动原始的 THANKS 文件):
|
||||
|
||||
- 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面为我
|
||||
提供支持。没想到能得到这么大的帮助。
|
||||
以下公司曾为 Linux USB / UUSBD 的开发提供帮助:
|
||||
|
||||
- USAR Systems 向我们提供了他们出色的 USB 评估套件,
|
||||
使我们能够测试 Linux USB 驱动对最新 USB 规范的符合性。
|
||||
USAR Systems 认识到保持开放操作系统与时俱进的重要性,
|
||||
并以硬件支持这个项目。感谢!
|
||||
- 3Com GmbH 捐赠了一台 ISDN Pro TA,并在技术问题和测试设备方面提供
|
||||
了大力支持。
|
||||
|
||||
- USAR Systems 向我们提供了出色的 USB 评估套件,使我们得以测试
|
||||
Linux USB 驱动对最新 USB 规范的符合性。USAR Systems 也认识到,
|
||||
让开放操作系统跟上时代很重要,因此以硬件支持了这个项目,在此
|
||||
致谢。
|
||||
|
||||
- 感谢英特尔提供的宝贵帮助。
|
||||
|
||||
- 我们与 Cherry 合作,使 Linux 成为首个内置 USB 支持的操作系统。
|
||||
Cherry 是全球最大的键盘制造商之一。
|
||||
|
||||
- CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI-to-USB
|
||||
控制卡,用于测试 OHCI 实现。
|
||||
- CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI 转 USB 控制卡,
|
||||
用于测试 OHCI 实现。
|
||||
|
||||
- 由于他们对我们的支持,Keytronic 可以放心,
|
||||
他们的键盘能卖给至少 300 万 Linux 用户中的一部分。
|
||||
- 有了他们的支持,Keytronic 可以确信,其键盘能够销售给至少 300 万
|
||||
Linux 用户中的一部分。
|
||||
|
||||
- ing büro h doran [http://www.ibhdoran.com]!
|
||||
在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的
|
||||
(我自己做的那个相当糟糕 :))。现在我知道该去哪里买漂亮的 USB
|
||||
配件了!
|
||||
- 特别感谢 ing büro h doran [http://www.ibhdoran.com]。
|
||||
在欧洲,想给主板配一个 PC 背板 USB 连接器几乎是不可能的(我自己
|
||||
做的那个效果并不好)。现在我知道该去哪里购买合适的 USB 配件了。
|
||||
|
||||
- Genius Germany 捐赠了一只 USB 鼠标,用于测试鼠标启动协议;
|
||||
他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢!
|
||||
他们还捐赠了 F-23 数字摇杆和 NetMouse Pro,在此致谢。
|
||||
|
||||
- AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB 驱动。
|
||||
AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux 在内的
|
||||
所有操作系统平台开放。
|
||||
- AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN Controller B1 USB
|
||||
驱动。AVM 是主动式和被动式 ISDN 控制器及基于 CAPI 2.0 软件的领先
|
||||
制造商。AVM B1 的主动式设计对包括 Linux 在内的所有操作系统平台
|
||||
开放。
|
||||
|
||||
- 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,
|
||||
使我们能够测试批量传输代码。
|
||||
- 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,使我们能够测试
|
||||
批量传输代码。
|
||||
|
||||
- 感谢 Logitech 捐赠了一只三轴 USB 鼠标。
|
||||
|
||||
Logitech 负责设计、制造并销售各种人机接口设备,
|
||||
在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的
|
||||
控制设备方面拥有悠久历史和丰富经验。
|
||||
Logitech 负责设计、制造并销售各种人机接口设备,在键盘、鼠标、轨迹球、
|
||||
摄像头、扬声器,以及面向游戏和专业用途的控制设备方面拥有悠久历史和
|
||||
丰富经验。
|
||||
|
||||
作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、
|
||||
摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户
|
||||
能在自己喜欢的操作系统上获得支持,并让所有 Linux 用户都能使用
|
||||
Logitech 以及其他 USB 硬件。
|
||||
作为这些设备广为人知的供应商和销售商,他们捐赠了 USB 鼠标、摇杆和
|
||||
扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户能在自己偏爱的
|
||||
操作系统上获得支持,并让所有 Linux 用户都能使用 Logitech 及其他
|
||||
USB 硬件。
|
||||
|
||||
Logitech 也是 1999 年 2 月 11 日维也纳 Linux 大会的官方赞助商,
|
||||
我们将在会上展示 Linux USB 工作的最新进展。
|
||||
|
||||
- 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI 内部实现中
|
||||
那些不为人知的角落。
|
||||
- 感谢 CATC 提供 USB Inspector,帮助我们看到 UHCI 内部实现中的那些
|
||||
隐秘角落。
|
||||
|
||||
- 感谢 Entrega 为开发工作提供 PCI 转 USB 卡、集线器和转换器产品。
|
||||
|
||||
- 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,
|
||||
让这个驱动得以写成。
|
||||
- 感谢 ConnectTech 提供 WhiteHEAT USB 转串口转换器以及相关文档,让
|
||||
这个驱动得以写成。
|
||||
|
||||
- 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,
|
||||
以及驱动开发过程中的宝贵建议。
|
||||
- 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,以及驱动
|
||||
开发过程中的宝贵建议。
|
||||
|
||||
另外还要感谢以下个人(嘿,顺序不分先后 :))
|
||||
另外还要感谢以下个人(排名不分先后):
|
||||
|
||||
- Oren Tirosh <orenti@hishome.net>,
|
||||
他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。
|
||||
- Oren Tirosh <orenti@hishome.net>
|
||||
他非常耐心地解答我反复提出的各种 USB 问题,并提供了许多有价值的
|
||||
想法。
|
||||
|
||||
- Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>,
|
||||
指出了致命 bug,并给出了宝贵建议。
|
||||
- Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>
|
||||
指出了严重问题,并给出了宝贵建议。
|
||||
|
||||
- Edmund Humemberger <ed@atnet.at>,他在公共关系与项目管理方面
|
||||
为 Linux-USB 项目付出了巨大的努力。
|
||||
- Edmund Humemberger <ed@atnet.at>,他在公共关系与项目管理方面为
|
||||
Linux-USB 项目付出了巨大的努力。
|
||||
|
||||
- Alberto Menegazzi <flash@flash.iol.it> 正在着手编写 UUSBD 文档,加油!
|
||||
- Alberto Menegazzi <flash@flash.iol.it> 正在着手编写 UUSBD 文档。
|
||||
|
||||
- Ric Klaren <ia_ric@cs.utwente.nl> 编写了很好的入门文档,
|
||||
与 Alberto 的作品形成良性竞争:)。
|
||||
- Ric Klaren <ia_ric@cs.utwente.nl> 编写了很好的入门文档,与
|
||||
Alberto 的作品形成了良性互补。
|
||||
|
||||
- Christian Groessler <cpg@aladdin.de>,感谢他在那些棘手细节上的帮助。
|
||||
- Christian Groessler <cpg@aladdin.de>,感谢他在诸多复杂细节上的帮助。
|
||||
|
||||
- Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,
|
||||
并提供了大量的改进意见。
|
||||
- Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,并提供了
|
||||
大量的改进意见。
|
||||
|
||||
- Fernando Herrera <fherrera@eurielec.etsit.upm.es>
|
||||
负责撰写、维护并不断补充那份期待已久、独一无二又精彩的
|
||||
UUSBD FAQ!太棒了!
|
||||
- Fernando Herrera <fherrera@eurielec.etsit.upm.es> 负责撰写、维护并
|
||||
持续补充那份期待已久、内容翔实的 UUSBD FAQ。
|
||||
|
||||
- Rasca Gmelch <thron@gmx.de> 重新启用了 raw 驱动,
|
||||
指出了一些错误,并启动了 uusbd-utils 软件包。
|
||||
- Rasca Gmelch <thron@gmx.de> 重新启用了 raw 驱动,指出了一些错误,并
|
||||
启动了 uusbd-utils 软件包。
|
||||
|
||||
- Peter Dettori <dettori@ozy.dec.com>,像疯了一样挖掘 bug,
|
||||
还提出了很多很酷的建议,太棒了!
|
||||
- Peter Dettori <dettori@ozy.dec.com>,持续发现问题,并提出了许多
|
||||
有价值的建议。
|
||||
|
||||
- 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、
|
||||
MIT X 联盟、TeX 社区等等,谢谢你们!
|
||||
- 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、MIT X 联盟、
|
||||
TeX 社区等,在此一并致谢。
|
||||
|
||||
- 特别感谢 Richard Stallman 创造了 Emacs!
|
||||
- 特别感谢 Richard Stallman 创造了 Emacs。
|
||||
|
||||
- 感谢 linux-usb 邮件列表的所有成员,读了那么多邮件——不开玩笑了,
|
||||
感谢你们提出的所有建议!
|
||||
- 感谢 linux-usb 邮件列表的所有成员阅读了大量邮件,并提出了诸多
|
||||
建议。
|
||||
|
||||
- 感谢 USB Implementers Forum 成员们的帮助与支持。
|
||||
|
||||
- Nathan Myers <ncm@cantrip.org>,感谢他的建议!
|
||||
(希望你喜欢 Cibeles 的派对。)
|
||||
- Nathan Myers <ncm@cantrip.org>,感谢他的建议。(也希望你喜欢
|
||||
Cibeles 的派对。)
|
||||
|
||||
- 感谢 Linus Torvalds 创建、开发并管理 Linux。
|
||||
- 感谢 Linus Torvalds 创立、开发并维护 Linux。
|
||||
|
||||
- Mike Smith、Craig Keithley、Thierry Giron 和 Janet Schank
|
||||
感谢他们让我认识到标准 USB 集线器其实也没那么“标准”,
|
||||
这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。
|
||||
感谢他们让我认识到,标准 USB 集线器其实一点也不“标准”;也正因
|
||||
如此,我们才能在标准集线器驱动中加入厂商特定的处理。
|
||||
|
|
|
|||
|
|
@ -20,33 +20,26 @@ Linux ACM 驱动 v0.16
|
|||
|
||||
0. 免责声明
|
||||
~~~~~~~~~~~
|
||||
本程序是自由软件;你可以在自由软件基金会发布的
|
||||
GNU 通用公共许可证第 2 版,或者(按你的选择)
|
||||
任何后续版本的条款下重新发布和/或修改它。
|
||||
本程序是自由软件;你可以在自由软件基金会发布的 GNU 通用公共许可证第 2 版,
|
||||
或者(按你的选择)任何后续版本的条款下重新发布和/或修改它。
|
||||
|
||||
发布本程序是希望它能发挥作用,但它不附带任何担保;
|
||||
甚至不包括对适销性或特定用途适用性的默示担保。
|
||||
详情见 GNU 通用公共许可证。
|
||||
发布本程序是希望它能发挥作用,但它不附带任何担保;甚至不包括对适销性或
|
||||
特定用途适用性的默示担保。详情见 GNU 通用公共许可证。
|
||||
|
||||
你应该已经随本程序收到了 GNU 通用公共许可证的副本;
|
||||
如果没有,请致信:Free Software Foundation, Inc., 59
|
||||
Temple Place, Suite 330, Boston, MA 02111-1307 USA。
|
||||
你应该已经随本程序收到了 GNU 通用公共许可证的副本;如果没有,请参见
|
||||
COPYING 文件。
|
||||
|
||||
如需联系作者,可发送电子邮件至 vojtech@suse.cz,
|
||||
或邮寄至:
|
||||
Vojtech Pavlik, Ucitelska 1576, Prague 8,
|
||||
182 00, Czech Republic。
|
||||
如需联系作者,可发送电子邮件至 vojtech@suse.cz,或邮寄至:
|
||||
Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00, Czech Republic。
|
||||
|
||||
为方便起见,软件包中已附带 GNU 通用公共许可证
|
||||
第 2 版:见 COPYING 文件。
|
||||
为方便起见,软件包中已附带 GNU 通用公共许可证第 2 版:见 COPYING 文件。
|
||||
|
||||
1. 使用方法
|
||||
~~~~~~~~~~~
|
||||
``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB
|
||||
通信设备类抽象控制模型(USB CDC ACM)规范的
|
||||
USB 调制解调器和 USB ISDN 终端适配器。
|
||||
``drivers/usb/class/cdc-acm.c`` 驱动适用于符合 USB 通信设备类抽象控制模型
|
||||
(USB CDC ACM)规范的 USB 调制解调器和 USB ISDN 终端适配器。
|
||||
|
||||
许多调制解调器支持此驱动,以下是我所知道的一些型号:
|
||||
已知支持该驱动的调制解调器包括:
|
||||
|
||||
- 3Com OfficeConnect 56k
|
||||
- 3Com Voice FaxModem Pro
|
||||
|
|
@ -56,17 +49,16 @@ USB 调制解调器和 USB ISDN 终端适配器。
|
|||
- Compaq 56k FaxModem
|
||||
- ELSA Microlink 56k
|
||||
|
||||
我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用:
|
||||
已知有一款 ISDN 终端适配器可以配合 ACM 驱动使用:
|
||||
|
||||
- 3Com USR ISDN Pro TA
|
||||
|
||||
一些手机也可以通过 USB 连接。
|
||||
我知道以下机型可以正常工作:
|
||||
一些手机也可以通过 USB 连接,已知可用的机型有:
|
||||
|
||||
- SonyEricsson K800i
|
||||
|
||||
遗憾的是,许多调制解调器和大多数 ISDN TA
|
||||
都使用专有接口,因此无法与此驱动配合工作。
|
||||
遗憾的是,很多调制解调器和大多数 ISDN TA 都使用专有接口,因此无法配合该
|
||||
驱动工作。
|
||||
购买前请先确认设备是否符合 ACM 规范。
|
||||
|
||||
要使用这些调制解调器,需要加载以下模块::
|
||||
|
|
@ -75,15 +67,13 @@ USB 调制解调器和 USB ISDN 终端适配器。
|
|||
uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko
|
||||
cdc-acm.ko
|
||||
|
||||
之后就应该可以访问这些调制解调器了。
|
||||
应当可以使用 ``minicom``、``ppp`` 和 ``mgetty``
|
||||
与它们通信。
|
||||
之后就应该能访问这些调制解调器,并用 ``minicom``、``ppp`` 和
|
||||
``mgetty`` 与它们通信。
|
||||
|
||||
2. 验证驱动是否正常工作
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
第一步是检查 ``/sys/kernel/debug/usb/devices``,
|
||||
其内容应该类似如下::
|
||||
第一步是查看 ``/sys/kernel/debug/usb/devices``,其内容应当类似下面这样::
|
||||
|
||||
T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
|
||||
B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0
|
||||
|
|
@ -112,11 +102,10 @@ USB 调制解调器和 USB ISDN 终端适配器。
|
|||
E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
|
||||
E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
|
||||
|
||||
这三行的存在很关键(以及 ``Cls=`` 字段里出现的
|
||||
``comm`` 和 ``data`` 类);它说明这是一个 ACM
|
||||
设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。
|
||||
如果只看到 ``Cls=ff(vend.)``,那就无能为力了:
|
||||
这说明你手上的设备使用的是厂商专有接口::
|
||||
关键是看这三行,再结合 ``Cls=`` 字段里出现的 ``comm`` 和 ``data`` 类,就
|
||||
能判断这是一台 ACM 设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。如果
|
||||
只看到 ``Cls=ff(vend.)``,那就说明这台设备使用的是厂商专有接口,ACM 驱动
|
||||
无法处理::
|
||||
|
||||
D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
|
||||
I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
|
||||
|
|
@ -142,6 +131,5 @@ USB 调制解调器和 USB ISDN 终端适配器。
|
|||
usb.c: acm driver claimed interface c7b5f3f8
|
||||
usb.c: acm driver claimed interface c7691fa0
|
||||
|
||||
如果以上都正常,请启动 ``minicom``,
|
||||
把它配置为连接 ``ttyACM`` 设备,然后
|
||||
尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。
|
||||
如果这些都正常,请启动 ``minicom``,把它配置为连接到 ``ttyACM`` 设备,然后
|
||||
尝试输入 ``at``。如果返回 ``OK``,说明驱动工作正常。
|
||||
|
|
|
|||
|
|
@ -10,34 +10,32 @@
|
|||
:校译:
|
||||
|
||||
|
||||
=============================
|
||||
授权或禁止 USB 设备连接到系统
|
||||
=============================
|
||||
===========================
|
||||
允许或禁止 USB 设备接入系统
|
||||
===========================
|
||||
|
||||
版权 (C) 2007 Inaky Perez-Gonzalez
|
||||
<inaky@linux.intel.com> 英特尔公司
|
||||
|
||||
此功能允许你控制 USB 设备是否可以在系统中使用。
|
||||
借助它,你可以完全通过用户空间实现对 USB 设备的锁定。
|
||||
有了这项功能,你就可以控制 USB 设备是否允许在系统中使用,并把 USB 设备锁
|
||||
定机制完全放在用户空间实现。
|
||||
|
||||
目前,当插入一个 USB 设备时,系统会对其进行配置,
|
||||
其接口会立即向用户开放。
|
||||
有了这项改动,只有在 root 授权设备完成配置后,
|
||||
设备才可被使用。
|
||||
目前,USB 设备一接入系统就会被立即配置,其接口也会立刻向用户开放。引入
|
||||
这项机制后,只有在 root 明确授权后,设备才会完成配置并允许使用。
|
||||
|
||||
|
||||
用法
|
||||
====
|
||||
|
||||
授权设备接入::
|
||||
授权设备接入系统::
|
||||
|
||||
$ echo 1 > /sys/bus/usb/devices/DEVICE/authorized
|
||||
|
||||
取消对设备的授权::
|
||||
取消设备授权::
|
||||
|
||||
$ echo 0 > /sys/bus/usb/devices/DEVICE/authorized
|
||||
|
||||
将新连接到 ``hostX`` 的设备默认设为未授权(即锁定)::
|
||||
将连接到 ``hostX`` 的新设备默认设为未授权(即锁定)::
|
||||
|
||||
$ echo 0 > /sys/bus/usb/devices/usbX/authorized_default
|
||||
|
||||
|
|
@ -45,15 +43,14 @@
|
|||
|
||||
$ echo 1 > /sys/bus/usb/devices/usbX/authorized_default
|
||||
|
||||
默认情况下,所有 USB 设备都是授权的。
|
||||
向 ``authorized_default`` 属性写入 ``2`` 会使内核
|
||||
默认只授权连接到内部 USB 端口的设备。
|
||||
默认情况下,所有 USB 设备都是授权的。向 ``authorized_default`` 属性写入
|
||||
``2`` 会使内核默认只授权连接到内部 USB 端口的设备。
|
||||
|
||||
系统锁定示例(比较粗糙)
|
||||
------------------------
|
||||
系统锁定示例(简化版)
|
||||
----------------------
|
||||
|
||||
假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入
|
||||
(例如某台带有外露 USB 端口的自助服务终端)::
|
||||
假设你想做一个锁定机制,只允许 XYZ 类型的设备接入(例如一台带有外露 USB
|
||||
端口的自助终端)::
|
||||
|
||||
启动系统
|
||||
rc.local ->
|
||||
|
|
@ -63,21 +60,18 @@
|
|||
echo 0 > $host/authorized_default
|
||||
done
|
||||
|
||||
给 udev 挂一个脚本,用于处理新插入的 USB 设备::
|
||||
为 udev 配置一个脚本,用于处理新插入的 USB 设备::
|
||||
|
||||
if device_is_my_type $DEV
|
||||
then
|
||||
echo 1 > $device_path/authorized
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
``device_is_my_type()`` 才是锁定方案真正见功夫的
|
||||
地方。仅仅检查 class、type 和 protocol 是否匹配
|
||||
某个值,是你能做出的最糟糕的安全验证之一;
|
||||
对想绕过它的人来说,这反而是最容易利用的方案。
|
||||
如果你需要真正安全的办法,那就该使用加密、
|
||||
证书认证之类的机制。把 USB 存储设备当作
|
||||
“钥匙”的一个简单例子可以是::
|
||||
锁定方案是否可靠,关键全在 ``device_is_my_type()`` 的实现。仅仅检查
|
||||
class、type 和 protocol 是否匹配,几乎是最差的一种安全校验方式;对想绕过
|
||||
它的人来说,这种做法反而最容易伪造。如果你真要做安全控制,就该使用加密、
|
||||
证书认证之类的机制。把 USB 存储设备当作“钥匙”的一个简单示例可以写成::
|
||||
|
||||
function device_is_my_type()
|
||||
{
|
||||
|
|
@ -87,7 +81,7 @@
|
|||
sum=$(md5sum /mntpoint/.signature)
|
||||
if [ $sum = $(cat /etc/lockdown/keysum) ]
|
||||
then
|
||||
echo "We are good, connected"
|
||||
echo "验证通过,已连接"
|
||||
umount /mntpoint
|
||||
# 再做一些额外处理,让其他人也能使用它
|
||||
else
|
||||
|
|
@ -96,17 +90,16 @@
|
|||
}
|
||||
|
||||
|
||||
当然,这个例子很粗糙;真正要做的话,
|
||||
你会想用基于 PKI 的证书校验,这样就不必依赖
|
||||
共享密钥之类的东西。不过你应该已经明白意思了。
|
||||
任何拿到设备仿真工具包的人都能伪造描述符和设备信息。
|
||||
别信这个。
|
||||
当然,这个例子仍然比较简化。真正落地时,更合适的做法是使用基于 PKI 的证
|
||||
书校验,这样就不必依赖共享密钥之类的机制了。不过意思已经很清楚:任何拿到
|
||||
设备仿真工具包的人,都能伪造描述符和设备信息,所以别把这类检查当成真正
|
||||
的安全保障。
|
||||
|
||||
接口授权
|
||||
--------
|
||||
|
||||
也有类似的方法用于允许或拒绝特定 USB 接口。
|
||||
这使得你可以只阻止某个 USB 设备中的部分接口。
|
||||
也可以用类似的方法允许或拒绝特定的 USB 接口。这样一来,你只需要阻止某个
|
||||
USB 设备中的部分接口。
|
||||
|
||||
授权接口::
|
||||
|
||||
|
|
@ -126,14 +119,12 @@
|
|||
|
||||
$ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default
|
||||
|
||||
默认情况下,
|
||||
``interface_authorized_default`` 位为 ``1``,
|
||||
因此所有接口默认都处于已授权状态。
|
||||
默认情况下,``interface_authorized_default`` 位为 ``1``,因此所有接口默认
|
||||
都会处于授权状态。
|
||||
|
||||
注意:
|
||||
如果把一个先前未授权的接口改为已授权,
|
||||
则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe``
|
||||
来手动触发驱动探测。
|
||||
如果把一个先前未授权的接口改为已授权,则必须通过将 ``INTERFACE`` 写入
|
||||
``/sys/bus/usb/drivers_probe`` 来手动触发驱动探测。
|
||||
|
||||
对于需要多个接口的驱动程序,应先授权所有必需接口,
|
||||
然后再触发驱动探测。这样做可以避免副作用。
|
||||
对于需要多个接口的驱动程序,应先授权所有必需接口,然后再触发驱动探测。
|
||||
这样做可以避免副作用。
|
||||
|
|
|
|||
|
|
@ -17,18 +17,17 @@ ChipIdea 高速双角色控制器驱动
|
|||
1. 如何测试 OTG FSM(HNP 和 SRP)
|
||||
---------------------------------
|
||||
|
||||
下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,
|
||||
说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。
|
||||
下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,演示如何通过 sysfs 属性
|
||||
测试 OTG 的 HNP 和 SRP 功能。
|
||||
|
||||
1.1 如何使能 OTG FSM
|
||||
1.1 如何启用 OTG FSM
|
||||
--------------------
|
||||
|
||||
1.1.1 在 ``menuconfig`` 中选择 ``CONFIG_USB_OTG_FSM``,并重新编译内核
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
重新构建内核镜像和模块。如果想查看 OTG FSM 的
|
||||
一些内部变量,可以挂载 ``debugfs``;其中有两个文件
|
||||
可以显示 OTG FSM 变量以及部分控制器寄存器的值::
|
||||
重新构建内核镜像和模块。如果想查看 OTG FSM 的内部变量,可以挂载
|
||||
``debugfs``;其中有两个文件,分别显示 OTG FSM 的变量和部分控制器寄存器值::
|
||||
|
||||
cat /sys/kernel/debug/ci_hdrc.0/otg
|
||||
cat /sys/kernel/debug/ci_hdrc.0/registers
|
||||
|
|
@ -44,11 +43,10 @@ ChipIdea 高速双角色控制器驱动
|
|||
1.2 测试步骤
|
||||
------------
|
||||
|
||||
1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,
|
||||
并加载 gadget 类驱动(例如 ``g_mass_storage``)。
|
||||
1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,并加载 gadget 类驱动(例如
|
||||
``g_mass_storage``)。
|
||||
|
||||
2) 用 USB 线连接两块开发板:
|
||||
一端是 micro A 插头,另一端是 micro B 插头。
|
||||
2) 用 USB 线连接两块开发板:一端是 micro A 插头,另一端是 micro B 插头。
|
||||
|
||||
插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。
|
||||
|
||||
|
|
@ -66,32 +64,28 @@ ChipIdea 高速双角色控制器驱动
|
|||
|
||||
echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
|
||||
|
||||
或者,通过引入 HNP 轮询,B 端主机可以知道
|
||||
A 端外设希望切换为主机角色,因此这次角色切换
|
||||
也可以通过 A 端外设响应 B 端主机的轮询,
|
||||
在 A 侧触发。
|
||||
这可以通过在 A 设备上执行下面的命令来完成::
|
||||
或者,也可以借助 HNP 轮询,让 B 端主机知道 A 端外设希望切回主机角色。
|
||||
因此,这次切换也可以由 A 侧触发,也就是由 A 端外设响应 B 端主机的轮询
|
||||
来完成。可在 A 设备上执行下面的命令::
|
||||
|
||||
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
|
||||
|
||||
A 设备应切回主机角色并枚举 B 设备。
|
||||
|
||||
5) 拔掉 B 设备(拔掉 micro B 插头),
|
||||
并在 10 秒内重新插入;
|
||||
5) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒内重新插入。
|
||||
A 设备应重新枚举 B 设备。
|
||||
|
||||
6) 拔掉 B 设备(拔掉 micro B 插头),
|
||||
并在 10 秒后重新插入;
|
||||
6) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒后重新插入。
|
||||
A 设备不应重新枚举 B 设备。
|
||||
|
||||
如果 A 设备希望使用总线:
|
||||
如果 A 设备还想继续使用总线:
|
||||
|
||||
在 A 设备上执行::
|
||||
|
||||
echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
|
||||
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
|
||||
|
||||
如果 B 设备希望使用总线:
|
||||
如果 B 设备想使用总线:
|
||||
|
||||
在 B 设备上执行::
|
||||
|
||||
|
|
@ -111,40 +105,41 @@ ChipIdea 高速双角色控制器驱动
|
|||
|
||||
echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
|
||||
|
||||
A 设备应恢复 USB 总线并枚举 B 设备。
|
||||
A 设备应恢复 USB 总线,并枚举 B 设备。
|
||||
|
||||
1.3 参考文档
|
||||
------------
|
||||
《On-The-Go and Embedded Host Supplement
|
||||
to the USB Revision 2.0 Specification
|
||||
《On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification
|
||||
July 27, 2012 Revision 2.0 version 1.1a》
|
||||
|
||||
2. 如何将 USB 用作系统唤醒源
|
||||
----------------------------
|
||||
下面是在 i.MX6 平台上把 USB 用作系统唤醒源的示例。
|
||||
下面给出在 i.MX6 平台上将 USB 用作系统唤醒源的示例。
|
||||
|
||||
2.1 使能核心控制器的唤醒功能::
|
||||
2.1 启用核心控制器的唤醒功能::
|
||||
|
||||
echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup
|
||||
|
||||
2.2 使能 glue 层的唤醒功能::
|
||||
2.2 启用 glue 层的唤醒功能::
|
||||
|
||||
echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup
|
||||
|
||||
2.3 使能 PHY 的唤醒功能(可选)::
|
||||
2.3 启用 PHY 的唤醒功能(可选)::
|
||||
|
||||
echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup
|
||||
|
||||
2.4 使能根集线器的唤醒功能::
|
||||
2.4 启用根集线器的唤醒功能::
|
||||
|
||||
echo enabled > /sys/bus/usb/devices/usb1/power/wakeup
|
||||
|
||||
2.5 使能相关设备的唤醒功能::
|
||||
2.5 启用相关设备的唤醒功能::
|
||||
|
||||
echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
|
||||
|
||||
如果系统只有一个 USB 端口,
|
||||
而你希望在该端口上启用 USB 唤醒功能,
|
||||
可以使用下面的脚本::
|
||||
如果系统只有一个 USB 端口,而你希望在该端口上启用 USB 唤醒功能,可以使用
|
||||
下面的脚本::
|
||||
|
||||
for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done;
|
||||
for i in $(find /sys -name wakeup | grep usb)
|
||||
do
|
||||
echo enabled > $i
|
||||
done
|
||||
|
|
|
|||
|
|
@ -18,46 +18,43 @@ DWC3 驱动
|
|||
待办
|
||||
~~~~
|
||||
|
||||
阅读时如果想顺手认领点任务,可以从下面挑一项 :)
|
||||
如果你愿意接手其中一项任务,可以从下面选择:
|
||||
|
||||
- 将中断处理程序改为每个端点各自使用线程化 IRQ
|
||||
|
||||
事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。
|
||||
当前代码会一直自旋等待命令完成,这种设计并不好。
|
||||
实践表明,某些 DWC3 命令大约需要 ``~1 ms`` 才能完成。当前代码会一直自旋
|
||||
等待命令完成,这并不是好办法。
|
||||
|
||||
实现思路:
|
||||
|
||||
- DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。
|
||||
中断号在探测(``probe``)阶段分配,并归属于该设备。
|
||||
如果硬件通过 ``MSI`` 为每个端点提供独立中断,
|
||||
那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。
|
||||
- DWC 核心实现了一个按端点分发中断的 IRQ 控制器。中断号在探测
|
||||
(``probe``)阶段分配,并归属于该设备。如果硬件通过 ``MSI`` 为每个
|
||||
端点提供独立中断,那么这个“虚拟”IRQ 控制器就可以被真实的端点中断
|
||||
取代。
|
||||
|
||||
- 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,
|
||||
在调用 ``usb_ep_disable()`` 时释放中断资源。
|
||||
最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。
|
||||
- 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,在调用
|
||||
``usb_ep_disable()`` 时释放中断资源。最坏情况下需要 32 个中断,最少是
|
||||
``ep0/1`` 的两个中断。
|
||||
- ``dwc3_send_gadget_ep_cmd()`` 将在 ``wait_for_completion_timeout()``
|
||||
中休眠,直到命令完成。
|
||||
- 中断处理程序分为以下几个部分:
|
||||
|
||||
- 设备级主中断处理程序
|
||||
遍历每个事件,并对其调用 ``generic_handle_irq()``。
|
||||
从 ``generic_handle_irq()`` 返回后,确认事件计数器,使中断最终消失。
|
||||
遍历每个事件,并调用 ``generic_handle_irq()`` 处理。返回后再确认
|
||||
事件计数器,让中断最终消失。
|
||||
|
||||
- 设备级线程化处理程序
|
||||
无。
|
||||
|
||||
- 端点中断的主处理程序
|
||||
读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。
|
||||
事件保存在每个端点的数据结构中。
|
||||
还要注意,一旦把某项工作交给线程处理,
|
||||
就不要再在主处理程序里处理它,
|
||||
以免出现优先级反转之类的问题。
|
||||
读取事件并尽量处理;凡是需要睡眠的操作都交给线程处理。事件保存在
|
||||
每个端点的数据结构中。一旦某项工作已经交给线程处理,主处理程序里就
|
||||
不要再碰它,以免出现优先级反转之类的问题。
|
||||
|
||||
- 端点中断的线程化处理程序
|
||||
处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。
|
||||
|
||||
延迟:
|
||||
延迟:
|
||||
|
||||
不应增加延迟,因为中断线程具有较高优先级,
|
||||
会在普通用户态任务之前运行
|
||||
不应增加额外延迟,因为中断线程优先级较高,会在普通用户任务之前运行
|
||||
(除非用户更改了调度优先级)。
|
||||
|
|
|
|||
|
|
@ -14,45 +14,37 @@
|
|||
EHCI 驱动
|
||||
=========
|
||||
|
||||
2002年12月27日
|
||||
2002 年 12 月 27 日
|
||||
|
||||
EHCI 驱动用于通过支持 USB 2.0 的主机控制器
|
||||
硬件与高速 USB 2.0 设备通信。USB 2.0 兼容
|
||||
USB 1.1 标准,它定义了三种传输速率:
|
||||
EHCI 驱动用于借助支持 USB 2.0 的主机控制器,与高速 USB 2.0 设备通信。USB
|
||||
2.0 向下兼容 USB 1.1,并定义了三种传输速率:
|
||||
|
||||
- “高速”(High Speed)480 Mbit/sec(60 MByte/sec)
|
||||
- “全速”(Full Speed)12 Mbit/sec(1.5 MByte/sec)
|
||||
- “低速”(Low Speed)1.5 Mbit/sec
|
||||
|
||||
USB 1.1 仅支持全速与低速。
|
||||
高速设备可以在 USB 1.1 系统上使用,
|
||||
但速度会降到 USB 1.1 的速率。
|
||||
USB 1.1 仅支持全速与低速。高速设备可以在 USB 1.1 系统上使用,但速度会
|
||||
降到 USB 1.1 的速率。
|
||||
|
||||
USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
|
||||
插入 EHCI 控制器时,会被交由 USB 1.1 的伴随
|
||||
(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
|
||||
USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们插入 EHCI 控制器时,会交给
|
||||
USB 1.1 的伴随(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
|
||||
|
||||
当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过
|
||||
集线器中的事务转换器(Transaction Translator,TT)
|
||||
与 EHCI 控制器交互,该转换器将低速或全速事务转换为
|
||||
高速分割事务,从而避免浪费传输带宽。
|
||||
当 USB 1.1 设备插入 USB 2.0 集线器时,它们会通过集线器里的事务转换器
|
||||
(Transaction Translator,TT)与 EHCI 控制器通信。该转换器会把低速或全
|
||||
速事务转换为高速分割事务,从而避免浪费传输带宽。
|
||||
|
||||
截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行
|
||||
(按字母顺序):Intel、NEC、Philips 和 VIA。
|
||||
其他供应商的 EHCI 实现正在陆续问世;
|
||||
预计该驱动在这些实现上也可正常运行。
|
||||
截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行(按字母顺序):
|
||||
Intel、NEC、Philips 和 VIA。随着其他供应商的 EHCI 实现陆续问世,预计该
|
||||
驱动在那些实现上也能正常运行。
|
||||
|
||||
自 2001 年年中起,usb-storage 设备就已可用
|
||||
(在 2.4 版该驱动上速度相当不错),
|
||||
集线器则直到 2001 年底才开始可用,而其他类型的高速设备
|
||||
似乎要等到更多系统内置 USB 2.0 后才会出现。
|
||||
这类新系统从 2002 年初开始上市,
|
||||
并在 2002 年下半年变得更加常见。
|
||||
自 2001 年年中起,usb-storage 设备就已可用(在 2.4 版该驱动上速度表现相当
|
||||
不错),集线器则直到 2001 年底才开始可用。其他类型的高速设备似乎要等到
|
||||
更多系统内置 USB 2.0 后才会出现。这类新系统从 2002 年初开始上市,并在
|
||||
2002 年下半年变得更加常见。
|
||||
|
||||
注意,USB 2.0 支持并不只是 EHCI 本身。
|
||||
它还需要对 Linux-USB 核心 API 作出其他修改,
|
||||
包括 hub 驱动;不过这些修改并不需要真正改变
|
||||
暴露给 USB 设备驱动的基本 ``usbcore`` API。
|
||||
注意,USB 2.0 的支持并不只靠 EHCI 本身。它还需要对 Linux-USB 核心 API
|
||||
做其他修改,包括 hub 驱动;不过这些修改并不需要真正改变向 USB 设备驱动
|
||||
暴露的基本 ``usbcore`` API。
|
||||
|
||||
- David Brownell
|
||||
<dbrownell@users.sourceforge.net>
|
||||
|
|
@ -61,58 +53,46 @@ USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
|
|||
功能
|
||||
====
|
||||
|
||||
该驱动会定期在 x86 硬件上进行测试,
|
||||
也已在 PPC 硬件上使用,因此大小端问题应当已经解决。
|
||||
因此可以认为,它已经处理好了所有必要的 PCI 细节,
|
||||
所以即便在 DMA 映射有些特殊的系统上,
|
||||
I/O 也应能正常运行。
|
||||
该驱动长期在 x86 硬件上接受测试,也在 PPC 平台上使用过,因此大小端问题应
|
||||
该都已解决。再加上各种必要的 PCI 细节都已处理妥当,即便在 DMA 映射较特
|
||||
殊的系统上,I/O 也应能正常工作。
|
||||
|
||||
传输类型
|
||||
--------
|
||||
|
||||
截至本文撰写时,该驱动应当已经能够很好地处理
|
||||
所有控制传输、批量传输和中断传输,
|
||||
包括通过 USB 2.0 集线器中的事务转换器
|
||||
与 USB 1.1 设备通信;但仍可能存在 bug。
|
||||
截至本文撰写时,该驱动应当已经能够稳定处理所有控制传输、批量传输和中断传
|
||||
输,包括经由 USB 2.0 集线器里的事务转换器访问 USB 1.1 设备;不过仍可能
|
||||
存在 bug。
|
||||
|
||||
高速等时(ISO)传输支持也已可用,但截至本文撰写时,
|
||||
还没有 Linux 驱动使用这项支持。
|
||||
高速等时(ISO)传输支持也已可用,不过截至本文撰写时,还没有 Linux 驱动真
|
||||
正使用它。
|
||||
|
||||
目前尚不支持通过事务转换器实现全速等时传输。
|
||||
需要注意,ISO 传输的 split transaction 支持
|
||||
与高速 ISO 传输几乎无法共用代码,
|
||||
因为 EHCI 用不同的数据结构表示它们。
|
||||
因此,目前大多数 USB 音频和视频设备
|
||||
还不能通过高速总线连接使用。
|
||||
目前尚不支持通过事务转换器实现全速等时传输。需要注意,ISO 传输的分割
|
||||
事务支持与高速 ISO 传输几乎无法共用代码,因为 EHCI 用不同的数据结构表示
|
||||
它们。因此,目前大多数 USB 音频和视频设备还无法在高速总线上使用。
|
||||
|
||||
驱动行为
|
||||
--------
|
||||
|
||||
所有类型的传输都可以排队。
|
||||
这意味着来自一个接口驱动的控制传输
|
||||
(或通过 usbfs 发出的控制传输)不会干扰
|
||||
另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期,
|
||||
而不必担心中断处理开销导致的数据丢失。
|
||||
所有类型的传输都可以排队提交。这意味着某个接口驱动发出的控制传输(或经由
|
||||
usbfs 提交的控制传输)不会干扰其他驱动的控制传输,而中断传输可以按 1 帧
|
||||
周期运行,不必担心中断处理开销导致数据丢失。
|
||||
|
||||
|
||||
EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
|
||||
该驱动不需要了解那些驱动的任何细节;
|
||||
一个原本就能正常工作的 OHCI 或 UHCI 驱动,
|
||||
并不会因为 EHCI 驱动也存在而需要更改。
|
||||
EHCI 根集线器代码会将 USB 1.1 设备交给其伴随控制器处理。该驱动无需了解
|
||||
那些驱动的任何细节;一个原本就能正常工作的 OHCI 或 UHCI 驱动,也不会因为
|
||||
EHCI 驱动存在而需要修改。
|
||||
|
||||
电源管理方面还有一些问题;
|
||||
当前挂起/恢复的行为还不完全正确。
|
||||
电源管理方面仍有一些问题;当前挂起/恢复行为还不完全正确。
|
||||
|
||||
此外,在调度周期性事务
|
||||
(中断和等时传输)时还采取了一些简化处理。
|
||||
这些简化会限制可调度的周期性事务数量,
|
||||
并且无法使用小于一帧的轮询间隔。
|
||||
此外,在调度周期性事务(中断和等时传输)时还采取了一些简化处理。这些
|
||||
简化会限制可调度的周期性事务数量,并且无法使用小于一帧的轮询间隔。
|
||||
|
||||
使用方式
|
||||
========
|
||||
|
||||
假设有一个 EHCI 控制器(位于 PCI 卡或主板上),
|
||||
并且已将此驱动编译为模块,可这样加载::
|
||||
假设系统中有一个 EHCI 控制器(位于 PCI 卡或主板上),并且此驱动是以模块形
|
||||
式编译的,那么可以这样加载::
|
||||
|
||||
# modprobe ehci-hcd
|
||||
|
||||
|
|
@ -120,27 +100,24 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
|
|||
|
||||
# rmmod ehci-hcd
|
||||
|
||||
还应加载一个伴随控制器驱动,
|
||||
例如 ``ohci-hcd`` 或 ``uhci-hcd``。
|
||||
如果 EHCI 驱动出现任何问题,只需卸载它的模块,
|
||||
随后该伴随控制器驱动就会接手
|
||||
此前由 EHCI 驱动处理的所有设备
|
||||
(但速度会降低)。
|
||||
还应加载一个伴随控制器驱动,例如 ``ohci-hcd`` 或 ``uhci-hcd``。如果 EHCI
|
||||
驱动出了问题,只要卸载它的模块,伴随控制器驱动就会接管此前由 EHCI 驱动处
|
||||
理的全部设备(只是速度会降低)。
|
||||
|
||||
模块参数(传给 ``modprobe``)包括:
|
||||
|
||||
log2_irq_thresh(默认值 0):
|
||||
默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧
|
||||
(125 微秒)。最大值 6 表示 2^6 = 64 个微帧。
|
||||
默认中断延迟的 log2 值,单位为微帧。默认值 0 表示 1 个微帧
|
||||
(125 微秒),最大值 6 表示 2^6 = 64 个微帧。
|
||||
该值控制 EHCI 控制器发出中断的频率。
|
||||
|
||||
如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,
|
||||
则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件:
|
||||
如果你在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,那么任一 EHCI 控
|
||||
制器对应的 ``sysfs`` 目录下都会看到三个文件:
|
||||
|
||||
``async``
|
||||
转储异步调度,用于控制传输和批量传输。它会显示每个活动的 ``qh``
|
||||
以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。
|
||||
(可以在 ``usb-storage`` 做磁盘 I/O 时看它;顺便观察请求队列!)
|
||||
(可以在 ``usb-storage`` 执行磁盘 I/O 时查看;也可顺便观察请求队列。)
|
||||
|
||||
``periodic``
|
||||
转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。
|
||||
|
|
@ -151,111 +128,81 @@ EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
|
|||
这些文件的内容有助于定位驱动问题。
|
||||
|
||||
|
||||
设备驱动通常不需要关心自己是否运行在 EHCI 之上,
|
||||
但它们可能想检查
|
||||
``usb_device->speed == USB_SPEED_HIGH``。
|
||||
高速设备能做到全速(或低速)设备做不到的事,
|
||||
例如高带宽的周期性传输(中断或 ISO 传输)。
|
||||
另外,设备描述符中的某些值
|
||||
(例如周期性传输的轮询间隔)
|
||||
在高速模式下使用不同的编码方式。
|
||||
设备驱动通常不需要关心自己是否运行在 EHCI 之上,但有时可能会想检查
|
||||
``usb_device->speed == USB_SPEED_HIGH``。高速设备能做到全速(或低速)设备
|
||||
做不到的事,例如高带宽的周期性传输(中断或 ISO 传输)。另外,设备描述符
|
||||
中的某些值(例如周期性传输的轮询间隔)在高速模式下使用不同的编码方式。
|
||||
|
||||
不过,一定要让设备驱动经过 USB 2.0 集线器的测试。
|
||||
当使用事务转换器时,这些集线器报告某些故障
|
||||
(例如断开连接)的方式会不同;
|
||||
已经见过一些驱动在遇到与 OHCI 或 UHCI
|
||||
所报告的不同故障时表现不佳。
|
||||
不过,设备驱动一定要在 USB 2.0 集线器后面测一遍。使用事务转换器时,这些
|
||||
集线器报告某些故障(例如断开连接)的方式会有所不同;已经见过一些驱动在
|
||||
遇到与 OHCI 或 UHCI 不同的故障时表现不佳。
|
||||
|
||||
性能
|
||||
====
|
||||
|
||||
USB 2.0 吞吐量主要受两个因素制约:
|
||||
主机控制器处理请求的速度,以及设备响应这些请求的速度。
|
||||
480 Mbit/sec 的“原始传输率”对所有设备都成立,
|
||||
但总吞吐量还会受到诸如单个高速包之间的延迟、
|
||||
驱动是否足够聪明,以及系统整体负载等因素的影响。
|
||||
延迟也是性能考量因素。
|
||||
USB 2.0 的吞吐量主要受两个因素制约:主机控制器处理请求的速度,以及设备响
|
||||
应这些请求的速度。480 Mbit/sec 的“原始传输率”对所有设备都一样,但整体吞
|
||||
吐量还会受到诸如高速包之间的间隔、驱动实现是否足够高效以及系统总体负载等
|
||||
因素影响。延迟同样是需要考虑的性能指标。
|
||||
|
||||
批量传输最常用于关注吞吐量的场景。
|
||||
需要记住的是,批量传输总是以 512 字节包为单位,
|
||||
而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。
|
||||
8 个 USB 2.0 微帧构成一个 USB 1.1 帧;
|
||||
一个微帧的时长是 1 毫秒 / 8 = 125 微秒。
|
||||
批量传输通常用于看重吞吐量的场景。需要记住的是,批量传输总是以 512 字节包
|
||||
为单位,而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。8 个 USB 2.0 微
|
||||
帧构成一个 USB 1.1 帧,因此一个微帧的时长就是 125 微秒。
|
||||
|
||||
因此,只要硬件和设备驱动软件都允许,
|
||||
批量传输可提供超过 50 MByte/sec 的带宽。
|
||||
周期性传输模式(等时和中断)允许使用更大的包大小,
|
||||
从而可以逼近所宣称的 480 Mbit/sec 传输率。
|
||||
因此,只要硬件和驱动实现都足够成熟,批量传输就可以提供 50 MByte/sec 以上
|
||||
的带宽。周期性传输模式(等时和中断)允许使用更大的包大小,从而可以逼近所
|
||||
宣称的 480 Mbit/sec 传输率。
|
||||
|
||||
硬件性能
|
||||
--------
|
||||
|
||||
截至本文撰写时,单个 USB 2.0 设备的最大传输速率
|
||||
通常约为 20 MByte/sec。
|
||||
这当然会随着时间改变:一些设备现在更快,一些更慢。
|
||||
截至本文撰写时,单个 USB 2.0 设备的最大传输速率通常约为 20 MByte/sec。
|
||||
这种情况当然会随时间变化:有些设备现在更快,有些则更慢。
|
||||
|
||||
第一代 NEC EHCI 实现似乎存在
|
||||
大约 28 MByte/sec 的硬件瓶颈。
|
||||
虽然这对单个 20 MByte/sec 的设备显然已经够用,
|
||||
但把三个这样的设备挂到同一总线上,
|
||||
并不能得到 60 MByte/sec。
|
||||
问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
|
||||
因此它每个微帧只会尝试 6 次(也许是 7 次)
|
||||
USB 事务,而不是 13 次。
|
||||
(对一个比其他产品早上市一年的芯片来说,
|
||||
这是个合理的妥协!)
|
||||
第一代 NEC EHCI 实现似乎存在大约 28 MByte/sec 的硬件瓶颈。虽然这对单个
|
||||
20 MByte/sec 的设备显然已经够用,但把三个这样的设备挂到同一总线上,并不
|
||||
能得到 60 MByte/sec。问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
|
||||
因此它每个微帧只会尝试 6 次(也许是 7 次)USB 事务,而不是 13 次。
|
||||
(对一款比其他产品早上市一年的芯片来说,这样的取舍也算合理。)
|
||||
|
||||
|
||||
预计较新的实现会在这方面做得更好,
|
||||
通过投入更多芯片面积来解决这个问题,
|
||||
使新的主板芯片组更接近 60 MByte/sec 的目标。
|
||||
这既包括 NEC 的更新实现,也包括其他厂商的芯片。
|
||||
预计较新的实现会在这方面做得更好,通过投入更多芯片面积来解决这个问题,
|
||||
使新的主板芯片组更接近 60 MByte/sec 的目标。这既包括 NEC 的更新实现,也
|
||||
包括其他厂商的芯片。
|
||||
|
||||
|
||||
主机从 EHCI 控制器收到“请求已完成”中断的最小延迟
|
||||
为一个微帧(125 微秒)。该延迟可以调节;
|
||||
驱动提供了一个模块选项。默认情况下,
|
||||
``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制
|
||||
或批量请求时,通常可以在不到 250 微秒内得知它已完成
|
||||
(具体取决于传输大小)。
|
||||
主机从 EHCI 控制器收到“请求已完成”中断的最小延迟为一个微帧
|
||||
(125 微秒)。该延迟可以调节;驱动提供了一个模块选项。
|
||||
默认情况下,``ehci-hcd`` 使用最小延迟,这意味着发出控制或批量请求后,通
|
||||
常不到 250 微秒就能得知它已经完成(具体取决于传输大小)。
|
||||
|
||||
软件性能
|
||||
--------
|
||||
|
||||
即便只是要达到 20 MByte/sec 的传输速率,
|
||||
Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。
|
||||
这意味着要发出较大的请求,
|
||||
或者在需要发出一连串小请求时使用批量请求排队。
|
||||
如果驱动未做到这一点,那么会直接从性能结果上表现出来。
|
||||
即便只是要达到 20 MByte/sec 的传输速率,Linux-USB 设备驱动也必须让 EHCI
|
||||
队列始终保持满载。这意味着要发出较大的请求,或者在需要发出一连串小请求
|
||||
时使用批量请求排队。如果驱动做不到这一点,性能就会明显受影响。
|
||||
|
||||
|
||||
在典型情况下,使用 ``usb_bulk_msg()``
|
||||
以 4 KB 块循环写出,
|
||||
会浪费超过一半的 USB 2.0 带宽。
|
||||
I/O 完成与驱动发出下一次请求之间的延迟,
|
||||
通常会比一次 I/O 本身耗时更长。
|
||||
如果同样的循环改用 16 KB 块,会好一些;
|
||||
若使用一连串 128 KB 块,则浪费会少得多。
|
||||
在典型场景下,如果使用 ``usb_bulk_msg()`` 以 4 KB 块循环写出,会浪费超过
|
||||
一半的 USB 2.0 带宽。I/O 完成与驱动发出下一次请求之间的空档,往往比一次
|
||||
I/O 本身耗时还长。如果同样的循环改用 16 KB 块,情况会好一些;若使用一连串
|
||||
128 KB 块,则浪费会少得多。
|
||||
|
||||
但与其依赖这么大的 I/O 缓冲区来提升同步 I/O 的效率,不如直接向主机控制器
|
||||
排队提交多个(批量)请求,然后等待它们全部完成(或在出错时取消)。这种
|
||||
URB 排队方式对所有 USB 1.1 主机控制器驱动同样适用。
|
||||
|
||||
|
||||
但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效,
|
||||
不如直接向主机控制器排入多个(批量)请求,
|
||||
然后等待它们全部完成(或在出错时取消)。
|
||||
这种 URB 排队方式对所有 USB 1.1
|
||||
主机控制器驱动也同样适用。
|
||||
|
||||
|
||||
在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;
|
||||
它们会把 scatterlist 中的所有缓冲区都排入队列。
|
||||
它们还使用 scatterlist 的 DMA 映射
|
||||
(其中可能应用 IOMMU)并减少中断次数,
|
||||
这些都有助于让高速传输尽可能快地运行。
|
||||
在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;它们会把 scatterlist
|
||||
中的所有缓冲区都排入队列。它们还使用 scatterlist 的 DMA 映射(其中可能
|
||||
应用 IOMMU)并减少中断次数,这些都有助于让高速传输尽可能快地运行。
|
||||
|
||||
待办:
|
||||
中断传输和等时(ISO)传输的性能问题。
|
||||
这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。
|
||||
这些周期性传输都是完全调度的,因此主要问题可能在于如何触发高带宽模式。
|
||||
|
||||
待办:
|
||||
通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,
|
||||
可以分配超过标准 80% 的周期性带宽。
|
||||
通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,可以分配超过标准
|
||||
80% 的周期性带宽。
|
||||
后续将对此进行说明。
|
||||
|
|
|
|||
|
|
@ -1,4 +1,14 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
.. only:: subproject and latex
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\renewcommand{\thesection}{}
|
||||
\renewcommand{\thesubsection}{}
|
||||
\kerneldocCJKon
|
||||
\kerneldocBeginSC{
|
||||
|
||||
.. include:: ../disclaimer-zh_CN.rst
|
||||
|
||||
:Original: Documentation/usb/index.rst
|
||||
|
|
@ -24,7 +34,7 @@ USB 支持
|
|||
ehci
|
||||
usbmon
|
||||
|
||||
Todolist:
|
||||
待翻译文档:
|
||||
|
||||
* functionfs
|
||||
* functionfs-desc
|
||||
|
|
@ -52,3 +62,9 @@ Todolist:
|
|||
====
|
||||
|
||||
* :ref:`genindex`
|
||||
|
||||
.. only:: subproject and latex
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
}\kerneldocEndSC
|
||||
|
|
|
|||
|
|
@ -16,67 +16,56 @@ usbmon
|
|||
|
||||
简介
|
||||
====
|
||||
小写形式的 ``usbmon`` 指的是内核中的一项功能,
|
||||
用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具
|
||||
``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。
|
||||
类似地,人们希望使用 usbdump 或 USBMon
|
||||
(首字母大写)之类的工具来检查
|
||||
usbmon 生成的原始跟踪数据。
|
||||
小写的 ``usbmon`` 指的是内核中的一项功能,用于收集 USB 总线上的 I/O 跟踪
|
||||
信息。它类似于网络监控工具 ``tcpdump(1)`` 或 Ethereal 使用的数据包套接
|
||||
字。通常会用 usbdump 或 USBMon(首字母大写)之类的工具来查看 usbmon 生成
|
||||
的原始跟踪数据。
|
||||
|
||||
usbmon 报告的是各个外设驱动
|
||||
向主机控制器驱动(HCD)发出的请求。
|
||||
因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息
|
||||
可能无法精确对应实际的总线事务。
|
||||
这和 tcpdump 的情况是一样的。
|
||||
usbmon 记录的是各个设备驱动向主机控制器驱动(HCD)发出的请求。因此,如果
|
||||
HCD 自身有 bug,usbmon 输出的跟踪信息就未必能和真实的总线事务一一对应。
|
||||
这和 tcpdump 的情况类似。
|
||||
|
||||
目前实现了两种 API: ``text`` 和 ``binary``。
|
||||
二进制 API 通过 ``/dev`` 命名空间中的字符设备提供,
|
||||
并且属于 ABI。文本 API 自内核 2.6.35 起已废弃,
|
||||
但为了方便仍然可用。
|
||||
目前实现了两种 API:``text`` 和 ``binary``。二进制 API 通过 ``/dev`` 下的
|
||||
字符设备提供,是 ABI 的一部分。文本 API 自内核 2.6.35 起已废弃,但为了
|
||||
兼容和使用方便,至今仍然保留。
|
||||
|
||||
如何使用 usbmon 收集原始文本跟踪信息
|
||||
====================================
|
||||
|
||||
与数据包套接字不同,usbmon 提供了一种接口,
|
||||
可以输出文本格式的跟踪信息。这样做有两个目的:
|
||||
第一,在更完善的格式最终确定之前,
|
||||
它作为工具间通用的跟踪交换格式;
|
||||
第二,在不使用工具的情况下,人们也可以直接阅读这些信息。
|
||||
与数据包套接字不同,usbmon 还提供了一个输出文本格式跟踪信息的接口。这样
|
||||
做主要有两个目的:一是在更完善的格式最终确定之前,将其作为工具间通用的跟
|
||||
踪交换格式;二是在没有工具时也能直接阅读这些信息。
|
||||
|
||||
要收集原始文本跟踪信息,请按以下步骤进行操作。
|
||||
要收集原始文本跟踪信息,按下面的步骤做即可。
|
||||
|
||||
1. 准备
|
||||
-------
|
||||
|
||||
挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块
|
||||
(如果它是作为模块构建的)。如果 usbmon 已经编入内核,
|
||||
那么第二步可以省略。
|
||||
挂载 debugfs(内核配置里必须启用它),并加载 usbmon 模块(如果它是以模块
|
||||
方式构建的)。如果 usbmon 已经编译进内核,这一步就可以省略。
|
||||
|
||||
命令示例::
|
||||
|
||||
# mount -t debugfs none_debugs /sys/kernel/debug
|
||||
# mount -t debugfs none /sys/kernel/debug
|
||||
# modprobe usbmon
|
||||
#
|
||||
|
||||
确认总线套接字是否存在::
|
||||
确认 ``usbmon`` 目录下是否有这些条目::
|
||||
|
||||
# ls /sys/kernel/debug/usb/usbmon
|
||||
0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u
|
||||
#
|
||||
|
||||
现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包,
|
||||
并跳到第 3 步;
|
||||
也可以先按第 2 步找到目标设备所在的总线。
|
||||
这样可以过滤掉那些持续输出数据的烦人设备。
|
||||
现在,你可以直接用 ``0u`` 捕获所有总线上的数据包,然后跳到第 3 步;也可
|
||||
以先按第 2 步找出目标设备所在的总线。这样可以把那些持续产生流量的设备过
|
||||
滤掉。
|
||||
|
||||
2. 查找目标设备连接的是哪条总线
|
||||
-------------------------------
|
||||
|
||||
运行 ``cat /sys/kernel/debug/usb/devices``,
|
||||
找到对应设备的 T 行。通常可以通过厂商字符串来查找。
|
||||
如果有许多类似设备,可以拔掉其中一个,
|
||||
再比较前后两次 ``/sys/kernel/debug/usb/devices``
|
||||
的输出。T 行里会包含总线编号。
|
||||
运行 ``cat /sys/kernel/debug/usb/devices``,找到对应设备的 T 行。通常可以通过
|
||||
厂商字符串来查找。如果有很多相似设备,可以拔掉其中一个,再比较前后两次
|
||||
``/sys/kernel/debug/usb/devices`` 的输出。T 行里会包含总线编号。
|
||||
|
||||
示例::
|
||||
|
||||
|
|
@ -86,8 +75,8 @@ usbmon 报告的是各个外设驱动
|
|||
S: Manufacturer=ATEN
|
||||
S: Product=UC100KM V2.00
|
||||
|
||||
``Bus=03`` 表示它位于 3 号总线上。或者,
|
||||
也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。
|
||||
``Bus=03`` 表示它位于 3 号总线上。或者,也可以查看 ``lsusb`` 的输出,并从
|
||||
对应条目里找到总线编号。
|
||||
|
||||
示例如下::
|
||||
|
||||
|
|
@ -97,133 +86,110 @@ usbmon 报告的是各个外设驱动
|
|||
3. 启动 cat 命令
|
||||
----------------
|
||||
|
||||
如果只监听单条总线,可执行::
|
||||
如果只监听单条总线,执行::
|
||||
|
||||
# cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out
|
||||
|
||||
否则,如果要监听所有总线,则执行::
|
||||
否则,如果要监听所有总线,执行::
|
||||
|
||||
# cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out
|
||||
|
||||
此进程会一直读取,直到被终止。
|
||||
由于输出通常会很长,因此更推荐将输出重定向到某个位置。
|
||||
这个进程会一直运行到被终止为止。由于输出通常会很长,最好把它重定向到文件
|
||||
或其他位置。
|
||||
|
||||
|
||||
4. 在 USB 总线上执行期望的操作
|
||||
------------------------------
|
||||
|
||||
此处需要执行一些会产生 USB 流量的动作,
|
||||
比如插入 U 盘、拷贝文件、操作摄像头等。
|
||||
这里做一些会产生 USB 流量的操作即可,比如插入 U 盘、拷贝文件、操作摄像头
|
||||
等。
|
||||
|
||||
|
||||
5. 停止 cat
|
||||
-----------
|
||||
|
||||
这一步通常通过键盘中断(Control-C)完成。
|
||||
这一步通常按下键盘中断(Control-C)即可完成。
|
||||
|
||||
此时输出文件(本例中为 ``/tmp/1.mon.out``)
|
||||
可以保存、通过电子邮件发送,或使用文本编辑器查看。
|
||||
如果使用最后一种方式,请确保文件不会大到编辑器无法打开。
|
||||
此时,输出文件(本例中为 ``/tmp/1.mon.out``)可以保存下来,通过电子邮件发
|
||||
送,也可以用文本编辑器查看。如果要用文本编辑器查看,请确保文件大小不会
|
||||
大到编辑器无法处理。
|
||||
|
||||
|
||||
原始文本数据格式
|
||||
================
|
||||
|
||||
目前支持两种格式:原始格式,也就是 ``1t`` 格式,
|
||||
以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。
|
||||
``1u`` 格式增加了一些字段,例如 ISO 帧描述符、
|
||||
``interval`` 等。它生成的行会稍长一些,
|
||||
但在其他方面是 ``1t`` 格式的完整超集。
|
||||
目前支持两种格式:原始的 ``1t`` 格式和 ``1u`` 格式。``1t`` 格式在内核
|
||||
2.6.21 中已被废弃。``1u`` 格式增加了一些字段,例如 ISO 帧描述符和
|
||||
``interval``。它生成的行会稍长一些,但除此之外,它是 ``1t`` 格式的完整
|
||||
超集。
|
||||
|
||||
如果程序需要区分上述两种格式,
|
||||
可以查看 ``address`` 字段(见下文)。
|
||||
如果其中有两个冒号,就是 ``1t`` 格式;
|
||||
否则是 ``1u`` 格式。
|
||||
如果程序需要区分上述两种格式,可以查看 ``address`` 字段(见下文)。如果
|
||||
其中有两个冒号,就是 ``1t`` 格式;否则是 ``1u`` 格式。
|
||||
|
||||
任何文本格式的数据由一系列事件组成,
|
||||
如 URB 提交、URB 回调、提交错误等。
|
||||
每个事件对应单独的一行文本,
|
||||
由使用空白符间隔的若干字段组成。
|
||||
字段的数量与位置可能取决于事件类型,
|
||||
但以下字段对所有类型都通用:
|
||||
任何文本格式的数据都由一系列事件构成,例如 URB 提交、URB 回调和提交错
|
||||
误。每个事件占一行,由若干以空白符分隔的字段组成。字段数量和位置会随事件
|
||||
类型变化,但下面这些字段对所有类型都通用:
|
||||
|
||||
下面按从左到右的顺序列出这些共有字段:
|
||||
下面按从左到右的顺序说明这些通用字段:
|
||||
|
||||
- URB Tag。用于标识 URB,通常是 URB 结构体在内核中的地址
|
||||
(以十六进制表示),
|
||||
但也可能是序号或其他合理的唯一字符串。
|
||||
- URB 标识(URB Tag)。用于标识 URB,通常是 URB 结构体在内核中的地址
|
||||
(十六进制),也可能是序号或其他足以唯一标识 URB 的字符串。
|
||||
|
||||
- 时间戳(微秒),十进制数字。
|
||||
时间戳的精度取决于可用时钟,
|
||||
因此可能远差于
|
||||
1 微秒(例如实现使用的是 jiffies)。
|
||||
- 时间戳(微秒),十进制数字。时间戳的精度取决于可用时钟,所以可能远低于
|
||||
1 微秒(例如实现使用 jiffies 时)。
|
||||
|
||||
- 事件类型。它表示的是事件的格式,而不是 URB 的类型。
|
||||
可用值为:``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。
|
||||
- 事件类型。它表示的是这一行事件的格式,而不是 URB 的类型。可用值为:
|
||||
``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。
|
||||
|
||||
- ``Address`` 字段(以前称作 ``pipe``)。
|
||||
它包含四个由冒号分隔的字段:
|
||||
URB 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下:
|
||||
- ``Address`` 字段(以前称为 ``pipe``)。它包含四个由冒号分隔的字段:URB
|
||||
类型及方向、总线号、设备地址和端点号。类型与方向按下面的方式编码:
|
||||
|
||||
== == ==========================
|
||||
Ci Co 控制输入和输出
|
||||
Zi Zo 等时输入和输出
|
||||
Ii Io 中断输入和输出
|
||||
Bi Bo 批量输入和输出
|
||||
== == ==========================
|
||||
== == ====================
|
||||
Ci Co 控制输入与输出
|
||||
Zi Zo 等时输入与输出
|
||||
Ii Io 中断输入与输出
|
||||
Bi Bo 批量输入与输出
|
||||
== == ====================
|
||||
|
||||
总线号、设备地址和端点号使用十进制,但可能有前导零。
|
||||
总线号、设备地址和端点号都是十进制数,但可能有前导零,方便人阅读。
|
||||
|
||||
- URB 状态字段。这个字段要么是一个字母,
|
||||
要么是几个由冒号分隔的数字:
|
||||
URB 状态、``interval``、``start frame`` 和 ``error count``。
|
||||
与 ``address`` 字段不同,除了状态外,其余字段都是可选的。
|
||||
``interval`` 只会为中断和等时 URB 打印;``start frame`` 只会为
|
||||
等时 URB 打印;错误计数只会在等时回调事件中打印。
|
||||
- URB 状态字段。这个字段要么是一个字母,要么是几个用冒号分隔的数字,依次
|
||||
表示 URB 状态、``interval``、``start frame`` 和 ``error count``。与
|
||||
``address`` 字段不同,除状态外,其余字段都可能省略。``interval`` 只会在
|
||||
中断和等时 URB 中打印;``start frame`` 只会在等时 URB 中打印;错误计数只
|
||||
会在等时回调事件中打印。
|
||||
|
||||
状态字段是一个十进制数字,有时为负数,
|
||||
对应 URB 的 ``status`` 字段。
|
||||
对于提交事件,这个字段本身没有实际意义,
|
||||
但为了便于脚本解析,它仍然存在。
|
||||
当发生错误时,该字段包含错误码。
|
||||
状态字段是一个十进制数,有时为负数,对应 URB 的 ``status`` 字段。对于提
|
||||
交事件,这个字段本身并无实际语义,但为了便于脚本解析仍会保留。发生错误
|
||||
时,这里填的是错误码。
|
||||
|
||||
在提交控制包时,这个字段包含的是 ``Setup Tag``,
|
||||
而不是一组数字。
|
||||
判断 ``Setup Tag`` 是否存在很容易,因为它从来不是数字。
|
||||
因此,如果脚本在这个字段里发现的是一组数字,
|
||||
就会继续读取数据长度(等时 URB 除外)。
|
||||
如果发现的是其他内容,比如一个字母,
|
||||
那么脚本会先读取 ``Setup`` 包,再读取数据长度或等时描述符。
|
||||
如果是控制包的提交事件,这个字段里放的不是一组数字,而是 ``Setup Tag``。
|
||||
这很容易分辨,因为 ``Setup Tag`` 永远不是数字。所以脚本如果在这里读到一
|
||||
组数字,就会继续读取数据长度(等时 URB 除外);如果读到的是字母之类的内
|
||||
容,就要先读取 ``Setup`` 包,再读取数据长度或等时描述符。
|
||||
|
||||
- ``Setup`` 包由 5 个字段组成:
|
||||
``bmRequestType``、``bRequest``、``wValue``、
|
||||
``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。
|
||||
如果 ``Setup Tag`` 为 ``s``,就可以安全地解码这些字段。
|
||||
否则,说明 Setup 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。
|
||||
- ``Setup`` 包由 5 个字段组成:``bmRequestType``、``bRequest``、``wValue``、
|
||||
``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。如果 ``Setup Tag``
|
||||
是 ``s``,就可以安全解码这些字段。否则,说明 Setup 包虽然存在,但并未被
|
||||
捕获,此时各字段中会填入占位内容。
|
||||
|
||||
- 等时传输帧描述符的数量及其内容:
|
||||
如果一个等时传输事件带有一组描述符,首先打印该 URB 中描述符的总数,
|
||||
然后为每个描述符打印一个字段,最多打印 5 个字段。
|
||||
每个字段由三个用冒号分隔的十进制数字组成,
|
||||
分别表示状态(status)、偏移(offset)和长度(length)。
|
||||
对于提交(submission),报告的是初始长度;
|
||||
对于回调(callback),报告的是实际长度。
|
||||
如果某个等时传输事件带有描述符,会先打印该 URB 的描述符总数,再为每个描
|
||||
述符打印一个字段,最多 5 个。每个字段由三个用冒号分隔的十进制数组成,依
|
||||
次表示状态(status)、偏移(offset)和长度(length)。对于提交事件,报
|
||||
告的是初始长度;对于回调事件,报告的是实际长度。
|
||||
|
||||
- 数据长度:
|
||||
对于提交,表示请求的长度;对于回调,表示实际传输的长度。
|
||||
- 数据长度:对于提交,表示请求的长度;对于回调,表示实际传输的长度。
|
||||
|
||||
- 数据标签:
|
||||
即使数据长度非零,usbmon 也不一定会捕获数据。
|
||||
仅当标签为 ``=`` 时,才会有数据字段。
|
||||
- 数据标签:即使数据长度非零,usbmon 也不一定会捕获数据。只有标签为
|
||||
``=`` 时,才会有数据字段。
|
||||
|
||||
- 数据字段:
|
||||
以大端十六进制格式显示。注意,这些并不是真正的机器字,
|
||||
而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含
|
||||
1 到 4 个字节。
|
||||
收集的数据长度是有限的,可能小于数据长度字段中报告的值。
|
||||
因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区,
|
||||
所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下,
|
||||
收集的数据长度可能大于数据长度字段的值。
|
||||
- 数据字段:以大端十六进制格式显示。注意,这些并不是真正的机器字,只是为
|
||||
了便于阅读,把字节流按“字”分组显示。因此最后一个字可能只包含 1 到 4 个
|
||||
字节。捕获的数据长度是有限的,可能小于数据长度字段中报告的值。对于等时
|
||||
输入(Zi)完成事件,如果缓冲区里的接收数据比较稀疏,捕获数据的长度甚至
|
||||
可能大于数据长度字段,因为后者只统计实际接收到的字节,而数据字段展示的
|
||||
是整个传输缓冲区。
|
||||
|
||||
|
||||
|
||||
|
|
@ -234,18 +200,19 @@ usbmon 报告的是各个外设驱动
|
|||
d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 <
|
||||
d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000
|
||||
|
||||
向地址为 5 的存储设备发送
|
||||
31 字节 Bulk 包装的 SCSI 命令 ``0x28``
|
||||
(``READ_10``)的输出批量传输::
|
||||
向地址为 5 的存储设备发送一个输出批量传输,其中 31 字节的 Bulk 封装用于承
|
||||
载 SCSI 命令 ``0x28``(``READ_10``)。为便于排版,下面的第一条记录按两行
|
||||
显示,但实际 usbmon 输出仍是一行::
|
||||
|
||||
dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000
|
||||
dd65f0e8 4128379752 S Bo:1:005:2 -115 31 =
|
||||
55534243 ad000000 00800000 80010a28 20000000 20000040
|
||||
00000000 000000
|
||||
dd65f0e8 4128379808 C Bo:1:005:2 0 31 >
|
||||
|
||||
原始二进制格式与 API
|
||||
====================
|
||||
API 的整体架构与前文大体相同,只是事件以二进制格式传递。
|
||||
每个事件都通过下面的结构发送
|
||||
(这个名字是为了叙述方便而虚构的)::
|
||||
API 的整体架构与前文大体相同,只是事件以二进制格式传递。每个事件都通过
|
||||
下面的结构发送(这个结构名只是为了叙述方便而虚构的)::
|
||||
|
||||
|
||||
struct usbmon_packet {
|
||||
|
|
@ -275,29 +242,22 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
unsigned int ndesc; /* 60: 实际 ISO 描述符数量 */
|
||||
}; /* 64 总长度 */
|
||||
|
||||
可以用 ``read(2)``、``ioctl(2)``,
|
||||
或者通过 ``mmap`` 访问缓冲区,
|
||||
从字符设备接收这些事件。
|
||||
不过,出于兼容性原因,``read(2)``
|
||||
只返回前 48 个字节。
|
||||
可以用 ``read(2)``、``ioctl(2)``,或者通过 ``mmap`` 访问缓冲区,从字符设
|
||||
备接收这些事件。不过,出于兼容性原因,``read(2)`` 只返回前 48 个字节。
|
||||
|
||||
字符设备通常命名为 ``/dev/usbmonN``,
|
||||
其中 ``N`` 是 USB 总线号。
|
||||
编号为零的设备(``/dev/usbmon0``)比较特殊,
|
||||
表示“所有总线”。
|
||||
请注意,具体命名策略由 Linux 发行版决定。
|
||||
字符设备通常命名为 ``/dev/usbmonN``,其中 ``N`` 是 USB 总线号。编号为零的
|
||||
设备(``/dev/usbmon0``)比较特殊,表示“所有总线”。具体命名策略由 Linux
|
||||
发行版决定。
|
||||
|
||||
如果你手动创建 ``/dev/usbmon0``,
|
||||
请确保它归 root 所有,并且权限为 ``0600``。
|
||||
否则,非特权用户将能够窃听键盘流量。
|
||||
如果你手动创建 ``/dev/usbmon0``,请确保它归 root 所有,并且权限为 ``0600``。
|
||||
否则,非特权用户就能窃听键盘输入流量。
|
||||
|
||||
以下 ``MON_IOC_MAGIC`` 为 ``0x92`` 的 ioctl 调用可用:
|
||||
|
||||
``MON_IOCQ_URB_LEN``,定义为 ``_IO(MON_IOC_MAGIC, 1)``
|
||||
|
||||
该调用返回下一个事件的数据长度。
|
||||
注意大多数事件不包含数据,
|
||||
因此如果该调用返回零,并不意味着没有事件。
|
||||
该调用返回下一个事件的数据长度。注意大多数事件不包含数据,因此如果它返回
|
||||
零,并不意味着没有事件。
|
||||
|
||||
``MON_IOCG_STATS``,定义为
|
||||
``_IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)``
|
||||
|
|
@ -309,18 +269,16 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
u32 dropped;
|
||||
};
|
||||
|
||||
成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,
|
||||
而不是自上次重置以来处理过的事件数量。
|
||||
成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,而不是自上次重置以来
|
||||
处理过的事件数量。
|
||||
|
||||
成员 ``dropped`` 表示自上次调用
|
||||
``MON_IOCG_STATS`` 以来丢失的事件数量。
|
||||
成员 ``dropped`` 表示自上次调用 ``MON_IOCG_STATS`` 以来丢失的事件数量。
|
||||
|
||||
``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)``
|
||||
|
||||
此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。
|
||||
大小可能会向下取整到下一个块(或页)。
|
||||
如果请求的大小超出该内核的 [未指定] 范围,
|
||||
则调用会失败并返回 ``-EINVAL``。
|
||||
此调用设置缓冲区大小。参数是以字节为单位的缓冲区大小。大小可能会向下取整
|
||||
到下一个块(或页)。如果请求的大小超出当前内核允许的范围,则调用会失败并
|
||||
返回 ``-EINVAL``。
|
||||
|
||||
``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)``
|
||||
|
||||
|
|
@ -331,9 +289,8 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
``MON_IOCX_GETX``,定义为
|
||||
``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)``
|
||||
|
||||
如果内核缓冲区中没有事件,
|
||||
这些调用就会一直等待,直到有事件到达,
|
||||
然后返回第一个事件。
|
||||
如果内核缓冲区中没有事件,这些调用就会一直等待,直到有事件到达,然后返回
|
||||
第一个事件。
|
||||
参数是指向以下结构的指针::
|
||||
|
||||
struct mon_get_arg {
|
||||
|
|
@ -343,20 +300,18 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
};
|
||||
|
||||
|
||||
调用前,应填好 ``hdr``、``data`` 和 ``alloc``。
|
||||
调用返回后,``hdr`` 指向的区域中包含下一个事件的结构;
|
||||
如果存在数据,那么数据缓冲区中也会包含相应数据。
|
||||
该事件会从内核缓冲区中移除。
|
||||
调用前,应填好 ``hdr``、``data`` 和 ``alloc``。调用返回后,``hdr`` 指向的
|
||||
内存区域中会写入下一个事件的结构;如果存在数据,数据缓冲区中也会填入相应
|
||||
内容。该事件会从内核缓冲区中移除。
|
||||
|
||||
``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,
|
||||
``MON_IOCX_GETX`` 会复制 64 字节。
|
||||
``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,``MON_IOCX_GETX``
|
||||
会复制 64 字节。
|
||||
|
||||
``MON_IOCX_MFETCH``,定义为
|
||||
``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)``
|
||||
|
||||
当应用程序通过 ``mmap(2)`` 访问缓冲区时,
|
||||
主要使用这个 ioctl。
|
||||
其参数是指向以下结构的指针::
|
||||
应用程序通过 ``mmap(2)`` 访问缓冲区时,主要使用这个 ioctl。其参数是指向
|
||||
以下结构的指针::
|
||||
|
||||
struct mon_mfetch_arg {
|
||||
uint32_t *offvec; /* 获取的事件偏移向量 */
|
||||
|
|
@ -365,41 +320,36 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
};
|
||||
|
||||
|
||||
该 ioctl 的操作分为三个阶段:
|
||||
这个 ioctl 的流程分为三个阶段:
|
||||
|
||||
首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。
|
||||
实际丢弃的事件数量会写回 ``nflush``。
|
||||
首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。实际丢弃的事件数量会
|
||||
写回 ``nflush``。
|
||||
|
||||
其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待,
|
||||
直到缓冲区中出现事件。
|
||||
其次,除非设备以 ``O_NONBLOCK`` 打开,否则会一直等待,直到缓冲区中出现
|
||||
事件。
|
||||
|
||||
第三,将最多 ``nfetch`` 个偏移量提取到 mmap
|
||||
缓冲区,并存入 ``offvec`` 中。
|
||||
实际提取到的事件偏移数量会存回 ``nfetch``。
|
||||
第三,将最多 ``nfetch`` 个偏移量提取到 mmap 缓冲区,并存入 ``offvec`` 中。
|
||||
实际提取到的事件偏移数量会写回 ``nfetch``。
|
||||
|
||||
``MON_IOCH_MFLUSH``,定义为 ``_IO(MON_IOC_MAGIC, 8)``
|
||||
|
||||
此调用从内核缓冲区移除若干事件。
|
||||
其参数为要移除的事件数量。
|
||||
如果缓冲区中的事件少于请求数量,
|
||||
则移除所有事件,且不报告错误。
|
||||
当没有事件时也可使用。
|
||||
此调用从内核缓冲区移除若干事件。其参数是要移除的事件数量。如果缓冲区中的
|
||||
事件少于请求数量,则移除全部现有事件,且不报告错误。即使当前没有事件,也
|
||||
可以调用。
|
||||
|
||||
``FIONBIO``
|
||||
|
||||
如果有需要,将来可能会实现 ``FIONBIO`` ioctl。
|
||||
|
||||
除了 ``ioctl(2)`` 和 ``read(2)`` 之外,
|
||||
二进制 API 的特殊文件也可以用 ``select(2)`` 和
|
||||
``poll(2)`` 轮询。
|
||||
但 ``lseek(2)`` 不起作用。
|
||||
除了 ``ioctl(2)`` 和 ``read(2)`` 之外,二进制 API 对应的特殊文件还可以用
|
||||
``select(2)`` 和 ``poll(2)`` 轮询,但 ``lseek(2)`` 不可用。
|
||||
|
||||
* 二进制 API 的内核缓冲区内存映射访问
|
||||
|
||||
基本思想很简单:
|
||||
基本思路很简单:
|
||||
|
||||
准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。
|
||||
然后执行类似下面伪代码的循环::
|
||||
准备时,先查询当前大小,再用 ``mmap(2)`` 映射缓冲区。之后运行与下面伪代码
|
||||
类似的循环::
|
||||
|
||||
struct mon_mfetch_arg fetch;
|
||||
struct usbmon_packet *hdr;
|
||||
|
|
@ -411,7 +361,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
ioctl(fd, MON_IOCX_MFETCH, &fetch); // 同时处理错误
|
||||
nflush = fetch.nfetch; // 完成后要刷新这么多包
|
||||
for (i = 0; i < nflush; i++) {
|
||||
hdr = (struct ubsmon_packet *) &mmap_area[vec[i]];
|
||||
hdr = (struct usbmon_packet *) &mmap_area[vec[i]];
|
||||
if (hdr->type == '@') // 填充包
|
||||
continue;
|
||||
caddr_t data = &mmap_area[vec[i]] + 64;
|
||||
|
|
@ -421,7 +371,7 @@ API 的整体架构与前文大体相同,只是事件以二进制格式传递
|
|||
|
||||
|
||||
|
||||
因此,主要思想是每 N 个事件只执行一次 ioctl。
|
||||
因此,这里的核心思路就是每 N 个事件只执行一次 ioctl。
|
||||
|
||||
虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端,
|
||||
因此上面的伪代码无需任何合并操作。
|
||||
虽然缓冲区是环形的,但返回的头部和数据不会跨越缓冲区末端,因此上面的伪代
|
||||
码无需做任何拼接。
|
||||
|
|
|
|||
|
|
@ -72,6 +72,82 @@ have been written to their ep0's.
|
|||
Conversely, the gadget is unregistered after the first USB function
|
||||
closes its endpoints.
|
||||
|
||||
Endpoint IOCTLs
|
||||
===============
|
||||
|
||||
FunctionFS supports additional IOCTLs that can be performed on data endpoints
|
||||
(ie. not ep0). For a full list of these IOCTLs, please refer to the documentation
|
||||
in ``include/uapi/linux/usb/functionfs.h``.
|
||||
|
||||
One such IOCTL is:
|
||||
|
||||
``FUNCTIONFS_ENDPOINT_ENABLE_ZLP(__u32 *)``
|
||||
Enable or disable automatic zero-length packet (ZLP) appending for the
|
||||
endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to
|
||||
enable. When enabled, the kernel will automatically append a ZLP at the end
|
||||
of a transfer if the payload length is an exact multiple of the endpoint's
|
||||
max packet size. This is useful for compatibility with legacy protocols
|
||||
which require automatic ZLP appending to data written from userspace. This
|
||||
IOCTL can only be used on IN endpoints. It can be called at any time after
|
||||
the FunctionFS instance is active, even before the host has connected or
|
||||
enabled the endpoint. Returns zero on success, or a negative errno value on
|
||||
error:
|
||||
|
||||
* ``-ENODEV``: The FunctionFS instance is not active.
|
||||
* ``-EINVAL``: The endpoint is not an IN endpoint.
|
||||
* ``-EFAULT``: Invalid user space pointer for the argument.
|
||||
|
||||
RW Proxy Endpoints
|
||||
==================
|
||||
|
||||
If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header
|
||||
(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a
|
||||
bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair
|
||||
of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware
|
||||
endpoints, allowing userspace to use a single file descriptor for both reading
|
||||
(OUT) and writing (IN).
|
||||
|
||||
This flag requires the total number of hardware endpoints to be an even number.
|
||||
FunctionFS will automatically walk the provided endpoints and group them into
|
||||
adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the
|
||||
second pair). Each pair must consist of exactly one IN endpoint and one OUT
|
||||
endpoint.
|
||||
|
||||
For each valid pair, a rw_proxy file is created and named after the first
|
||||
endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are
|
||||
paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired,
|
||||
"ep3_rw" is created.
|
||||
|
||||
If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be
|
||||
named using their physical endpoint address in hexadecimal instead of their
|
||||
index. RW proxy files will inherit this naming convention. For example, if the
|
||||
first endpoint of a pair maps to address 0x02, the rw_proxy file will be
|
||||
named "ep02_rw".
|
||||
|
||||
When this flag is enabled, userspace has the choice of performing data transfers
|
||||
via the single rw_proxy file descriptor or the two base file descriptors. The
|
||||
rw_proxy file descriptor acts as a pure VFS alias that proxies all operations
|
||||
directly to the underlying base file descriptors.
|
||||
|
||||
Because it is a pure proxy, there are no data races or buffer corruptions if
|
||||
userspace uses both the rw_proxy endpoint and the base endpoints concurrently.
|
||||
The native mutexes of the base endpoints perfectly serialize all concurrent
|
||||
transfers. However, userspace should generally pick one method and stick to it
|
||||
to avoid interleaving its own data stream.
|
||||
|
||||
- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and
|
||||
will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue
|
||||
the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base
|
||||
endpoint file descriptor.
|
||||
- **Intentional Stalls:** The traditional mechanism for intentionally halting an
|
||||
endpoint by issuing a reverse-direction data operation (e.g., attempting to
|
||||
read from an IN endpoint) continues to work, but it must be issued on the
|
||||
base endpoint. RW proxy endpoints cannot be used to trigger a stall because
|
||||
they are fully bidirectional.
|
||||
|
||||
Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported
|
||||
via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF
|
||||
transfers are required, users must use the standard base endpoints.
|
||||
DMABUF interface
|
||||
================
|
||||
|
||||
|
|
@ -79,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the
|
|||
userspace can attach DMABUF objects (externally created) to an endpoint,
|
||||
and subsequently use them for data transfers.
|
||||
|
||||
Note: The DMABUF interface is unsupported on rw_proxy endpoints. See
|
||||
the RW Proxy Endpoints section for details on using DMABUF alongside
|
||||
the ``FUNCTIONFS_RW_PROXY_EPS`` flag.
|
||||
|
||||
A userspace application can then use this interface to share DMABUF
|
||||
objects between several interfaces, allowing it to transfer data in a
|
||||
zero-copy fashion, for instance between IIO and the USB stack.
|
||||
|
|
|
|||
|
|
@ -714,9 +714,6 @@ The uac1 function provides these attributes in its function directory:
|
|||
|
||||
=============== ====================================
|
||||
audio_buf_size audio buffer size
|
||||
fn_cap capture pcm device file name
|
||||
fn_cntl control device file name
|
||||
fn_play playback pcm device file name
|
||||
req_buf_size ISO OUT endpoint request buffer size
|
||||
req_count ISO OUT endpoint request count
|
||||
=============== ====================================
|
||||
|
|
|
|||
|
|
@ -24,37 +24,35 @@ static void as102_usb_stop_stream(struct as102_dev_t *dev);
|
|||
static int as102_open(struct inode *inode, struct file *file);
|
||||
static int as102_release(struct inode *inode, struct file *file);
|
||||
|
||||
struct as102_dev_info {
|
||||
const char *name;
|
||||
/*
|
||||
* eLNA configuration: devices built on the reference design work best
|
||||
* with 0xA0, while custom designs seem to require 0xC0
|
||||
*/
|
||||
uint8_t elna_cfg;
|
||||
};
|
||||
|
||||
#define DRIVER_INFO(dev_name, dev_elna_cfg) \
|
||||
.driver_info = (kernel_ulong_t)&(const struct as102_dev_info){ \
|
||||
.name = (dev_name), \
|
||||
.elna_cfg = (dev_elna_cfg), \
|
||||
}
|
||||
|
||||
static const struct usb_device_id as102_usb_id_table[] = {
|
||||
{ USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) },
|
||||
{ USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) },
|
||||
{ USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) },
|
||||
{ USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) },
|
||||
{ USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) },
|
||||
{ USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001),
|
||||
DRIVER_INFO(AS102_REFERENCE_DESIGN, 0xA0) },
|
||||
{ USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID),
|
||||
DRIVER_INFO(AS102_PCTV_74E, 0xC0) },
|
||||
{ USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID),
|
||||
DRIVER_INFO(AS102_ELGATO_EYETV_DTT_NAME, 0xC0) },
|
||||
{ USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID),
|
||||
DRIVER_INFO(AS102_NBOX_DVBT_DONGLE_NAME, 0xA0) },
|
||||
{ USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID),
|
||||
DRIVER_INFO(AS102_SKY_IT_DIGITAL_KEY_NAME, 0xA0) },
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
/* Note that this table must always have the same number of entries as the
|
||||
as102_usb_id_table struct */
|
||||
static const char * const as102_device_names[] = {
|
||||
AS102_REFERENCE_DESIGN,
|
||||
AS102_PCTV_74E,
|
||||
AS102_ELGATO_EYETV_DTT_NAME,
|
||||
AS102_NBOX_DVBT_DONGLE_NAME,
|
||||
AS102_SKY_IT_DIGITAL_KEY_NAME,
|
||||
NULL /* Terminating entry */
|
||||
};
|
||||
|
||||
/* eLNA configuration: devices built on the reference design work best
|
||||
with 0xA0, while custom designs seem to require 0xC0 */
|
||||
static uint8_t const as102_elna_cfg[] = {
|
||||
0xA0,
|
||||
0xC0,
|
||||
0xC0,
|
||||
0xA0,
|
||||
0xA0,
|
||||
0x00 /* Terminating entry */
|
||||
};
|
||||
|
||||
struct usb_driver as102_usb_driver = {
|
||||
.name = DRIVER_FULL_NAME,
|
||||
.probe = as102_usb_probe,
|
||||
|
|
@ -336,29 +334,18 @@ static int as102_usb_probe(struct usb_interface *intf,
|
|||
{
|
||||
int ret;
|
||||
struct as102_dev_t *as102_dev;
|
||||
int i;
|
||||
|
||||
/* This should never actually happen */
|
||||
if (ARRAY_SIZE(as102_usb_id_table) !=
|
||||
(sizeof(as102_device_names) / sizeof(const char *))) {
|
||||
pr_err("Device names table invalid size");
|
||||
return -EINVAL;
|
||||
}
|
||||
const struct as102_dev_info *info = (const struct as102_dev_info *)id->driver_info;
|
||||
|
||||
as102_dev = kzalloc_obj(struct as102_dev_t);
|
||||
if (as102_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Assign the user-friendly device name */
|
||||
for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) {
|
||||
if (id == &as102_usb_id_table[i]) {
|
||||
as102_dev->name = as102_device_names[i];
|
||||
as102_dev->elna_cfg = as102_elna_cfg[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (as102_dev->name == NULL)
|
||||
if (info) {
|
||||
as102_dev->name = info->name;
|
||||
as102_dev->elna_cfg = info->elna_cfg;
|
||||
} else {
|
||||
as102_dev->name = "Unknown AS102 device";
|
||||
}
|
||||
|
||||
/* set private callback functions */
|
||||
as102_dev->bus_adap.ops = &as102_priv_ops;
|
||||
|
|
|
|||
|
|
@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev)
|
|||
dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
|
||||
}
|
||||
|
||||
static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id)
|
||||
static int tbnet_probe(struct tb_service *svc)
|
||||
{
|
||||
struct tb_xdomain *xd = tb_service_parent(svc);
|
||||
struct net_device *dev;
|
||||
|
|
@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume);
|
|||
|
||||
static const struct tb_service_id tbnet_ids[] = {
|
||||
{ TB_SERVICE("network", 1) },
|
||||
{ },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(tbsvc, tbnet_ids);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,21 +43,12 @@ static bool loopback;
|
|||
static bool mii_mode;
|
||||
static char *devid;
|
||||
|
||||
static struct usb_eth_dev usb_dev_id[] = {
|
||||
#define PEGASUS_DEV(pn, vid, pid, flags) \
|
||||
{.name = pn, .vendor = vid, .device = pid, .private = flags},
|
||||
#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \
|
||||
PEGASUS_DEV(pn, vid, pid, flags)
|
||||
#include "pegasus.h"
|
||||
#undef PEGASUS_DEV
|
||||
#undef PEGASUS_DEV_CLASS
|
||||
{NULL, 0, 0, 0},
|
||||
{NULL, 0, 0, 0}
|
||||
};
|
||||
static struct usb_eth_dev dynamic_id_info = {};
|
||||
|
||||
static struct usb_device_id pegasus_ids[] = {
|
||||
#define PEGASUS_DEV(pn, vid, pid, flags) \
|
||||
{.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid},
|
||||
{.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid, \
|
||||
.driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}},
|
||||
/*
|
||||
* The Belkin F8T012xx1 bluetooth adaptor has the same vendor and product
|
||||
* IDs as the Belkin F5D5050, so we need to teach the pegasus driver to
|
||||
|
|
@ -66,7 +57,8 @@ static struct usb_device_id pegasus_ids[] = {
|
|||
*/
|
||||
#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \
|
||||
{.match_flags = (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS), \
|
||||
.idVendor = vid, .idProduct = pid, .bDeviceClass = dclass},
|
||||
.idVendor = vid, .idProduct = pid, .bDeviceClass = dclass, \
|
||||
.driver_info = (kernel_ulong_t)&(const struct usb_eth_dev) {.name = pn, .private = flags}},
|
||||
#include "pegasus.h"
|
||||
#undef PEGASUS_DEV
|
||||
#undef PEGASUS_DEV_CLASS
|
||||
|
|
@ -402,12 +394,12 @@ static inline int reset_mac(pegasus_t *pegasus)
|
|||
if (i == REG_TIMEOUT)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
|
||||
usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) {
|
||||
if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS ||
|
||||
le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) {
|
||||
set_register(pegasus, Gpio0, 0x24);
|
||||
set_register(pegasus, Gpio0, 0x26);
|
||||
}
|
||||
if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_ELCON) {
|
||||
if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_ELCON) {
|
||||
__u16 auxmode;
|
||||
ret = read_mii_word(pegasus, 3, 0x1b, &auxmode);
|
||||
if (ret < 0)
|
||||
|
|
@ -445,9 +437,9 @@ static int enable_net_traffic(struct net_device *dev, struct usb_device *usb)
|
|||
memcpy(pegasus->eth_regs, data, sizeof(data));
|
||||
ret = set_registers(pegasus, EthCtrl0, 3, data);
|
||||
|
||||
if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
|
||||
usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS2 ||
|
||||
usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) {
|
||||
if (le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS ||
|
||||
le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_LINKSYS2 ||
|
||||
le16_to_cpu(pegasus->usb->descriptor.idVendor) == VENDOR_DLINK) {
|
||||
u16 auxmode;
|
||||
ret = read_mii_word(pegasus, 0, 0x1b, &auxmode);
|
||||
if (ret < 0)
|
||||
|
|
@ -1153,7 +1145,7 @@ static int pegasus_probe(struct usb_interface *intf,
|
|||
struct usb_device *dev = interface_to_usbdev(intf);
|
||||
struct net_device *net;
|
||||
pegasus_t *pegasus;
|
||||
int dev_index = id - pegasus_ids;
|
||||
const struct usb_eth_dev *info = (const struct usb_eth_dev *)id->driver_info;
|
||||
int res = -ENOMEM;
|
||||
static const u8 bulk_ep_addr[] = {
|
||||
PEGASUS_USB_EP_BULK_IN | USB_DIR_IN,
|
||||
|
|
@ -1178,7 +1170,6 @@ static int pegasus_probe(struct usb_interface *intf,
|
|||
goto out;
|
||||
|
||||
pegasus = netdev_priv(net);
|
||||
pegasus->dev_index = dev_index;
|
||||
pegasus->intf = intf;
|
||||
|
||||
res = alloc_urbs(pegasus);
|
||||
|
|
@ -1206,7 +1197,7 @@ static int pegasus_probe(struct usb_interface *intf,
|
|||
pegasus->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV
|
||||
| NETIF_MSG_PROBE | NETIF_MSG_LINK);
|
||||
|
||||
pegasus->features = usb_dev_id[dev_index].private;
|
||||
pegasus->features = info ? info->private : DEFAULT_GPIO_RESET;
|
||||
res = get_interrupt_interval(pegasus);
|
||||
if (res)
|
||||
goto out2;
|
||||
|
|
@ -1235,7 +1226,7 @@ static int pegasus_probe(struct usb_interface *intf,
|
|||
queue_delayed_work(system_long_wq, &pegasus->carrier_check,
|
||||
CARRIER_CHECK_DELAY);
|
||||
dev_info(&intf->dev, "%s, %s, %pM\n", net->name,
|
||||
usb_dev_id[dev_index].name, net->dev_addr);
|
||||
info ? info->name : "(unknown)", net->dev_addr);
|
||||
return 0;
|
||||
|
||||
out3:
|
||||
|
|
@ -1325,8 +1316,9 @@ static struct usb_driver pegasus_driver = {
|
|||
|
||||
static void __init parse_id(char *id)
|
||||
{
|
||||
unsigned int vendor_id = 0, device_id = 0, flags = 0, i = 0;
|
||||
unsigned int vendor_id = 0, device_id = 0, flags = 0;
|
||||
char *token, *name = NULL;
|
||||
int dyn_id_index = ARRAY_SIZE(pegasus_ids) - 2;
|
||||
|
||||
token = strsep(&id, ":");
|
||||
if (token)
|
||||
|
|
@ -1348,14 +1340,12 @@ static void __init parse_id(char *id)
|
|||
if (device_id > 0x10000 || device_id == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; usb_dev_id[i].name; i++);
|
||||
usb_dev_id[i].name = name;
|
||||
usb_dev_id[i].vendor = vendor_id;
|
||||
usb_dev_id[i].device = device_id;
|
||||
usb_dev_id[i].private = flags;
|
||||
pegasus_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
|
||||
pegasus_ids[i].idVendor = vendor_id;
|
||||
pegasus_ids[i].idProduct = device_id;
|
||||
dynamic_id_info.name = name;
|
||||
dynamic_id_info.private = flags;
|
||||
pegasus_ids[dyn_id_index].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
|
||||
pegasus_ids[dyn_id_index].idVendor = vendor_id;
|
||||
pegasus_ids[dyn_id_index].idProduct = device_id;
|
||||
pegasus_ids[dyn_id_index].driver_info = (kernel_ulong_t)&dynamic_id_info;
|
||||
}
|
||||
|
||||
static int __init pegasus_init(void)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ typedef struct pegasus {
|
|||
unsigned features;
|
||||
u32 msg_enable;
|
||||
u32 wolopts;
|
||||
int dev_index;
|
||||
int intr_interval;
|
||||
struct tasklet_struct rx_tl;
|
||||
struct delayed_work carrier_check;
|
||||
|
|
@ -102,8 +101,6 @@ typedef struct pegasus {
|
|||
|
||||
struct usb_eth_dev {
|
||||
char *name;
|
||||
__u16 vendor;
|
||||
__u16 device;
|
||||
__u32 private; /* LSB is gpio reset value */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1087,7 +1087,7 @@ static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
|
|||
}
|
||||
kfree(buf);
|
||||
|
||||
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
|
||||
if (IS_AR7010_DEVICE(hif_dev->id_info))
|
||||
firm_offset = AR7010_FIRMWARE_TEXT;
|
||||
else
|
||||
firm_offset = AR9271_FIRMWARE_TEXT;
|
||||
|
|
@ -1182,7 +1182,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev,
|
|||
if (MAJOR_VERSION_REQ == 1 && hif_dev->fw_minor_index == 3) {
|
||||
const char *filename;
|
||||
|
||||
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
|
||||
if (IS_AR7010_DEVICE(hif_dev->id_info))
|
||||
filename = FIRMWARE_AR7010_1_1;
|
||||
else
|
||||
filename = FIRMWARE_AR9271;
|
||||
|
|
@ -1198,7 +1198,7 @@ static int ath9k_hif_request_firmware(struct hif_device_usb *hif_dev,
|
|||
|
||||
return -ENOENT;
|
||||
} else {
|
||||
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
|
||||
if (IS_AR7010_DEVICE(hif_dev->id_info))
|
||||
chip = "7010";
|
||||
else
|
||||
chip = "9271";
|
||||
|
|
@ -1255,9 +1255,9 @@ static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context)
|
|||
|
||||
ret = ath9k_htc_hw_init(hif_dev->htc_handle,
|
||||
&hif_dev->interface->dev,
|
||||
hif_dev->usb_device_id->idProduct,
|
||||
le16_to_cpu(hif_dev->udev->descriptor.idProduct),
|
||||
hif_dev->udev->product,
|
||||
hif_dev->usb_device_id->driver_info);
|
||||
hif_dev->id_info);
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
goto err_htc_hw_init;
|
||||
|
|
@ -1369,7 +1369,7 @@ static int ath9k_hif_usb_probe(struct usb_interface *interface,
|
|||
|
||||
hif_dev->udev = udev;
|
||||
hif_dev->interface = interface;
|
||||
hif_dev->usb_device_id = id;
|
||||
hif_dev->id_info = id->driver_info;
|
||||
#ifdef CONFIG_PM
|
||||
udev->reset_resume = 1;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ struct cmd_buf {
|
|||
struct hif_device_usb {
|
||||
struct usb_device *udev;
|
||||
struct usb_interface *interface;
|
||||
const struct usb_device_id *usb_device_id;
|
||||
int id_info;
|
||||
const void *fw_data;
|
||||
size_t fw_size;
|
||||
struct completion fw_done;
|
||||
|
|
|
|||
|
|
@ -690,6 +690,14 @@ static void target_fabric_port_unlink(
|
|||
}
|
||||
|
||||
core_dev_del_lun(se_tpg, lun);
|
||||
|
||||
if (tf->tf_ops->fabric_post_unlink) {
|
||||
/*
|
||||
* Allow fabrics to release state that must remain valid until
|
||||
* core_dev_del_lun() has drained all active LUN references.
|
||||
*/
|
||||
tf->tf_ops->fabric_post_unlink(se_tpg, lun);
|
||||
}
|
||||
}
|
||||
|
||||
static void target_fabric_port_release(struct config_item *item)
|
||||
|
|
|
|||
|
|
@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf,
|
|||
if (!access_ok(user_buf, *count))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
buf = (void *)get_zeroed_page(GFP_KERNEL);
|
||||
buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
nbytes = min_t(size_t, *count, PAGE_SIZE);
|
||||
if (copy_from_user(buf, user_buf, nbytes)) {
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port,
|
|||
out:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
|
@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf
|
|||
out:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
|
@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file,
|
|||
out:
|
||||
pm_runtime_mark_last_busy(&rt->dev);
|
||||
pm_runtime_put_autosuspend(&rt->dev);
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
|
@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf,
|
|||
margining->ber_level = val;
|
||||
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
out_unlock:
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
|
|
@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf,
|
|||
}
|
||||
}
|
||||
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
|
||||
if (lane == -1)
|
||||
return -EINVAL;
|
||||
|
|
@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
|
|||
else
|
||||
goto err_free;
|
||||
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
|
||||
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
|
||||
if (!margining->software)
|
||||
|
|
@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
|
|||
return count;
|
||||
|
||||
err_free:
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file,
|
|||
mutex_unlock(&tb->lock);
|
||||
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
|
|
@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file,
|
|||
mutex_unlock(&tb->lock);
|
||||
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
|
|
@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file,
|
|||
mutex_unlock(&tb->lock);
|
||||
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
|
|
@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file,
|
|||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
|
|
@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf,
|
|||
out:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
free_page((unsigned long)buf);
|
||||
kfree(buf);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc)
|
|||
debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops);
|
||||
}
|
||||
|
||||
static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id)
|
||||
static int dma_test_probe(struct tb_service *svc)
|
||||
{
|
||||
struct tb_xdomain *xd = tb_service_parent(svc);
|
||||
struct dma_test *dt;
|
||||
|
|
@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = {
|
|||
|
||||
static const struct tb_service_id dma_test_ids[] = {
|
||||
{ TB_SERVICE("dma_test", 1) },
|
||||
{ },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(tbsvc, dma_test_ids);
|
||||
|
||||
|
|
|
|||
|
|
@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev)
|
|||
{
|
||||
struct tb_service *svc = tb_to_service(dev);
|
||||
struct tb_service_driver *driver;
|
||||
const struct tb_service_id *id;
|
||||
|
||||
driver = container_of(dev->driver, struct tb_service_driver, driver);
|
||||
id = __tb_service_match(dev, &driver->driver);
|
||||
|
||||
return driver->probe(svc, id);
|
||||
return driver->probe(svc);
|
||||
}
|
||||
|
||||
static void tb_service_remove(struct device *dev)
|
||||
|
|
@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
|
|||
transmit_ring, receive_path, receive_ring);
|
||||
}
|
||||
|
||||
static void tb_domain_reset_interface(struct tb *tb)
|
||||
{
|
||||
struct tb_nhi *nhi = tb->nhi;
|
||||
|
||||
if (!nhi->ops->reset_interface)
|
||||
return;
|
||||
|
||||
guard(mutex)(&tb->lock);
|
||||
|
||||
/* The reset clears the ring state so stop the control channel */
|
||||
tb_ctl_stop(tb->ctl);
|
||||
nhi->ops->reset_interface(nhi);
|
||||
tb_ctl_start(tb->ctl);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
|
||||
* @tb: Domain disabling the DMA paths
|
||||
|
|
@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
|
|||
int transmit_path, int transmit_ring,
|
||||
int receive_path, int receive_ring)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!tb->cm_ops->disconnect_xdomain_paths)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
|
||||
ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
|
||||
transmit_ring, receive_path, receive_ring);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN)
|
||||
tb_domain_reset_interface(tb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disconnect_xdomain(struct device *dev, void *data)
|
||||
|
|
|
|||
|
|
@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring)
|
|||
{
|
||||
struct ring_frame *frame, *n;
|
||||
struct ring_desc *descriptor;
|
||||
u32 flags;
|
||||
|
||||
flags = RING_DESC_POSTED;
|
||||
if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
|
||||
flags |= RING_DESC_INTERRUPT;
|
||||
|
||||
list_for_each_entry_safe(frame, n, &ring->queue, list) {
|
||||
if (ring_full(ring))
|
||||
break;
|
||||
|
|
@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring)
|
|||
descriptor = &ring->descriptors[ring->head];
|
||||
descriptor->phys = frame->buffer_phy;
|
||||
descriptor->time = 0;
|
||||
descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT;
|
||||
descriptor->flags = flags;
|
||||
if (ring->is_tx) {
|
||||
descriptor->length = frame->size;
|
||||
descriptor->eof = frame->eof;
|
||||
|
|
@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue);
|
|||
* @ring: Ring to poll
|
||||
*
|
||||
* This function can be called when @start_poll callback of the @ring
|
||||
* has been called. It will read one completed frame from the ring and
|
||||
* return it to the caller.
|
||||
* has been called or the ring is created with %RING_FLAG_NO_INTERRUPT.
|
||||
* It will read one completed frame from the ring and return it to the
|
||||
* caller.
|
||||
*
|
||||
* Return: Pointer to &struct ring_frame, %NULL if there is no more
|
||||
* completed frames.
|
||||
|
|
@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
|
|||
dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n",
|
||||
transmit ? "TX" : "RX", hop, size);
|
||||
|
||||
if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) {
|
||||
dev_WARN(nhi->dev,
|
||||
"start_poll() and NO_INTERRUPT cannot be used at the same time\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ring = kzalloc_obj(*ring);
|
||||
if (!ring)
|
||||
return NULL;
|
||||
|
|
@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
|
|||
if (!ring->descriptors)
|
||||
goto err_free_ring;
|
||||
|
||||
if (nhi->ops->request_ring_irq) {
|
||||
if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) {
|
||||
if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND))
|
||||
goto err_free_descs;
|
||||
}
|
||||
|
|
@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring)
|
|||
ring_iowrite32options(ring, flags, 0);
|
||||
}
|
||||
|
||||
ring_interrupt_active(ring, true);
|
||||
if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
|
||||
ring_interrupt_active(ring, true);
|
||||
ring->running = true;
|
||||
err:
|
||||
spin_unlock(&ring->lock);
|
||||
|
|
@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring)
|
|||
RING_TYPE(ring), ring->hop);
|
||||
goto err;
|
||||
}
|
||||
ring_interrupt_active(ring, false);
|
||||
if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
|
||||
ring_interrupt_active(ring, false);
|
||||
|
||||
ring_iowrite32options(ring, 0, 0);
|
||||
ring_iowrite64desc(ring, 0, 0);
|
||||
|
|
@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi)
|
|||
dev_warn(nhi->dev, "timeout resetting host router\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* nhi_reset_interface() - Reset the host interface
|
||||
* @nhi: Host interface to reset
|
||||
*
|
||||
* Brings the registers in the memory BAR back to their default state and
|
||||
* clears the End-to-End Flow Control state. The caller is responsible for
|
||||
* stopping the control channel over the reset because it clears the ring
|
||||
* state as well.
|
||||
*/
|
||||
void nhi_reset_interface(struct tb_nhi *nhi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = ioread32(nhi->iobase + REG_CAPS);
|
||||
/* Only v1 host interfaces implement the reset */
|
||||
if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2)
|
||||
return;
|
||||
|
||||
dev_dbg(nhi->dev, "issuing host interface reset\n");
|
||||
|
||||
iowrite32(REG_HOST_INTERFACE_RESET_RST,
|
||||
nhi->iobase + REG_HOST_INTERFACE_RESET);
|
||||
/* Wait for tHIReset (10 ms) to complete */
|
||||
usleep_range(10000, 20000);
|
||||
}
|
||||
|
||||
static struct tb *nhi_select_cm(struct tb_nhi *nhi)
|
||||
{
|
||||
struct tb *tb;
|
||||
|
|
@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi)
|
|||
|
||||
dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
|
||||
|
||||
nhi->host_reset = host_reset;
|
||||
|
||||
res = tb_domain_add(tb, host_reset);
|
||||
if (res) {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data);
|
|||
irqreturn_t ring_msix(int irq, void *data);
|
||||
int nhi_probe(struct tb_nhi *nhi);
|
||||
void nhi_shutdown(struct tb_nhi *nhi);
|
||||
void nhi_reset_interface(struct tb_nhi *nhi);
|
||||
|
||||
extern const struct dev_pm_ops nhi_pm_ops;
|
||||
|
||||
/**
|
||||
|
|
@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
|
|||
* @release_ring_irq: NHI specific interrupt release hook
|
||||
* @is_present: Whether the device is currently present on the parent bus
|
||||
* @init_interrupts: NHI specific interrupt initialization hook
|
||||
* @reset_interface: Resets the host interface
|
||||
*/
|
||||
struct tb_nhi_ops {
|
||||
int (*init)(struct tb_nhi *nhi);
|
||||
|
|
@ -66,6 +69,7 @@ struct tb_nhi_ops {
|
|||
void (*release_ring_irq)(struct tb_ring *ring);
|
||||
bool (*is_present)(struct tb_nhi *nhi);
|
||||
int (*init_interrupts)(struct tb_nhi *nhi);
|
||||
void (*reset_interface)(struct tb_nhi *nhi);
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -116,11 +120,24 @@ struct tb_nhi_ops {
|
|||
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433
|
||||
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434
|
||||
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e
|
||||
|
||||
#define PCI_CLASS_SERIAL_USB_USB4 0x0c0340
|
||||
|
||||
/* Host interface quirks */
|
||||
#define QUIRK_AUTO_CLEAR_INT BIT(0)
|
||||
#define QUIRK_E2E BIT(1)
|
||||
#define QUIRK_AUTO_CLEAR_INT BIT(0)
|
||||
#define QUIRK_E2E BIT(1)
|
||||
#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2)
|
||||
|
||||
/*
|
||||
* Minimal number of vectors when we use MSI-X. Two for control channel
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@ struct ring_desc {
|
|||
#define REG_CAPS_VERSION_MASK GENMASK(23, 16)
|
||||
#define REG_CAPS_VERSION_2 0x40
|
||||
|
||||
/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */
|
||||
#define REG_HOST_INTERFACE_RESET 0x39858
|
||||
#define REG_HOST_INTERFACE_RESET_RST BIT(0)
|
||||
|
||||
#define REG_DMA_MISC 0x39864
|
||||
#define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2)
|
||||
#define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci)
|
|||
nhi->quirks |= QUIRK_E2E;
|
||||
break;
|
||||
}
|
||||
} else if (pdev->vendor == PCI_VENDOR_ID_AMD) {
|
||||
switch (pdev->device) {
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1:
|
||||
/*
|
||||
* These AMD hosts may hang the Tx ring when the
|
||||
* DMA paths are torn down so they need the host
|
||||
* interface reset after each teardown.
|
||||
*/
|
||||
nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
|
|||
{
|
||||
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
|
||||
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
||||
struct device *dev = &pdev->dev;
|
||||
int res, irq, nvec;
|
||||
|
||||
ida_init(&nhi_pci->msix_ida);
|
||||
|
|
@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi)
|
|||
res = devm_request_irq(&pdev->dev, irq, nhi_msi,
|
||||
IRQF_NO_SUSPEND, "thunderbolt", nhi);
|
||||
if (res)
|
||||
return dev_err_probe(dev, res, "request_irq failed, aborting\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring)
|
|||
ring->irq = 0;
|
||||
}
|
||||
|
||||
static void nhi_pci_shutdown(struct tb_nhi *nhi)
|
||||
static void nhi_pci_release_irq(struct tb_nhi *nhi)
|
||||
{
|
||||
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
|
||||
struct pci_dev *pdev = to_pci_dev(nhi->dev);
|
||||
|
|
@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = {
|
|||
.post_nvm_auth = nhi_pci_complete_dma_port,
|
||||
.request_ring_irq = nhi_pci_ring_request_msix,
|
||||
.release_ring_irq = nhi_pci_ring_release_msix,
|
||||
.shutdown = nhi_pci_shutdown,
|
||||
.shutdown = nhi_pci_release_irq,
|
||||
.is_present = nhi_pci_is_present,
|
||||
.init_interrupts = nhi_pci_init_msi,
|
||||
.reset_interface = nhi_reset_interface,
|
||||
};
|
||||
|
||||
/* Ice Lake specific NHI operations */
|
||||
|
|
@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi)
|
|||
|
||||
static void icl_nhi_shutdown(struct tb_nhi *nhi)
|
||||
{
|
||||
nhi_pci_shutdown(nhi);
|
||||
nhi_pci_release_irq(nhi);
|
||||
|
||||
icl_nhi_force_power(nhi, false);
|
||||
}
|
||||
|
|
@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = {
|
|||
.release_ring_irq = nhi_pci_ring_release_msix,
|
||||
.is_present = nhi_pci_is_present,
|
||||
.init_interrupts = nhi_pci_init_msi,
|
||||
.reset_interface = nhi_reset_interface,
|
||||
};
|
||||
|
||||
static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
|
|
@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|||
return nhi_probe(&nhi_pci->nhi);
|
||||
}
|
||||
|
||||
static void nhi_pci_remove(struct pci_dev *pdev)
|
||||
static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset)
|
||||
{
|
||||
struct tb *tb = pci_get_drvdata(pdev);
|
||||
struct tb_nhi *nhi = tb->nhi;
|
||||
|
||||
/*
|
||||
* On system shutdown/reboot force a host router reset so the
|
||||
* connection manager asserts DPR on connected Thunderbolt 3 devices
|
||||
* before the router tree is removed (see tb_stop()).
|
||||
*/
|
||||
if (reset)
|
||||
nhi->host_reset = true;
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
pm_runtime_dont_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_forbid(&pdev->dev);
|
||||
|
|
@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev)
|
|||
nhi_shutdown(nhi);
|
||||
}
|
||||
|
||||
static void nhi_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
nhi_pci_do_remove(pdev, false);
|
||||
}
|
||||
|
||||
static void nhi_pci_shutdown(struct pci_dev *pdev)
|
||||
{
|
||||
nhi_pci_do_remove(pdev, true);
|
||||
}
|
||||
|
||||
static struct pci_device_id nhi_ids[] = {
|
||||
/*
|
||||
* We have to specify class, the TB bridges use the same device and
|
||||
|
|
@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = {
|
|||
.id_table = nhi_ids,
|
||||
.probe = nhi_pci_probe,
|
||||
.remove = nhi_pci_remove,
|
||||
.shutdown = nhi_pci_remove,
|
||||
.shutdown = nhi_pci_shutdown,
|
||||
.driver.pm = &nhi_pm_ops,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@
|
|||
|
||||
#define pr_fmt(fmt) "tbstream: " fmt
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/configfs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
|
|
@ -128,6 +130,7 @@ struct tbstream_ring {
|
|||
* @out_hopid: Out HopID
|
||||
* @ring_size: Size of the rings
|
||||
* @throttling: Interrupt throttling rate in ns
|
||||
* @busy_poll: Instead of interrupts, busy poll the rings
|
||||
* @users: Number of times @cdev has been opened
|
||||
* @closed: CLOSE packet was received
|
||||
* @removed: Userspace removed the ConfigFS group underneath.
|
||||
|
|
@ -147,6 +150,7 @@ struct tbstream_dev {
|
|||
int out_hopid;
|
||||
unsigned int ring_size;
|
||||
unsigned int throttling;
|
||||
bool busy_poll;
|
||||
int users;
|
||||
bool closed;
|
||||
bool removed;
|
||||
|
|
@ -512,8 +516,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf,
|
|||
dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size,
|
||||
DMA_TO_DEVICE);
|
||||
if (pdf == TBSTREAM_DATA) {
|
||||
if (copy_page_from_iter(sf->page, 0, size, from) != size)
|
||||
if (copy_page_from_iter(sf->page, 0, size, from) != size) {
|
||||
sdev->tx_ring.cons--;
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
} else {
|
||||
memset(page_address(sf->page), 0, size);
|
||||
}
|
||||
|
|
@ -534,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from,
|
|||
return tb_ring_tx(sdev->tx_ring.ring, &sf->frame);
|
||||
}
|
||||
|
||||
static void
|
||||
tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring)
|
||||
{
|
||||
struct ring_frame *frame;
|
||||
|
||||
if (!sdev->busy_poll)
|
||||
return;
|
||||
|
||||
while ((frame = tb_ring_poll(ring->ring)))
|
||||
frame->callback(ring->ring, frame, false);
|
||||
}
|
||||
|
||||
static int tbstream_dev_send_close(struct tbstream_dev *sdev)
|
||||
{
|
||||
struct tbstream_frame *sf;
|
||||
|
||||
if (sdev->busy_poll) {
|
||||
/*
|
||||
* When busy polling it's the write(2) path that
|
||||
* advances the completions so it is possible that the
|
||||
* ring is full at this point. Advance the ring here so
|
||||
* that there is room for the CLOSE packet to be sent.
|
||||
*/
|
||||
ktime_t timeout = ktime_add_ms(ktime_get(), 500);
|
||||
|
||||
do {
|
||||
if (tbstream_ring_available(&sdev->tx_ring))
|
||||
break;
|
||||
tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
|
||||
fsleep(15);
|
||||
} while (ktime_before(ktime_get(), timeout));
|
||||
}
|
||||
|
||||
sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256);
|
||||
if (IS_ERR(sf))
|
||||
return PTR_ERR(sf);
|
||||
|
|
@ -547,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev)
|
|||
static int tbstream_dev_start(struct tbstream_dev *sdev)
|
||||
{
|
||||
struct tb_xdomain *xd = tbstream_dev_xdomain(sdev);
|
||||
unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E;
|
||||
u16 sof_mask, eof_mask;
|
||||
struct tb_ring *ring;
|
||||
int ret, e2e_tx_hop;
|
||||
|
||||
ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size,
|
||||
RING_FLAG_FRAME | RING_FLAG_E2E);
|
||||
if (sdev->busy_poll)
|
||||
flags |= RING_FLAG_NO_INTERRUPT;
|
||||
|
||||
ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags);
|
||||
if (!ring)
|
||||
return -ENOMEM;
|
||||
sdev->tx_ring.ring = ring;
|
||||
|
|
@ -565,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
|
|||
sof_mask = BIT(TBSTREAM_FRAME_START);
|
||||
eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE);
|
||||
|
||||
ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size,
|
||||
RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop,
|
||||
sof_mask, eof_mask, NULL, NULL);
|
||||
ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags,
|
||||
e2e_tx_hop, sof_mask, eof_mask, NULL, NULL);
|
||||
if (!ring) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_tx_buffers;
|
||||
|
|
@ -605,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev)
|
||||
{
|
||||
const struct tbstream_ring *ring = &sdev->tx_ring;
|
||||
|
||||
/*
|
||||
* Everything is completed when number of free TX slots is back
|
||||
* to the maximum.
|
||||
*/
|
||||
return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1;
|
||||
}
|
||||
|
||||
static void tbstream_dev_stop(struct tbstream_dev *sdev)
|
||||
{
|
||||
struct tb_xdomain *xd;
|
||||
|
||||
/* Wait for the ring to complete any outstanding frames */
|
||||
tb_ring_flush(sdev->tx_ring.ring, 500);
|
||||
tb_ring_stop(sdev->tx_ring.ring);
|
||||
tb_ring_flush(sdev->rx_ring.ring, 500);
|
||||
tb_ring_stop(sdev->rx_ring.ring);
|
||||
if (sdev->busy_poll) {
|
||||
/*
|
||||
* When busy polling we must advance the ring ourselves
|
||||
* to push all outstanding frames on the wire.
|
||||
*/
|
||||
ktime_t timeout = ktime_add_ms(ktime_get(), 500);
|
||||
|
||||
do {
|
||||
if (tbstream_dev_tx_drained(sdev))
|
||||
break;
|
||||
tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
|
||||
fsleep(15);
|
||||
} while (ktime_before(ktime_get(), timeout));
|
||||
|
||||
tb_ring_stop(sdev->tx_ring.ring);
|
||||
tb_ring_stop(sdev->rx_ring.ring);
|
||||
} else {
|
||||
tb_ring_flush(sdev->tx_ring.ring, 500);
|
||||
tb_ring_stop(sdev->tx_ring.ring);
|
||||
tb_ring_flush(sdev->rx_ring.ring, 500);
|
||||
tb_ring_stop(sdev->rx_ring.ring);
|
||||
}
|
||||
|
||||
xd = tbstream_dev_xdomain(sdev);
|
||||
if (xd) {
|
||||
|
|
@ -631,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev)
|
|||
sdev->tx_ring.ring = NULL;
|
||||
}
|
||||
|
||||
/* Use only with read_iter/write_iter() to handle nowait */
|
||||
static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait)
|
||||
{
|
||||
if (nowait) {
|
||||
if (!mutex_trylock(&sdev->lock))
|
||||
return -EAGAIN;
|
||||
} else {
|
||||
if (mutex_lock_interruptible(&sdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
||||
{
|
||||
struct file *file = kiocb->ki_filp;
|
||||
bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT;
|
||||
struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
|
||||
size_t nbytes;
|
||||
int ret;
|
||||
|
|
@ -643,35 +722,54 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (mutex_lock_interruptible(&sdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
ret = tbstream_dev_lock(sdev, nowait);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (!tbstream_ring_available(&sdev->rx_ring)) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
ret = wait_event_interruptible(sdev->wait,
|
||||
tbstream_ring_available(&sdev->rx_ring) ||
|
||||
tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) ||
|
||||
tbstream_dev_removed(sdev));
|
||||
if (ret)
|
||||
return ret;
|
||||
for (;;) {
|
||||
/* When busy polling, advance any completions manually */
|
||||
tbstream_dev_poll_ring(sdev, &sdev->rx_ring);
|
||||
|
||||
ret = tbstream_dev_valid(sdev);
|
||||
if (ret) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tbstream_ring_available(&sdev->rx_ring))
|
||||
break;
|
||||
|
||||
mutex_unlock(&sdev->lock);
|
||||
|
||||
if (nowait)
|
||||
return -EAGAIN;
|
||||
|
||||
if (sdev->busy_poll) {
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
cond_resched();
|
||||
} else {
|
||||
ret = wait_event_interruptible(sdev->wait,
|
||||
tbstream_ring_available(&sdev->rx_ring) ||
|
||||
tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) ||
|
||||
tbstream_dev_removed(sdev));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = tbstream_dev_lock(sdev, nowait);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
|
||||
return 0;
|
||||
|
||||
if (mutex_lock_interruptible(&sdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
|
||||
nbytes = 0;
|
||||
while (nbytes < iov_iter_count(to)) {
|
||||
while (iov_iter_count(to)) {
|
||||
struct tbstream_frame *sf;
|
||||
size_t size, sf_size;
|
||||
|
||||
|
|
@ -693,7 +791,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
}
|
||||
|
||||
sf_size = tb_ring_frame_size(&sf->frame);
|
||||
size = min(iov_iter_count(to) - nbytes, sf_size);
|
||||
size = min(iov_iter_count(to), sf_size);
|
||||
|
||||
if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) {
|
||||
ret = -EFAULT;
|
||||
|
|
@ -727,6 +825,7 @@ static ssize_t
|
|||
tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
||||
{
|
||||
struct file *file = kiocb->ki_filp;
|
||||
bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT;
|
||||
struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
|
||||
size_t nbytes;
|
||||
int ret;
|
||||
|
|
@ -735,38 +834,56 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (mutex_lock_interruptible(&sdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
ret = tbstream_dev_lock(sdev, nowait);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (!tbstream_ring_available(&sdev->tx_ring)) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
ret = wait_event_interruptible(sdev->wait,
|
||||
tbstream_ring_available(&sdev->tx_ring) ||
|
||||
tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) ||
|
||||
tbstream_dev_removed(sdev));
|
||||
if (ret)
|
||||
return ret;
|
||||
for (;;) {
|
||||
tbstream_dev_poll_ring(sdev, &sdev->tx_ring);
|
||||
|
||||
ret = tbstream_dev_valid(sdev);
|
||||
if (ret) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) {
|
||||
mutex_unlock(&sdev->lock);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (tbstream_ring_available(&sdev->tx_ring))
|
||||
break;
|
||||
|
||||
mutex_unlock(&sdev->lock);
|
||||
|
||||
if (nowait)
|
||||
return -EAGAIN;
|
||||
|
||||
if (sdev->busy_poll) {
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
cond_resched();
|
||||
} else {
|
||||
ret = wait_event_interruptible(sdev->wait,
|
||||
tbstream_ring_available(&sdev->tx_ring) ||
|
||||
tbstream_dev_valid(sdev) != 0 ||
|
||||
tbstream_dev_closed(sdev) ||
|
||||
tbstream_dev_removed(sdev));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = tbstream_dev_lock(sdev, nowait);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev))
|
||||
return -ENXIO;
|
||||
|
||||
if (mutex_lock_interruptible(&sdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
|
||||
nbytes = 0;
|
||||
while (nbytes < iov_iter_count(from)) {
|
||||
while (iov_iter_count(from)) {
|
||||
size_t size;
|
||||
|
||||
size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE);
|
||||
size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE);
|
||||
ret = tbstream_dev_send_data(sdev, from, size);
|
||||
if (ret) {
|
||||
/*
|
||||
|
|
@ -793,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait)
|
|||
struct tbstream_dev *sdev = to_tbstream_dev(file->private_data);
|
||||
__poll_t mask = 0;
|
||||
|
||||
/*
|
||||
* Without interrupts there is nothing that can wake us up so
|
||||
* return failure instead.
|
||||
*/
|
||||
if (sdev->busy_poll)
|
||||
return EPOLLERR;
|
||||
|
||||
poll_wait(file, &sdev->wait, wait);
|
||||
guard(mutex)(&sdev->lock);
|
||||
if (tbstream_dev_valid(sdev) != 0) {
|
||||
|
|
@ -902,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group)
|
|||
return container_of(group, struct tbstream_dev, group);
|
||||
}
|
||||
|
||||
static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf)
|
||||
{
|
||||
struct config_group *group = to_config_group(item);
|
||||
struct tbstream_dev *sdev = tbstream_dev_from_group(group);
|
||||
|
||||
return sysfs_emit(buf, "%u\n", sdev->busy_poll);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
tbstream_dev_busy_poll_store(struct config_item *item, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct config_group *group = to_config_group(item);
|
||||
struct tbstream_dev *sdev = tbstream_dev_from_group(group);
|
||||
bool busy_poll;
|
||||
int ret;
|
||||
|
||||
ret = kstrtobool(buf, &busy_poll);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
guard(mutex)(&sdev->lock);
|
||||
if (sdev->users)
|
||||
return -EBUSY;
|
||||
sdev->busy_poll = busy_poll;
|
||||
return count;
|
||||
}
|
||||
CONFIGFS_ATTR(tbstream_dev_, busy_poll);
|
||||
|
||||
static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf)
|
||||
{
|
||||
struct config_group *group = to_config_group(item);
|
||||
|
|
@ -1208,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf,
|
|||
CONFIGFS_ATTR(tbstream_dev_, throttling);
|
||||
|
||||
static struct configfs_attribute *tbstream_dev_attrs[] = {
|
||||
&tbstream_dev_attr_busy_poll,
|
||||
&tbstream_dev_attr_index,
|
||||
&tbstream_dev_attr_in_hopid,
|
||||
&tbstream_dev_attr_out_hopid,
|
||||
|
|
@ -1540,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream)
|
|||
config_group_put(&sg->group);
|
||||
}
|
||||
|
||||
static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id)
|
||||
static int tbstream_probe(struct tb_service *svc)
|
||||
{
|
||||
struct tbstream *stream;
|
||||
|
||||
|
|
@ -1630,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = {
|
|||
|
||||
static const struct tb_service_id tbstream_ids[] = {
|
||||
{ TB_SERVICE("stream", 1) },
|
||||
{ },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(tbsvc, tbstream_ids);
|
||||
|
||||
|
|
|
|||
|
|
@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port)
|
|||
return __tb_port_enable(port, false);
|
||||
}
|
||||
|
||||
static int tb_port_reset(struct tb_port *port)
|
||||
/**
|
||||
* tb_port_reset() - Reset the port
|
||||
* @port: Port to reset
|
||||
*
|
||||
* Resets @port. For USB4 ports this issues a USB4 port reset and for
|
||||
* legacy ports the link controller port is reset.
|
||||
*
|
||||
* Return: %0 on success, negative errno otherwise.
|
||||
*/
|
||||
int tb_port_reset(struct tb_port *port)
|
||||
{
|
||||
if (tb_switch_is_usb4(port->sw))
|
||||
return port->cap_usb4 ? usb4_port_reset(port) : 0;
|
||||
|
|
|
|||
|
|
@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
|
|||
static void tb_stop(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
struct tb_nhi *nhi = tb->nhi;
|
||||
struct tb_tunnel *tunnel;
|
||||
struct tb_port *port;
|
||||
struct tb_tunnel *n;
|
||||
|
||||
cancel_delayed_work(&tcm->remove_work);
|
||||
|
|
@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb)
|
|||
tb_tunnel_deactivate(tunnel);
|
||||
tb_tunnel_put(tunnel);
|
||||
}
|
||||
/*
|
||||
* Signal disconnect to connected devices before the router tree is
|
||||
* removed below. A Thunderbolt 3 device directly connected to a USB4
|
||||
* host otherwise never receives a disconnect indication, leaving
|
||||
* firmware to poll the dead link for up to ~60 s which on some
|
||||
* platforms turns the shutdown into a warm reset. Asserting
|
||||
* PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device
|
||||
* detects SBRX low and goes to Uninitialized Unplugged immediately.
|
||||
*/
|
||||
if (nhi->host_reset) {
|
||||
tb_switch_for_each_port(tb->root_switch, port) {
|
||||
if (!tb_port_is_null(port) || !tb_port_has_remote(port))
|
||||
continue;
|
||||
if (tb_switch_is_usb4(port->remote->sw))
|
||||
continue;
|
||||
if (tb_port_reset(port))
|
||||
tb_port_dbg(port, "downstream port reset failed, continuing\n");
|
||||
}
|
||||
}
|
||||
tb_switch_remove(tb->root_switch);
|
||||
tb->root_switch = NULL;
|
||||
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
|
||||
|
|
|
|||
|
|
@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter);
|
|||
int tb_port_unlock(struct tb_port *port);
|
||||
int tb_port_enable(struct tb_port *port);
|
||||
int tb_port_disable(struct tb_port *port);
|
||||
int tb_port_reset(struct tb_port *port);
|
||||
int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
|
||||
void tb_port_release_in_hopid(struct tb_port *port, int hopid);
|
||||
int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@
|
|||
#define TB_DP_AUX_PRIORITY 2
|
||||
#define TB_DP_AUX_WEIGHT 1
|
||||
|
||||
/* struct tb_regs_hop::initial_credits is 7 bits wide */
|
||||
#define TB_MAX_CREDITS 127
|
||||
|
||||
/* Minimum number of credits needed for PCIe path */
|
||||
#define TB_MIN_PCIE_CREDITS 6U
|
||||
/*
|
||||
|
|
@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits)
|
|||
if (available < TB_MIN_DMA_CREDITS)
|
||||
return -ENOSPC;
|
||||
|
||||
while (credits > available)
|
||||
credits--;
|
||||
credits = min(credits, available);
|
||||
|
||||
tb_port_dbg(port, "reserving %u credits for DMA path\n",
|
||||
credits);
|
||||
|
|
@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
|
|||
struct tb_tunnel *tunnel;
|
||||
size_t npaths = 0, i = 0;
|
||||
struct tb_path *path;
|
||||
int credits;
|
||||
unsigned int credits;
|
||||
|
||||
/* Ring 0 is reserved for control channel */
|
||||
if (WARN_ON(!receive_ring || !transmit_ring))
|
||||
|
|
@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
|
|||
tunnel->destroy = tb_dma_destroy;
|
||||
|
||||
credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits);
|
||||
if (credits > TB_MAX_CREDITS) {
|
||||
tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n",
|
||||
credits, TB_MAX_CREDITS);
|
||||
credits = TB_MAX_CREDITS;
|
||||
}
|
||||
|
||||
if (receive_ring > 0) {
|
||||
path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0,
|
||||
|
|
|
|||
|
|
@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr,
|
|||
*/
|
||||
int tb_register_service_driver(struct tb_service_driver *drv)
|
||||
{
|
||||
if (!drv->probe)
|
||||
return -EINVAL;
|
||||
|
||||
drv->driver.bus = &tb_bus_type;
|
||||
return driver_register(&drv->driver);
|
||||
}
|
||||
|
|
@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work)
|
|||
tb_xdomain_failed(xd);
|
||||
} else {
|
||||
xd->state = XDOMAIN_STATE_ENUMERATED;
|
||||
tb_xdomain_queue_properties_changed(xd);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ static int xusbatm_bind(struct usbatm_data *usbatm,
|
|||
struct usb_interface *intf, const struct usb_device_id *id)
|
||||
{
|
||||
struct usb_device *usb_dev = interface_to_usbdev(intf);
|
||||
int drv_ix = id - xusbatm_usb_ids;
|
||||
int drv_ix = id->driver_info;
|
||||
int rx_alt = rx_altsetting[drv_ix];
|
||||
int tx_alt = tx_altsetting[drv_ix];
|
||||
struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]);
|
||||
|
|
@ -168,7 +168,8 @@ static struct usb_driver xusbatm_usb_driver = {
|
|||
.name = xusbatm_driver_name,
|
||||
.probe = xusbatm_usb_probe,
|
||||
.disconnect = usbatm_usb_disconnect,
|
||||
.id_table = xusbatm_usb_ids
|
||||
.id_table = xusbatm_usb_ids,
|
||||
.no_dynamic_id = 1,
|
||||
};
|
||||
|
||||
static int __init xusbatm_init(void)
|
||||
|
|
@ -190,6 +191,7 @@ static int __init xusbatm_init(void)
|
|||
xusbatm_usb_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
|
||||
xusbatm_usb_ids[i].idVendor = vendor[i];
|
||||
xusbatm_usb_ids[i].idProduct = product[i];
|
||||
xusbatm_usb_ids[i].driver_info = i;
|
||||
|
||||
xusbatm_drivers[i].driver_name = xusbatm_driver_name;
|
||||
xusbatm_drivers[i].bind = xusbatm_bind;
|
||||
|
|
|
|||
|
|
@ -761,13 +761,13 @@ static int c67x00_add_iso_urb(struct c67x00_hcd *c67x00, struct urb *urb)
|
|||
ret);
|
||||
urb->iso_frame_desc[urbp->cnt].actual_length = 0;
|
||||
urb->iso_frame_desc[urbp->cnt].status = ret;
|
||||
if (urbp->cnt + 1 == urb->number_of_packets)
|
||||
c67x00_giveback_urb(c67x00, urb, 0);
|
||||
}
|
||||
|
||||
urbp->ep_data->next_frame =
|
||||
frame_add(urbp->ep_data->next_frame, urb->interval);
|
||||
urbp->cnt++;
|
||||
if (ret && urbp->cnt == urb->number_of_packets)
|
||||
c67x00_giveback_urb(c67x00, urb, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev)
|
|||
cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy");
|
||||
if (IS_ERR(cdns->usb2_phy))
|
||||
return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy),
|
||||
"Failed to get cdn3,usb2-phy\n");
|
||||
"Failed to get cdns3,usb2-phy\n");
|
||||
|
||||
cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy");
|
||||
if (IS_ERR(cdns->usb3_phy))
|
||||
return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy),
|
||||
"Failed to get cdn3,usb3-phy\n");
|
||||
"Failed to get cdns3,usb3-phy\n");
|
||||
|
||||
ret = phy_init(cdns->usb2_phy);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -528,7 +528,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
|
|||
if (data->wakeup_irq > 0) {
|
||||
irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name);
|
||||
if (!irq_name) {
|
||||
dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
|
||||
ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ struct usbtmc_dev_capabilities {
|
|||
* allocated for each USBTMC device in the driver's probe function.
|
||||
*/
|
||||
struct usbtmc_device_data {
|
||||
const struct usb_device_id *id;
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *intf;
|
||||
struct list_head file_list;
|
||||
|
|
@ -2394,7 +2393,6 @@ static int usbtmc_probe(struct usb_interface *intf,
|
|||
return -ENOMEM;
|
||||
|
||||
data->intf = intf;
|
||||
data->id = id;
|
||||
data->usb_dev = usb_get_dev(interface_to_usbdev(intf));
|
||||
usb_set_intfdata(intf, data);
|
||||
kref_init(&data->kref);
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno,
|
|||
usb_endpoint_xfer_int(&ep->desc)) &&
|
||||
desc->bmAttributes != 0) {
|
||||
dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n",
|
||||
usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk",
|
||||
usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt",
|
||||
desc->bmAttributes,
|
||||
cfgno, inum, asnum, ep->desc.bEndpointAddress);
|
||||
ep->ss_ep_comp.bmAttributes = 0;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/usb.h>
|
||||
|
|
@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes,
|
|||
return 0;
|
||||
/* allocate 2^1 pages = 8K (on i386);
|
||||
* should be more than enough for one device */
|
||||
pages_start = (char *)__get_free_pages(GFP_NOIO, 1);
|
||||
pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO);
|
||||
if (!pages_start)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes,
|
|||
if (length > *nbytes)
|
||||
length = *nbytes;
|
||||
if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) {
|
||||
free_pages((unsigned long)pages_start, 1);
|
||||
kfree(pages_start);
|
||||
return -EFAULT;
|
||||
}
|
||||
*nbytes -= length;
|
||||
|
|
@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes,
|
|||
} else
|
||||
*skip_bytes -= length;
|
||||
|
||||
free_pages((unsigned long)pages_start, 1);
|
||||
kfree(pages_start);
|
||||
|
||||
/* Now look at all of this device's children. */
|
||||
usb_hub_for_each_child(usbdev, chix, childdev) {
|
||||
|
|
|
|||
|
|
@ -1113,7 +1113,6 @@ static int usbdev_release(struct inode *inode, struct file *file)
|
|||
if (!ps->suspend_allowed)
|
||||
usb_autosuspend_device(dev);
|
||||
usb_unlock_device(dev);
|
||||
usb_put_dev(dev);
|
||||
put_pid(ps->disc_pid);
|
||||
put_cred(ps->cred);
|
||||
|
||||
|
|
@ -1122,6 +1121,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
|
|||
free_async(as);
|
||||
as = async_getcompleted(ps);
|
||||
}
|
||||
usb_put_dev(dev);
|
||||
|
||||
kfree(ps);
|
||||
return 0;
|
||||
|
|
@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
|
|||
if (!connected(ps))
|
||||
return -ENODEV;
|
||||
|
||||
if (ps->dev->state != USB_STATE_CONFIGURED)
|
||||
return -EHOSTUNREACH;
|
||||
|
||||
intf = usb_ifnum_to_if(ps->dev, ctl->ifno);
|
||||
if (!intf)
|
||||
return -EINVAL;
|
||||
|
||||
/* alloc buffer */
|
||||
size = _IOC_SIZE(ctl->ioctl_code);
|
||||
if (size > 0) {
|
||||
|
|
@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
|
|||
}
|
||||
}
|
||||
|
||||
if (ps->dev->state != USB_STATE_CONFIGURED)
|
||||
retval = -EHOSTUNREACH;
|
||||
else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno)))
|
||||
retval = -EINVAL;
|
||||
else switch (ctl->ioctl_code) {
|
||||
switch (ctl->ioctl_code) {
|
||||
|
||||
/* disconnect kernel driver from interface */
|
||||
case USBDEVFS_DISCONNECT:
|
||||
|
|
|
|||
|
|
@ -228,14 +228,16 @@ static void usb_free_dynids(struct usb_driver *usb_drv)
|
|||
}
|
||||
|
||||
static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf,
|
||||
const struct usb_driver *drv)
|
||||
const struct usb_driver *drv,
|
||||
struct usb_device_id *id_copy)
|
||||
{
|
||||
struct usb_dynid *dynid;
|
||||
|
||||
guard(mutex)(&usb_dynids_lock);
|
||||
list_for_each_entry(dynid, &drv->dynids.list, node) {
|
||||
if (usb_match_one_id(intf, &dynid->id)) {
|
||||
return &dynid->id;
|
||||
*id_copy = dynid->id;
|
||||
return id_copy;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
|
@ -321,6 +323,7 @@ static int usb_probe_interface(struct device *dev)
|
|||
struct usb_interface *intf = to_usb_interface(dev);
|
||||
struct usb_device *udev = interface_to_usbdev(intf);
|
||||
const struct usb_device_id *id;
|
||||
struct usb_device_id id_copy;
|
||||
int error = -ENODEV;
|
||||
int lpm_disable_error = -ENODEV;
|
||||
|
||||
|
|
@ -340,7 +343,7 @@ static int usb_probe_interface(struct device *dev)
|
|||
return error;
|
||||
}
|
||||
|
||||
id = usb_match_dynamic_id(intf, driver);
|
||||
id = usb_match_dynamic_id(intf, driver, &id_copy);
|
||||
if (!id)
|
||||
id = usb_match_id(intf, driver->id_table);
|
||||
if (!id)
|
||||
|
|
@ -892,6 +895,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)
|
|||
struct usb_interface *intf;
|
||||
const struct usb_driver *usb_drv;
|
||||
const struct usb_device_id *id;
|
||||
struct usb_device_id id_copy;
|
||||
|
||||
/* device drivers never match interfaces */
|
||||
if (is_usb_device_driver(drv))
|
||||
|
|
@ -904,7 +908,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)
|
|||
if (id)
|
||||
return 1;
|
||||
|
||||
id = usb_match_dynamic_id(intf, usb_drv);
|
||||
id = usb_match_dynamic_id(intf, usb_drv, &id_copy);
|
||||
if (id)
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
|
|||
mutex_lock(&hub->status_mutex);
|
||||
ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len);
|
||||
if (ret < len) {
|
||||
if (ret != -ENODEV)
|
||||
dev_err(hub->intfdev,
|
||||
"%s failed (err = %d)\n", __func__, ret);
|
||||
if (ret >= 0)
|
||||
ret = -EIO;
|
||||
if (ret != -ENODEV)
|
||||
dev_dbg(hub->intfdev,
|
||||
"get_port_status failed: err = %d\n", ret);
|
||||
} else {
|
||||
*status = le16_to_cpu(hub->status->port.wPortStatus);
|
||||
*change = le16_to_cpu(hub->status->port.wPortChange);
|
||||
|
|
@ -753,10 +753,12 @@ void usb_wakeup_notification(struct usb_device *hdev,
|
|||
{
|
||||
struct usb_hub *hub;
|
||||
struct usb_port *port_dev;
|
||||
unsigned long flags;
|
||||
|
||||
if (!hdev)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&device_state_lock, flags);
|
||||
hub = usb_hub_to_struct_hub(hdev);
|
||||
if (hub) {
|
||||
port_dev = hub->ports[portnum - 1];
|
||||
|
|
@ -766,6 +768,7 @@ void usb_wakeup_notification(struct usb_device *hdev,
|
|||
set_bit(portnum, hub->wakeup_bits);
|
||||
kick_hub_wq(hub);
|
||||
}
|
||||
spin_unlock_irqrestore(&device_state_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_wakeup_notification);
|
||||
|
||||
|
|
@ -991,10 +994,12 @@ static int hub_hub_status(struct usb_hub *hub,
|
|||
|
||||
mutex_lock(&hub->status_mutex);
|
||||
ret = get_hub_status(hub->hdev, &hub->status->hub);
|
||||
if (ret < 0) {
|
||||
if (ret < (int)sizeof(hub->status->hub)) {
|
||||
if (ret >= 0)
|
||||
ret = -EIO;
|
||||
if (ret != -ENODEV)
|
||||
dev_err(hub->intfdev,
|
||||
"%s failed (err = %d)\n", __func__, ret);
|
||||
dev_dbg(hub->intfdev,
|
||||
"get_hub_status failed: err = %d\n", ret);
|
||||
} else {
|
||||
*status = le16_to_cpu(hub->status->hub.wHubStatus);
|
||||
*change = le16_to_cpu(hub->status->hub.wHubChange);
|
||||
|
|
|
|||
|
|
@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg)
|
|||
|
||||
/**
|
||||
* dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state
|
||||
*
|
||||
* @hsotg: Programming view of DWC_otg controller
|
||||
*
|
||||
* @remotewakeup: Whether this is a remote wakeup
|
||||
*/
|
||||
void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4680,6 +4680,7 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
|
|||
{
|
||||
struct dwc2_hsotg *hsotg = to_hsotg(gadget);
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
dev_dbg(hsotg->dev, "%s: is_on: %d op_state: %d\n", __func__, is_on,
|
||||
hsotg->op_state);
|
||||
|
|
@ -4691,6 +4692,13 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
|
|||
}
|
||||
|
||||
spin_lock_irqsave(&hsotg->lock, flags);
|
||||
if (hsotg->in_ppd) {
|
||||
ret = dwc2_exit_partial_power_down(hsotg, 0, true);
|
||||
if (ret) {
|
||||
dev_err(hsotg->dev, "exit partial_power_down failed\n");
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
if (is_on) {
|
||||
hsotg->enabled = 1;
|
||||
dwc2_hsotg_core_init_disconnected(hsotg, false);
|
||||
|
|
@ -4704,9 +4712,10 @@ static int dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on)
|
|||
}
|
||||
|
||||
hsotg->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
exit:
|
||||
spin_unlock_irqrestore(&hsotg->lock, flags);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active)
|
||||
|
|
|
|||
|
|
@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work)
|
|||
ret, dwc->current_limit);
|
||||
}
|
||||
|
||||
static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc)
|
||||
static int dwc3_psy_notifier(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct power_supply *usb_psy;
|
||||
const char *usb_psy_name;
|
||||
struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb);
|
||||
struct power_supply *psy = data;
|
||||
unsigned long flags;
|
||||
|
||||
if (dwc->usb_psy)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
/* Explicitly get the reference for this psy */
|
||||
psy = power_supply_get_by_name(dwc->usb_psy_name);
|
||||
if (!psy)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
/*
|
||||
* The USB power_supply may already be set. This can happen if notifier
|
||||
* callbacks for the USB power_supply race, or if a previous notifier
|
||||
* callback has already successfully fetched and associated the instance.
|
||||
* In such cases, release the newly acquired reference and ignore
|
||||
* subsequent notifications until the notifier is unregistered.
|
||||
*/
|
||||
if (dwc->usb_psy) {
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
power_supply_put(psy);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
dwc->usb_psy = psy;
|
||||
if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED)
|
||||
schedule_work(&dwc->vbus_draw_work);
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static void dwc3_get_usb_power_supply(struct dwc3 *dwc)
|
||||
{
|
||||
struct power_supply *psy;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name);
|
||||
ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
usb_psy = power_supply_get_by_name(usb_psy_name);
|
||||
if (!usb_psy)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
return;
|
||||
|
||||
INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work);
|
||||
return usb_psy;
|
||||
|
||||
dwc->current_limit = DWC3_CURRENT_UNSPECIFIED;
|
||||
dwc->psy_nb.notifier_call = dwc3_psy_notifier;
|
||||
ret = power_supply_reg_notifier(&dwc->psy_nb);
|
||||
if (ret) {
|
||||
dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret);
|
||||
dwc->psy_nb.notifier_call = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
psy = power_supply_get_by_name(dwc->usb_psy_name);
|
||||
if (!psy)
|
||||
return;
|
||||
|
||||
/* Unregister the notifier now that we have the power supply */
|
||||
power_supply_unreg_notifier(&dwc->psy_nb);
|
||||
dwc->psy_nb.notifier_call = NULL;
|
||||
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
/*
|
||||
* It is possible that the notifier callback ran before we reached here
|
||||
* and successfully fetched the power supply. In that case we need to
|
||||
* release the above reference.
|
||||
*/
|
||||
if (dwc->usb_psy) {
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
power_supply_put(psy);
|
||||
return;
|
||||
}
|
||||
|
||||
dwc->usb_psy = psy;
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
}
|
||||
|
||||
int dwc3_core_probe(const struct dwc3_probe_data *data)
|
||||
|
|
@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
|
|||
|
||||
dwc3_get_software_properties(dwc, &data->properties);
|
||||
|
||||
dwc->usb_psy = dwc3_get_usb_power_supply(dwc);
|
||||
if (IS_ERR(dwc->usb_psy))
|
||||
return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n");
|
||||
spin_lock_init(&dwc->lock);
|
||||
|
||||
dwc3_get_usb_power_supply(dwc);
|
||||
|
||||
if (!data->ignore_clocks_and_resets) {
|
||||
dwc->reset = devm_reset_control_array_get_optional_shared(dev);
|
||||
|
|
@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
|
|||
dwc->num_usb3_ports = 1;
|
||||
}
|
||||
|
||||
spin_lock_init(&dwc->lock);
|
||||
mutex_init(&dwc->mutex);
|
||||
|
||||
pm_runtime_get_noresume(dev);
|
||||
|
|
@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
|
|||
err_assert_reset:
|
||||
reset_control_assert(dwc->reset);
|
||||
err_put_psy:
|
||||
if (dwc->psy_nb.notifier_call)
|
||||
power_supply_unreg_notifier(&dwc->psy_nb);
|
||||
if (dwc->usb_psy)
|
||||
power_supply_put(dwc->usb_psy);
|
||||
|
||||
|
|
@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc)
|
|||
|
||||
dwc3_free_event_buffers(dwc);
|
||||
|
||||
if (dwc->psy_nb.notifier_call)
|
||||
power_supply_unreg_notifier(&dwc->psy_nb);
|
||||
|
||||
if (dwc->usb_psy) {
|
||||
cancel_work_sync(&dwc->vbus_draw_work);
|
||||
power_supply_put(dwc->usb_psy);
|
||||
|
|
|
|||
|
|
@ -722,7 +722,6 @@ struct dwc3_event_buffer {
|
|||
* @cancelled_list: list of cancelled requests for this endpoint
|
||||
* @pending_list: list of pending requests for this endpoint
|
||||
* @started_list: list of started requests on this endpoint
|
||||
* @regs: pointer to first endpoint register
|
||||
* @trb_pool: array of transaction buffers
|
||||
* @trb_pool_dma: dma address of @trb_pool
|
||||
* @trb_enqueue: enqueue 'pointer' into TRB array
|
||||
|
|
@ -1059,6 +1058,8 @@ struct dwc3_glue_ops {
|
|||
* @role_switch_default_mode: default operation mode of controller while
|
||||
* usb role is USB_ROLE_NONE.
|
||||
* @usb_psy: pointer to power supply interface.
|
||||
* @usb_psy_name: name of the USB power supply
|
||||
* @psy_nb: power supply notifier block
|
||||
* @vbus_draw_work: Work to set the vbus drawing limit
|
||||
* @current_limit: How much current to draw from vbus, in milliAmperes.
|
||||
* @usb2_phy: pointer to USB2 PHY
|
||||
|
|
@ -1251,9 +1252,13 @@ struct dwc3 {
|
|||
enum usb_dr_mode role_switch_default_mode;
|
||||
|
||||
struct power_supply *usb_psy;
|
||||
const char *usb_psy_name;
|
||||
struct notifier_block psy_nb;
|
||||
struct work_struct vbus_draw_work;
|
||||
unsigned int current_limit;
|
||||
|
||||
#define DWC3_CURRENT_UNSPECIFIED UINT_MAX
|
||||
|
||||
u32 fladj;
|
||||
u32 ref_clk_per;
|
||||
u32 irq_gadget;
|
||||
|
|
|
|||
|
|
@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62)
|
|||
|
||||
dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg);
|
||||
|
||||
clk_prepare_enable(am62->usb2_refclk);
|
||||
ret = clk_prepare_enable(am62->usb2_refclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Set mode valid bit to indicate role is valid */
|
||||
reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL);
|
||||
|
|
@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev)
|
|||
{
|
||||
struct dwc3_am62 *am62 = dev_get_drvdata(dev);
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG);
|
||||
if (reg != USBSS_DEBUG_CFG_DISABLED) {
|
||||
/* lost power/context */
|
||||
dwc3_ti_init(am62);
|
||||
ret = dwc3_ti_init(am62);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF);
|
||||
clk_prepare_enable(am62->usb2_refclk);
|
||||
ret = clk_prepare_enable(am62->usb2_refclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (device_may_wakeup(dev)) {
|
||||
|
|
|
|||
|
|
@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on)
|
|||
pm_runtime_mark_last_busy(qcom->dev);
|
||||
}
|
||||
|
||||
struct dwc3_glue_ops dwc3_qcom_glue_ops = {
|
||||
static struct dwc3_glue_ops dwc3_qcom_glue_ops = {
|
||||
.pre_set_role = dwc3_qcom_set_role_notifier,
|
||||
.pre_run_stop = dwc3_qcom_run_stop_notifier,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data)
|
|||
|
||||
dwc3_xlnx_mask_phy_rst(priv_data, false);
|
||||
|
||||
/* Assert and De-assert reset */
|
||||
ret = reset_control_assert(crst);
|
||||
if (ret < 0) {
|
||||
dev_err_probe(dev, ret, "failed to assert Reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(crst);
|
||||
if (ret < 0) {
|
||||
dev_err_probe(dev, ret, "failed to De-assert Reset\n");
|
||||
return ret;
|
||||
}
|
||||
/* assert and deassert reset */
|
||||
ret = reset_control_reset(crst);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to assert and deassert reset\n");
|
||||
|
||||
dwc3_xlnx_mask_phy_rst(priv_data, true);
|
||||
dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG);
|
||||
|
|
@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data)
|
|||
}
|
||||
|
||||
if (priv_data->usb3_phy) {
|
||||
/* Set PIPE Power Present signal in FPD Power Present Register*/
|
||||
/* Set PIPE Power Present signal in FPD Power Present Register */
|
||||
writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT);
|
||||
/* Set the PIPE Clock Select bit in FPD PIPE Clock register */
|
||||
writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK);
|
||||
|
|
|
|||
|
|
@ -3124,15 +3124,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g,
|
|||
static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
|
||||
{
|
||||
struct dwc3 *dwc = gadget_to_dwc(g);
|
||||
unsigned long flags;
|
||||
|
||||
if (dwc->usb2_phy)
|
||||
return usb_phy_set_power(dwc->usb2_phy, mA);
|
||||
|
||||
if (!dwc->usb_psy)
|
||||
return -EOPNOTSUPP;
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
if (!dwc->usb_psy) {
|
||||
if (!dwc->psy_nb.notifier_call) {
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
dwc->current_limit = mA;
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dwc->current_limit = mA;
|
||||
schedule_work(&dwc->vbus_draw_work);
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -3497,6 +3508,7 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc)
|
|||
}
|
||||
|
||||
dwc3_debugfs_remove_endpoint_dir(dep);
|
||||
cancel_delayed_work_sync(&dep->nostream_work);
|
||||
kfree(dep);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/hcd.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "../host/xhci-port.h"
|
||||
#include "../host/xhci-ext-caps.h"
|
||||
|
|
@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
|
|||
return;
|
||||
}
|
||||
|
||||
op_regs_base = HC_LENGTH(readl(xhci_regs));
|
||||
op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs));
|
||||
reg = readl(xhci_regs + XHCI_HCSPARAMS1);
|
||||
port_num = HCS_MAX_PORTS(reg);
|
||||
port_num = FIELD_GET(HCS_MAX_PORTS, reg);
|
||||
|
||||
for (i = 1; i <= port_num; i++) {
|
||||
offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1);
|
||||
|
|
|
|||
|
|
@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy)
|
|||
if (copy) {
|
||||
str = copy;
|
||||
} else {
|
||||
str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL);
|
||||
str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL);
|
||||
if (!str)
|
||||
return -ENOMEM;
|
||||
}
|
||||
strcpy(str, s);
|
||||
memcpy(str, s, ret + 1);
|
||||
if (str[ret - 1] == '\n')
|
||||
str[ret - 1] = '\0';
|
||||
*s_copy = str;
|
||||
|
|
@ -1177,7 +1177,7 @@ static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page)
|
|||
struct gadget_info *gi = os_desc_item_to_gadget_info(item);
|
||||
int res;
|
||||
|
||||
res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN,
|
||||
res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN / 2,
|
||||
UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1);
|
||||
page[res++] = '\n';
|
||||
|
||||
|
|
@ -1199,7 +1199,7 @@ static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page,
|
|||
mutex_lock(&gi->lock);
|
||||
res = utf8s_to_utf16s(page, l,
|
||||
UTF16_LITTLE_ENDIAN, (wchar_t *) gi->qw_sign,
|
||||
OS_STRING_QW_SIGN_LEN);
|
||||
OS_STRING_QW_SIGN_LEN / 2);
|
||||
if (res > 0)
|
||||
res = len;
|
||||
mutex_unlock(&gi->lock);
|
||||
|
|
|
|||
|
|
@ -159,7 +159,9 @@ struct ffs_epfile {
|
|||
struct mutex mutex;
|
||||
|
||||
struct ffs_data *ffs;
|
||||
struct ffs_ep *ep; /* P: ffs->eps_lock */
|
||||
struct ffs_ep *ep; /* P: ffs->eps_lock */
|
||||
struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */
|
||||
struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */
|
||||
|
||||
/*
|
||||
* Buffer for holding data from partial reads which may happen since
|
||||
|
|
@ -219,12 +221,13 @@ struct ffs_epfile {
|
|||
struct ffs_buffer *read_buffer;
|
||||
#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
|
||||
|
||||
char name[5];
|
||||
char name[8];
|
||||
|
||||
unsigned char in; /* P: ffs->eps_lock */
|
||||
unsigned char isoc; /* P: ffs->eps_lock */
|
||||
|
||||
unsigned char _pad;
|
||||
u8 zlp_enabled; /* P: ffs->eps_lock */
|
||||
bool is_rw_proxy;
|
||||
|
||||
/* Protects dmabufs */
|
||||
struct mutex dmabufs_mutex;
|
||||
|
|
@ -548,6 +551,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
|
|||
if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
|
||||
return -EIDRM;
|
||||
|
||||
retry:
|
||||
/* Acquire mutex */
|
||||
ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
|
||||
if (ret < 0)
|
||||
|
|
@ -582,10 +586,15 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
|
|||
break;
|
||||
}
|
||||
|
||||
if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq,
|
||||
ffs->ev.count)) {
|
||||
ret = -EINTR;
|
||||
break;
|
||||
if (!ffs->ev.count) {
|
||||
spin_unlock_irq(&ffs->ev.waitq.lock);
|
||||
mutex_unlock(&ffs->mutex);
|
||||
|
||||
if (wait_event_interruptible_exclusive(ffs->ev.waitq,
|
||||
ffs->ev.count))
|
||||
return -EINTR;
|
||||
|
||||
goto retry;
|
||||
}
|
||||
|
||||
/* unlocks spinlock */
|
||||
|
|
@ -867,9 +876,15 @@ static void ffs_user_copy_worker(struct work_struct *work)
|
|||
bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD;
|
||||
|
||||
if (io_data->read && ret > 0) {
|
||||
kthread_use_mm(io_data->mm);
|
||||
ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data);
|
||||
kthread_unuse_mm(io_data->mm);
|
||||
if (mmget_not_zero(io_data->mm)) {
|
||||
kthread_use_mm(io_data->mm);
|
||||
ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data);
|
||||
kthread_unuse_mm(io_data->mm);
|
||||
mmput(io_data->mm);
|
||||
} else {
|
||||
ret = -EFAULT;
|
||||
}
|
||||
mmdrop(io_data->mm);
|
||||
}
|
||||
|
||||
io_data->kiocb->ki_complete(io_data->kiocb, ret);
|
||||
|
|
@ -979,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static struct ffs_ep *ffs_epfile_wait_ep(struct file *file)
|
||||
static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file)
|
||||
{
|
||||
struct ffs_epfile *epfile = file->private_data;
|
||||
struct ffs_ep *ep;
|
||||
int ret;
|
||||
|
||||
|
|
@ -1008,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
|
|||
char *data = NULL;
|
||||
ssize_t ret, data_len = -EINVAL;
|
||||
int halt;
|
||||
bool is_rw_proxy = epfile->is_rw_proxy;
|
||||
|
||||
/* Are we still active? */
|
||||
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
|
||||
return -ENODEV;
|
||||
|
||||
ep = ffs_epfile_wait_ep(file);
|
||||
/* Proxy to base endpoint if rw_proxy */
|
||||
if (is_rw_proxy)
|
||||
epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in;
|
||||
|
||||
ep = ffs_epfile_wait_ep(epfile, file);
|
||||
if (IS_ERR(ep))
|
||||
return PTR_ERR(ep);
|
||||
|
||||
/* Do we halt? */
|
||||
halt = (!io_data->read == !epfile->in);
|
||||
halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in);
|
||||
if (halt && epfile->isoc)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -1115,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
|
|||
req->buf = data;
|
||||
req->num_sgs = 0;
|
||||
}
|
||||
|
||||
req->zero = !io_data->read ? epfile->zlp_enabled : 0;
|
||||
req->length = data_len;
|
||||
|
||||
io_data->buf = data;
|
||||
|
|
@ -1166,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
|
|||
req->buf = data;
|
||||
req->num_sgs = 0;
|
||||
}
|
||||
|
||||
req->zero = !io_data->read ? epfile->zlp_enabled : 0;
|
||||
req->length = data_len;
|
||||
|
||||
io_data->buf = data;
|
||||
|
|
@ -1264,16 +1287,22 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
|||
|
||||
kiocb->private = p;
|
||||
|
||||
if (p->aio)
|
||||
if (p->aio) {
|
||||
mmgrab(p->mm);
|
||||
kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);
|
||||
}
|
||||
|
||||
res = ffs_epfile_io(kiocb->ki_filp, p);
|
||||
if (res == -EIOCBQUEUED)
|
||||
return res;
|
||||
if (p->aio)
|
||||
if (p->aio) {
|
||||
kiocb->ki_complete(kiocb, res);
|
||||
mmdrop(p->mm);
|
||||
kfree(p);
|
||||
else
|
||||
return -EIOCBQUEUED;
|
||||
} else {
|
||||
*from = p->data;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
@ -1308,16 +1337,21 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to)
|
|||
|
||||
kiocb->private = p;
|
||||
|
||||
if (p->aio)
|
||||
if (p->aio) {
|
||||
mmgrab(p->mm);
|
||||
kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);
|
||||
}
|
||||
|
||||
res = ffs_epfile_io(kiocb->ki_filp, p);
|
||||
if (res == -EIOCBQUEUED)
|
||||
return res;
|
||||
|
||||
if (p->aio) {
|
||||
kiocb->ki_complete(kiocb, res);
|
||||
mmdrop(p->mm);
|
||||
kfree(p->to_free);
|
||||
kfree(p);
|
||||
return -EIOCBQUEUED;
|
||||
} else {
|
||||
*to = p->data;
|
||||
}
|
||||
|
|
@ -1643,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file,
|
|||
|
||||
priv = attach->importer_priv;
|
||||
|
||||
ep = ffs_epfile_wait_ep(file);
|
||||
ep = ffs_epfile_wait_ep(epfile, file);
|
||||
if (IS_ERR(ep)) {
|
||||
ret = PTR_ERR(ep);
|
||||
goto err_attachment_put;
|
||||
|
|
@ -1682,13 +1716,13 @@ static int ffs_dmabuf_transfer(struct file *file,
|
|||
/* In the meantime, endpoint got disabled or changed. */
|
||||
if (epfile->ep != ep) {
|
||||
ret = -ESHUTDOWN;
|
||||
goto err_fence_put;
|
||||
goto err_fence_free;
|
||||
}
|
||||
|
||||
usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC);
|
||||
if (!usb_req) {
|
||||
ret = -ENOMEM;
|
||||
goto err_fence_put;
|
||||
goto err_fence_free;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1709,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file,
|
|||
|
||||
/* Now that the dma_fence is in place, queue the transfer. */
|
||||
|
||||
usb_req->zero = epfile->zlp_enabled;
|
||||
usb_req->length = req->length;
|
||||
usb_req->buf = NULL;
|
||||
usb_req->sg = priv->sgt->sgl;
|
||||
|
|
@ -1737,9 +1772,9 @@ static int ffs_dmabuf_transfer(struct file *file,
|
|||
|
||||
return ret;
|
||||
|
||||
err_fence_put:
|
||||
err_fence_free:
|
||||
spin_unlock_irq(&epfile->ffs->eps_lock);
|
||||
dma_fence_put(&fence->base);
|
||||
kfree(fence);
|
||||
err_resv_unlock:
|
||||
dma_resv_unlock(dmabuf->resv);
|
||||
err_attachment_put:
|
||||
|
|
@ -1756,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
|
|||
struct ffs_epfile *epfile = file->private_data;
|
||||
struct ffs_ep *ep;
|
||||
int ret;
|
||||
__u32 enable_zlp = 0;
|
||||
|
||||
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
|
||||
return -ENODEV;
|
||||
|
||||
if (epfile->is_rw_proxy)
|
||||
return -ENOTTY;
|
||||
|
||||
switch (code) {
|
||||
case FUNCTIONFS_DMABUF_ATTACH:
|
||||
{
|
||||
|
|
@ -1788,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
|
|||
|
||||
return ffs_dmabuf_transfer(file, &req);
|
||||
}
|
||||
/*
|
||||
* We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace
|
||||
* to configure ZLP behavior immediately without blocking indefinitely
|
||||
* while waiting for the USB host to connect and enable the endpoint.
|
||||
*/
|
||||
case FUNCTIONFS_ENDPOINT_ENABLE_ZLP:
|
||||
if (!epfile->in)
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp)))
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock_irq(&epfile->ffs->eps_lock);
|
||||
epfile->zlp_enabled = !!enable_zlp;
|
||||
spin_unlock_irq(&epfile->ffs->eps_lock);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wait for endpoint to be enabled */
|
||||
ep = ffs_epfile_wait_ep(file);
|
||||
ep = ffs_epfile_wait_ep(epfile, file);
|
||||
if (IS_ERR(ep))
|
||||
return PTR_ERR(ep);
|
||||
|
||||
|
|
@ -2191,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs)
|
|||
|
||||
if (epfiles)
|
||||
ffs_epfiles_destroy(ffs->sb, epfiles,
|
||||
ffs->eps_count);
|
||||
ffs->epfiles_count);
|
||||
|
||||
if (ffs->setup_state == FFS_SETUP_PENDING)
|
||||
__ffs_ep0_stall(ffs);
|
||||
|
|
@ -2250,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs)
|
|||
* copy of epfile will save us from use-after-free.
|
||||
*/
|
||||
if (epfiles) {
|
||||
ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count);
|
||||
ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count);
|
||||
ffs->epfiles = NULL;
|
||||
}
|
||||
|
||||
|
|
@ -2348,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs)
|
|||
static int ffs_epfiles_create(struct ffs_data *ffs)
|
||||
{
|
||||
struct ffs_epfile *epfile, *epfiles;
|
||||
unsigned i, count;
|
||||
unsigned int i, count, epfiles_count;
|
||||
int err;
|
||||
|
||||
count = ffs->eps_count;
|
||||
epfiles = kzalloc_objs(*epfiles, count);
|
||||
epfiles_count = count;
|
||||
if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS)
|
||||
epfiles_count += count / 2;
|
||||
ffs->epfiles_count = epfiles_count;
|
||||
|
||||
epfiles = kzalloc_objs(*epfiles, epfiles_count);
|
||||
if (!epfiles)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -2375,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
|
|||
}
|
||||
}
|
||||
|
||||
if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) {
|
||||
struct ffs_epfile *comp = epfiles + count;
|
||||
|
||||
for (i = 0; i < count; i += 2, ++comp) {
|
||||
struct ffs_epfile *ep1 = &epfiles[i];
|
||||
struct ffs_epfile *ep2 = &epfiles[i + 1];
|
||||
bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK;
|
||||
|
||||
comp->ffs = ffs;
|
||||
comp->is_rw_proxy = true;
|
||||
comp->epfile_in = ep1_in ? ep1 : ep2;
|
||||
comp->epfile_out = ep1_in ? ep2 : ep1;
|
||||
mutex_init(&comp->mutex);
|
||||
mutex_init(&comp->dmabufs_mutex);
|
||||
INIT_LIST_HEAD(&comp->dmabufs);
|
||||
snprintf(comp->name, sizeof(comp->name), "%s_rw",
|
||||
epfiles[i].name);
|
||||
err = ffs_sb_create_file(ffs->sb, comp->name,
|
||||
comp, &ffs_epfile_operations);
|
||||
if (err) {
|
||||
ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ffs->epfiles = epfiles;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2952,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
|
|||
FUNCTIONFS_VIRTUAL_ADDR |
|
||||
FUNCTIONFS_EVENTFD |
|
||||
FUNCTIONFS_ALL_CTRL_RECIP |
|
||||
FUNCTIONFS_CONFIG0_SETUP)) {
|
||||
FUNCTIONFS_CONFIG0_SETUP |
|
||||
FUNCTIONFS_RW_PROXY_EPS)) {
|
||||
ret = -ENOSYS;
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -3040,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) {
|
||||
if (ffs->eps_count % 2) {
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 1; i < ffs->eps_count; i += 2) {
|
||||
if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) ==
|
||||
(ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) {
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ffs->raw_descs_data = _data;
|
||||
ffs->raw_descs = raw_descs;
|
||||
ffs->raw_descs_length = data - raw_descs;
|
||||
|
|
@ -3335,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
|
|||
struct usb_request *req;
|
||||
struct usb_ep *ep;
|
||||
u8 bEndpointAddress;
|
||||
u16 wMaxPacketSize;
|
||||
__le16 wMaxPacketSize;
|
||||
|
||||
/*
|
||||
* We back up bEndpointAddress because autoconfig overwrites
|
||||
|
|
|
|||
|
|
@ -203,7 +203,6 @@
|
|||
/*------------------------------------------------------------------------*/
|
||||
|
||||
#define FSG_DRIVER_DESC "Mass Storage Function"
|
||||
#define FSG_DRIVER_VERSION "2009/09/11"
|
||||
|
||||
static const char fsg_string_interface[] = "Mass Storage";
|
||||
|
||||
|
|
@ -3513,8 +3512,6 @@ static struct usb_function_instance *fsg_alloc_inst(void)
|
|||
if (rc)
|
||||
goto release_common;
|
||||
|
||||
pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.removable = true;
|
||||
rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0",
|
||||
|
|
|
|||
|
|
@ -2473,6 +2473,7 @@ static void f_midi2_ep_opts_release(struct config_item *item)
|
|||
{
|
||||
struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item);
|
||||
|
||||
configfs_remove_default_groups(&opts->group);
|
||||
kfree(opts->info.ep_name);
|
||||
kfree(opts->info.product_id);
|
||||
kfree(opts);
|
||||
|
|
@ -2639,6 +2640,7 @@ static void f_midi2_free_inst(struct usb_function_instance *f)
|
|||
|
||||
opts = container_of(f, struct f_midi2_opts, func_inst);
|
||||
|
||||
configfs_remove_default_groups(&opts->func_inst.group);
|
||||
kfree(opts->info.iface_name);
|
||||
kfree(opts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1675,19 +1675,25 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
|
|||
|
||||
opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts,
|
||||
func_inst);
|
||||
mutex_lock(&opts->dep_lock);
|
||||
if (!opts->ready)
|
||||
goto unlock_dep;
|
||||
if (!READ_ONCE(opts->ready))
|
||||
goto unlock_inst;
|
||||
|
||||
if (opts->has_dep) {
|
||||
if (!try_module_get(opts->dependent))
|
||||
goto unlock_dep;
|
||||
goto unlock_inst;
|
||||
} else {
|
||||
/*
|
||||
* configfs_depend_item_unlocked() may acquire the configfs
|
||||
* root inode lock when the target belongs to a different
|
||||
* subsystem. Calling it under dep_lock would create a
|
||||
* circular dependency:
|
||||
* dep_lock -> configfs inode lock -> su_mutex -> dep_lock
|
||||
*/
|
||||
ret = configfs_depend_item_unlocked(
|
||||
wwn->wwn_group.cg_subsys,
|
||||
&opts->func_inst.group.cg_item);
|
||||
if (ret)
|
||||
goto unlock_dep;
|
||||
goto unlock_inst;
|
||||
}
|
||||
|
||||
tpg = kzalloc_obj(struct usbg_tpg);
|
||||
|
|
@ -1714,7 +1720,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
|
|||
|
||||
tpg_instances[i].tpg = tpg;
|
||||
tpg->fi = tpg_instances[i].func_inst;
|
||||
mutex_unlock(&opts->dep_lock);
|
||||
mutex_unlock(&tpg_instances_lock);
|
||||
return &tpg->se_tpg;
|
||||
|
||||
|
|
@ -1727,8 +1732,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
|
|||
module_put(opts->dependent);
|
||||
else
|
||||
configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
|
||||
unlock_dep:
|
||||
mutex_unlock(&opts->dep_lock);
|
||||
unlock_inst:
|
||||
mutex_unlock(&tpg_instances_lock);
|
||||
|
||||
|
|
@ -2024,7 +2027,7 @@ static const struct target_core_fabric_ops usbg_ops = {
|
|||
.fabric_enable_tpg = usbg_enable_tpg,
|
||||
.fabric_drop_tpg = usbg_drop_tpg,
|
||||
.fabric_post_link = usbg_port_link,
|
||||
.fabric_pre_unlink = usbg_port_unlink,
|
||||
.fabric_post_unlink = usbg_port_unlink,
|
||||
.fabric_init_nodeacl = usbg_init_nodeacl,
|
||||
|
||||
.tfc_wwn_attrs = usbg_wwn_attrs,
|
||||
|
|
@ -2666,9 +2669,7 @@ static int tcm_set_name(struct usb_function_instance *f, const char *name)
|
|||
|
||||
pr_debug("tcm: Activating %s\n", name);
|
||||
|
||||
mutex_lock(&opts->dep_lock);
|
||||
opts->ready = true;
|
||||
mutex_unlock(&opts->dep_lock);
|
||||
WRITE_ONCE(opts->ready, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1594,7 +1594,8 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
|
|||
const char *page, size_t len) \
|
||||
{ \
|
||||
struct f_uac1_opts *opts = to_f_uac1_opts(item); \
|
||||
char *split_page = NULL; \
|
||||
char *buf = NULL; \
|
||||
char *split_page; \
|
||||
int ret = -EINVAL; \
|
||||
char *token; \
|
||||
u32 num; \
|
||||
|
|
@ -1608,18 +1609,22 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
|
|||
\
|
||||
i = 0; \
|
||||
memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
|
||||
split_page = kstrdup(page, GFP_KERNEL); \
|
||||
buf = kstrdup(page, GFP_KERNEL); \
|
||||
split_page = buf; \
|
||||
while ((token = strsep(&split_page, ",")) != NULL) { \
|
||||
ret = kstrtou32(token, 0, &num); \
|
||||
if (ret) \
|
||||
goto end; \
|
||||
\
|
||||
if (i >= UAC_MAX_RATES) { \
|
||||
ret = -EINVAL; \
|
||||
goto end; \
|
||||
} \
|
||||
opts->name##s[i++] = num; \
|
||||
ret = len; \
|
||||
}; \
|
||||
\
|
||||
end: \
|
||||
kfree(split_page); \
|
||||
kfree(buf); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return ret; \
|
||||
} \
|
||||
|
|
|
|||
|
|
@ -888,60 +888,10 @@ UAC1_INT_ATTRIBUTE(req_buf_size);
|
|||
UAC1_INT_ATTRIBUTE(req_count);
|
||||
UAC1_INT_ATTRIBUTE(audio_buf_size);
|
||||
|
||||
#define UAC1_STR_ATTRIBUTE(name) \
|
||||
static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
|
||||
char *page) \
|
||||
{ \
|
||||
struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
|
||||
int result; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
result = sprintf(page, "%s\n", opts->name); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
\
|
||||
return result; \
|
||||
} \
|
||||
\
|
||||
static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
|
||||
int ret = -EBUSY; \
|
||||
char *tmp; \
|
||||
\
|
||||
mutex_lock(&opts->lock); \
|
||||
if (opts->refcnt) \
|
||||
goto end; \
|
||||
\
|
||||
tmp = kstrndup(page, len, GFP_KERNEL); \
|
||||
if (tmp) { \
|
||||
ret = -ENOMEM; \
|
||||
goto end; \
|
||||
} \
|
||||
if (opts->name##_alloc) \
|
||||
kfree(opts->name); \
|
||||
opts->name##_alloc = true; \
|
||||
opts->name = tmp; \
|
||||
ret = len; \
|
||||
\
|
||||
end: \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
CONFIGFS_ATTR(f_uac1_opts_, name)
|
||||
|
||||
UAC1_STR_ATTRIBUTE(fn_play);
|
||||
UAC1_STR_ATTRIBUTE(fn_cap);
|
||||
UAC1_STR_ATTRIBUTE(fn_cntl);
|
||||
|
||||
static struct configfs_attribute *f_uac1_attrs[] = {
|
||||
&f_uac1_opts_attr_req_buf_size,
|
||||
&f_uac1_opts_attr_req_count,
|
||||
&f_uac1_opts_attr_audio_buf_size,
|
||||
&f_uac1_opts_attr_fn_play,
|
||||
&f_uac1_opts_attr_fn_cap,
|
||||
&f_uac1_opts_attr_fn_cntl,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
|
@ -956,12 +906,6 @@ static void f_audio_free_inst(struct usb_function_instance *f)
|
|||
struct f_uac1_legacy_opts *opts;
|
||||
|
||||
opts = container_of(f, struct f_uac1_legacy_opts, func_inst);
|
||||
if (opts->fn_play_alloc)
|
||||
kfree(opts->fn_play);
|
||||
if (opts->fn_cap_alloc)
|
||||
kfree(opts->fn_cap);
|
||||
if (opts->fn_cntl_alloc)
|
||||
kfree(opts->fn_cntl);
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2012,7 +2012,8 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
|
|||
const char *page, size_t len) \
|
||||
{ \
|
||||
struct f_uac2_opts *opts = to_f_uac2_opts(item); \
|
||||
char *split_page = NULL; \
|
||||
char *buf = NULL; \
|
||||
char *split_page; \
|
||||
int ret = -EINVAL; \
|
||||
char *token; \
|
||||
u32 num; \
|
||||
|
|
@ -2026,18 +2027,22 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
|
|||
\
|
||||
i = 0; \
|
||||
memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
|
||||
split_page = kstrdup(page, GFP_KERNEL); \
|
||||
buf = kstrdup(page, GFP_KERNEL); \
|
||||
split_page = buf; \
|
||||
while ((token = strsep(&split_page, ",")) != NULL) { \
|
||||
ret = kstrtou32(token, 0, &num); \
|
||||
if (ret) \
|
||||
goto end; \
|
||||
\
|
||||
if (i >= UAC_MAX_RATES) { \
|
||||
ret = -EINVAL; \
|
||||
goto end; \
|
||||
} \
|
||||
opts->name##s[i++] = num; \
|
||||
ret = len; \
|
||||
}; \
|
||||
\
|
||||
end: \
|
||||
kfree(split_page); \
|
||||
kfree(buf); \
|
||||
mutex_unlock(&opts->lock); \
|
||||
return ret; \
|
||||
} \
|
||||
|
|
|
|||
|
|
@ -889,9 +889,12 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
v4l2_error:
|
||||
v4l2_device_unregister(&uvc->v4l2_dev);
|
||||
error:
|
||||
if (uvc->control_req)
|
||||
if (uvc->control_req) {
|
||||
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
|
||||
uvc->control_req = NULL;
|
||||
}
|
||||
kfree(uvc->control_buf);
|
||||
uvc->control_buf = NULL;
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
return ret;
|
||||
|
|
@ -1075,7 +1078,9 @@ static void uvc_function_unbind(struct usb_configuration *c,
|
|||
uvc->vdev_release_done = NULL;
|
||||
|
||||
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
|
||||
uvc->control_req = NULL;
|
||||
kfree(uvc->control_buf);
|
||||
uvc->control_buf = NULL;
|
||||
|
||||
usb_free_all_descriptors(f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev)
|
|||
ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret);
|
||||
return ret; // TODO: Clean up out_ep
|
||||
goto err_out_ep;
|
||||
}
|
||||
|
||||
prm->fb_ep_enabled = true;
|
||||
ret = usb_ep_enable(ep_fback);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret);
|
||||
return ret; // TODO: Clean up out_ep
|
||||
goto err_out_ep;
|
||||
}
|
||||
prm->fb_ep_enabled = true;
|
||||
req_len = ep_fback->maxpacket;
|
||||
|
||||
req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC);
|
||||
|
|
@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev)
|
|||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
|
||||
return 0;
|
||||
|
||||
err_out_ep:
|
||||
set_active(prm, false);
|
||||
free_ep(prm, ep);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(u_audio_start_capture);
|
||||
|
||||
|
|
@ -1177,6 +1183,20 @@ static struct snd_kcontrol_new u_audio_controls[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static void u_audio_card_free(struct snd_card *card)
|
||||
{
|
||||
struct snd_uac_chip *uac = card->private_data;
|
||||
|
||||
if (!uac)
|
||||
return;
|
||||
|
||||
kfree(uac->p_prm.reqs);
|
||||
kfree(uac->c_prm.reqs);
|
||||
kfree(uac->p_prm.rbuf);
|
||||
kfree(uac->c_prm.rbuf);
|
||||
kfree(uac);
|
||||
}
|
||||
|
||||
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
|
||||
const char *card_name)
|
||||
{
|
||||
|
|
@ -1256,6 +1276,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
|
|||
goto fail;
|
||||
|
||||
uac->card = card;
|
||||
card->private_data = uac;
|
||||
card->private_free = u_audio_card_free;
|
||||
|
||||
/*
|
||||
* Create first PCM device
|
||||
|
|
@ -1424,6 +1446,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
|
|||
|
||||
snd_fail:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
|
||||
fail:
|
||||
kfree(uac->p_prm.reqs);
|
||||
kfree(uac->c_prm.reqs);
|
||||
|
|
@ -1449,12 +1473,6 @@ void g_audio_cleanup(struct g_audio *g_audio)
|
|||
card = uac->card;
|
||||
if (card)
|
||||
snd_card_free_when_closed(card);
|
||||
|
||||
kfree(uac->p_prm.reqs);
|
||||
kfree(uac->c_prm.reqs);
|
||||
kfree(uac->p_prm.rbuf);
|
||||
kfree(uac->c_prm.rbuf);
|
||||
kfree(uac);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(g_audio_cleanup);
|
||||
|
||||
|
|
|
|||
|
|
@ -252,8 +252,14 @@ struct ffs_data {
|
|||
|
||||
unsigned short strings_count;
|
||||
unsigned short interfaces_count;
|
||||
|
||||
/*
|
||||
* eps_count tracks the number of underlying hardware endpoints.
|
||||
* epfiles_count tracks the total number of VFS endpoint files.
|
||||
* When companion endpoints are active, epfiles_count > eps_count.
|
||||
*/
|
||||
unsigned short eps_count;
|
||||
unsigned short _pad1;
|
||||
unsigned short epfiles_count;
|
||||
|
||||
/* filled by __ffs_data_got_strings() */
|
||||
/* ids in stringtabs are set in functionfs_bind() */
|
||||
|
|
|
|||
|
|
@ -62,9 +62,6 @@ struct f_uac1_legacy_opts {
|
|||
char *fn_cap;
|
||||
char *fn_cntl;
|
||||
unsigned bound:1;
|
||||
unsigned fn_play_alloc:1;
|
||||
unsigned fn_cap_alloc:1;
|
||||
unsigned fn_cntl_alloc:1;
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -821,7 +821,7 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
|
|||
/* Allocate a kthread for asynchronous hw submit handler. */
|
||||
video->kworker = kthread_run_worker(0, "UVCG");
|
||||
if (IS_ERR(video->kworker)) {
|
||||
uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n");
|
||||
uvcg_err(&uvc->func, "failed to create UVCG kworker\n");
|
||||
return PTR_ERR(video->kworker);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work)
|
|||
struct kiocb *iocb = priv->iocb;
|
||||
size_t ret;
|
||||
|
||||
kthread_use_mm(mm);
|
||||
ret = copy_to_iter(priv->buf, priv->actual, &priv->to);
|
||||
kthread_unuse_mm(mm);
|
||||
if (!ret)
|
||||
if (mmget_not_zero(mm)) {
|
||||
kthread_use_mm(mm);
|
||||
ret = copy_to_iter(priv->buf, priv->actual, &priv->to);
|
||||
kthread_unuse_mm(mm);
|
||||
mmput(mm);
|
||||
if (!ret)
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
ret = -EFAULT;
|
||||
}
|
||||
mmdrop(mm);
|
||||
|
||||
/* completing the iocb can drop the ctx and mm, don't touch mm after */
|
||||
iocb->ki_complete(iocb, ret);
|
||||
|
|
@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req)
|
|||
* complete the aio request immediately.
|
||||
*/
|
||||
if (priv->to_free == NULL || unlikely(req->actual == 0)) {
|
||||
mmdrop(priv->mm);
|
||||
kfree(req->buf);
|
||||
kfree(priv->to_free);
|
||||
kfree(priv);
|
||||
|
|
@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb,
|
|||
priv->epdata = epdata;
|
||||
priv->actual = 0;
|
||||
priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */
|
||||
mmgrab(priv->mm);
|
||||
|
||||
/* each kiocb is coupled to one usb_request, but we can't
|
||||
* allocate or submit those if the host disconnected.
|
||||
|
|
@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb,
|
|||
|
||||
fail:
|
||||
spin_unlock_irq(&epdata->dev->lock);
|
||||
mmdrop(priv->mm);
|
||||
kfree(priv->to_free);
|
||||
kfree(priv);
|
||||
put_ep(epdata);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS;
|
|||
*/
|
||||
#define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS
|
||||
|
||||
#endif /* CONFIG_USB_DEBUG */
|
||||
#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
|
||||
|
||||
FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data);
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ config USB_BCM63XX_UDC
|
|||
|
||||
config USB_FSL_USB2
|
||||
tristate "Freescale Highspeed USB DR Peripheral Controller"
|
||||
depends on FSL_SOC
|
||||
depends on FSL_SOC || COMPILE_TEST
|
||||
help
|
||||
Some of Freescale PowerPC and i.MX processors have a High Speed
|
||||
Dual-Role(DR) USB controller, which supports device mode.
|
||||
|
|
|
|||
|
|
@ -1431,25 +1431,12 @@ static void ast_udc_init_hw(struct ast_udc_dev *udc)
|
|||
ast_udc_write(udc, 0, AST_UDC_EP0_CTRL);
|
||||
}
|
||||
|
||||
static void ast_udc_remove(struct platform_device *pdev)
|
||||
static void ast_udc_cleanup(struct platform_device *pdev)
|
||||
{
|
||||
struct ast_udc_dev *udc = platform_get_drvdata(pdev);
|
||||
unsigned long flags;
|
||||
u32 ctrl;
|
||||
|
||||
usb_del_gadget_udc(&udc->gadget);
|
||||
if (udc->driver) {
|
||||
/*
|
||||
* This is broken as only some cleanup is skipped, *udev is
|
||||
* freed and the register mapping goes away. Any further usage
|
||||
* probably crashes. Also the device is unbound, so the skipped
|
||||
* cleanup is never catched up later.
|
||||
*/
|
||||
dev_alert(&pdev->dev,
|
||||
"Driver is busy and still going away. Fasten your seat belts!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&udc->lock, flags);
|
||||
|
||||
/* Disable upstream port connection */
|
||||
|
|
@ -1469,6 +1456,26 @@ static void ast_udc_remove(struct platform_device *pdev)
|
|||
udc->ep0_buf = NULL;
|
||||
}
|
||||
|
||||
static void ast_udc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ast_udc_dev *udc = platform_get_drvdata(pdev);
|
||||
|
||||
usb_del_gadget_udc(&udc->gadget);
|
||||
if (udc->driver) {
|
||||
/*
|
||||
* This is broken as only some cleanup is skipped, *udev is
|
||||
* freed and the register mapping goes away. Any further usage
|
||||
* probably crashes. Also the device is unbound, so the skipped
|
||||
* cleanup is never catched up later.
|
||||
*/
|
||||
dev_alert(&pdev->dev,
|
||||
"Driver is busy and still going away. Fasten your seat belts!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ast_udc_cleanup(pdev);
|
||||
}
|
||||
|
||||
static int ast_udc_probe(struct platform_device *pdev)
|
||||
{
|
||||
enum usb_device_speed max_speed;
|
||||
|
|
@ -1521,6 +1528,12 @@ static int ast_udc_probe(struct platform_device *pdev)
|
|||
AST_UDC_NUM_ENDPOINTS,
|
||||
&udc->ep0_buf_dma, GFP_KERNEL);
|
||||
|
||||
if (!udc->ep0_buf) {
|
||||
clk_disable_unprepare(udc->clk);
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
udc->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
udc->gadget.max_speed = USB_SPEED_HIGH;
|
||||
udc->creq = udc->reg + AST_UDC_SETUP0;
|
||||
|
|
@ -1550,20 +1563,20 @@ static int ast_udc_probe(struct platform_device *pdev)
|
|||
udc->irq = platform_get_irq(pdev, 0);
|
||||
if (udc->irq < 0) {
|
||||
rc = udc->irq;
|
||||
goto err;
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0,
|
||||
KBUILD_MODNAME, udc);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to request interrupt\n");
|
||||
goto err;
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to add gadget udc\n");
|
||||
goto err;
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "Initialized udc in USB%s mode\n",
|
||||
|
|
@ -1571,9 +1584,10 @@ static int ast_udc_probe(struct platform_device *pdev)
|
|||
|
||||
return 0;
|
||||
|
||||
err_cleanup:
|
||||
ast_udc_cleanup(pdev);
|
||||
err:
|
||||
dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc);
|
||||
ast_udc_remove(pdev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1794,6 +1794,19 @@ static void at91udc_of_init(struct at91_udc *udc, struct device_node *np)
|
|||
udc->caps = match->data;
|
||||
}
|
||||
|
||||
/*
|
||||
* The work handler re-arms this timer, so shut the timer down before
|
||||
* draining the work; otherwise it restarts the polling cycle.
|
||||
*/
|
||||
static void at91_udc_shutdown_vbus_timer(struct at91_udc *udc)
|
||||
{
|
||||
if (!(udc->board.vbus_pin && udc->board.vbus_polled))
|
||||
return;
|
||||
|
||||
timer_shutdown_sync(&udc->vbus_timer);
|
||||
cancel_work_sync(&udc->vbus_timer_work);
|
||||
}
|
||||
|
||||
static int at91udc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
|
@ -1907,7 +1920,7 @@ static int at91udc_probe(struct platform_device *pdev)
|
|||
}
|
||||
retval = usb_add_gadget_udc(dev, &udc->gadget);
|
||||
if (retval)
|
||||
goto err_unprepare_iclk;
|
||||
goto err_shutdown_vbus;
|
||||
dev_set_drvdata(dev, udc);
|
||||
device_init_wakeup(dev, 1);
|
||||
create_debug_file(udc);
|
||||
|
|
@ -1915,6 +1928,8 @@ static int at91udc_probe(struct platform_device *pdev)
|
|||
INFO("%s version %s\n", driver_name, DRIVER_VERSION);
|
||||
return 0;
|
||||
|
||||
err_shutdown_vbus:
|
||||
at91_udc_shutdown_vbus_timer(udc);
|
||||
err_unprepare_iclk:
|
||||
clk_unprepare(udc->iclk);
|
||||
err_unprepare_fclk:
|
||||
|
|
@ -1933,6 +1948,9 @@ static void at91udc_remove(struct platform_device *pdev)
|
|||
DBG("remove\n");
|
||||
|
||||
usb_del_gadget_udc(&udc->gadget);
|
||||
|
||||
at91_udc_shutdown_vbus_timer(udc);
|
||||
|
||||
if (udc->driver) {
|
||||
dev_err(&pdev->dev,
|
||||
"Driver still in use but removing anyhow\n");
|
||||
|
|
|
|||
|
|
@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value,
|
|||
u16 index, u16 length)
|
||||
{
|
||||
u16 usb_status = 0;
|
||||
struct usb_request *usb_req;
|
||||
struct qe_req *req;
|
||||
struct qe_ep *ep;
|
||||
int status = 0;
|
||||
|
|
@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value,
|
|||
}
|
||||
}
|
||||
|
||||
req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL),
|
||||
struct qe_req, req);
|
||||
usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL);
|
||||
if (!usb_req)
|
||||
goto stall;
|
||||
|
||||
req = container_of(usb_req, struct qe_req, req);
|
||||
req->req.length = 2;
|
||||
req->req.buf = udc->statusbuf;
|
||||
*(u16 *)req->req.buf = cpu_to_le16(usb_status);
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@ struct pch_vbus_gpio_data {
|
|||
* @lock: protects all state
|
||||
* @stall: stall requested
|
||||
* @prot_stall: protcol stall requested
|
||||
* @registered: driver registered with system
|
||||
* @suspended: driver in suspended state
|
||||
* @connected: gadget driver associated
|
||||
* @vbus_session: required vbus_session state
|
||||
|
|
|
|||
|
|
@ -1951,7 +1951,6 @@ static int r8a66597_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
err_add_udc:
|
||||
r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req);
|
||||
clean_up2:
|
||||
if (r8a66597->pdata->on_chip)
|
||||
clk_disable_unprepare(r8a66597->clk);
|
||||
|
|
|
|||
|
|
@ -159,10 +159,9 @@ static int udc_plat_probe(struct platform_device *pdev)
|
|||
if (of_property_present(dev->of_node, "extcon")) {
|
||||
udc->edev = extcon_get_edev_by_phandle(dev, 0);
|
||||
if (IS_ERR(udc->edev)) {
|
||||
if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
dev_err(dev, "Invalid or missing extcon\n");
|
||||
ret = PTR_ERR(udc->edev);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "Invalid or missing extcon\n");
|
||||
goto exit_phy;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
struct fsl_usb2_dev_data {
|
||||
char *dr_mode; /* controller mode */
|
||||
|
|
@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev)
|
|||
const struct of_device_id *match;
|
||||
const unsigned char *prop;
|
||||
static unsigned int idx;
|
||||
int i;
|
||||
int i, err;
|
||||
|
||||
if (!of_device_is_available(np))
|
||||
return -ENODEV;
|
||||
|
|
@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev)
|
|||
}
|
||||
}
|
||||
|
||||
err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus");
|
||||
if (err)
|
||||
return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) {
|
||||
if (!dev_data->drivers[i])
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf)
|
|||
int ret;
|
||||
|
||||
if (!buf->page)
|
||||
buf->page = (char *)get_zeroed_page(GFP_KERNEL);
|
||||
buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
|
||||
if (!buf->page) {
|
||||
ret = -ENOMEM;
|
||||
|
|
@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file)
|
|||
{
|
||||
struct debug_buffer *buf = file->private_data;
|
||||
|
||||
if (buf) {
|
||||
if (buf->page)
|
||||
free_page((unsigned long)buf->page);
|
||||
kfree(buf);
|
||||
}
|
||||
kfree(buf->page);
|
||||
kfree(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,23 @@
|
|||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
/* hc_capbase - bitmasks */
|
||||
/* bits 7:0 - Capability Registers Length */
|
||||
#define HC_LENGTH(p) ((p) & 0xff)
|
||||
#define HC_LENGTH GENMASK(7, 0)
|
||||
/* bits 15:8 - Rsvd */
|
||||
/* bits 31:16 - Host Controller Interface Version Number */
|
||||
#define HC_VERSION(p) (((p) >> 16) & 0xffff)
|
||||
#define HC_VERSION GENMASK(31, 16)
|
||||
|
||||
/* HCSPARAMS1 - hcs_params1 - bitmasks */
|
||||
/* bits 7:0 - Number of Device Slots */
|
||||
#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff)
|
||||
#define HCS_SLOTS_MASK 0xff
|
||||
/* bits 18:8 - Number of Interrupters, max values is 1024 */
|
||||
#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff)
|
||||
/* bits 31:24, Max Ports - max value is 255 */
|
||||
#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff)
|
||||
#define HCS_SLOTS_MASK GENMASK(7, 0)
|
||||
/* bits 18:8 - Number of Interrupters, max values is 1024 */
|
||||
#define HCS_MAX_INTRS GENMASK(18, 8)
|
||||
/* bits 23:19 - Rsvd */
|
||||
/* bits 31:24 - Max Ports, max values is 255 */
|
||||
#define HCS_MAX_PORTS GENMASK(31, 24)
|
||||
|
||||
/* HCSPARAMS2 - hcs_params2 - bitmasks */
|
||||
/*
|
||||
|
|
@ -33,24 +34,25 @@
|
|||
* Note: 1 Frame = 8 Microframes
|
||||
* xHCI specification section 5.3.4.
|
||||
*/
|
||||
#define HCS_IST_VALUE(p) ((p) & 0x7)
|
||||
#define HCS_IST_VALUE GENMASK(2, 0)
|
||||
#define HCS_IST_UNIT BIT(3)
|
||||
/* bits 7:4 - Event Ring Segment Table Max, 2^(n) */
|
||||
#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf)
|
||||
#define HCS_ERST_MAX GENMASK(7, 4)
|
||||
/* bits 20:8 - Rsvd */
|
||||
/* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */
|
||||
#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f)
|
||||
#define HCS_MAX_SP_HI GENMASK(25, 21)
|
||||
/* bit 26 - Scratchpad restore, for save/restore HW state */
|
||||
/* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */
|
||||
#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f)
|
||||
#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p))
|
||||
#define HCS_MAX_SP_LO GENMASK(31, 27)
|
||||
#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \
|
||||
FIELD_GET(HCS_MAX_SP_LO, (p)))
|
||||
|
||||
/* HCSPARAMS3 - hcs_params3 - bitmasks */
|
||||
/* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */
|
||||
#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff)
|
||||
#define HCS_U1_LATENCY GENMASK(7, 0)
|
||||
/* bits 15:8 - Rsvd */
|
||||
/* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */
|
||||
#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff)
|
||||
#define HCS_U2_LATENCY GENMASK(31, 16)
|
||||
|
||||
/* HCCPARAMS1 - hcc_params - bitmasks */
|
||||
/* bit 0 - 64-bit Addressing Capability */
|
||||
|
|
@ -77,19 +79,20 @@
|
|||
/* bit 11 - Contiguous Frame ID Capability */
|
||||
#define HCC_CFC BIT(11)
|
||||
/* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */
|
||||
#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1))
|
||||
#define HCC_MAX_PSA GENMASK(15, 12)
|
||||
#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1))
|
||||
/* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */
|
||||
#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff)
|
||||
#define HCC_EXT_CAPS GENMASK(31, 16)
|
||||
|
||||
/* DBOFF - db_off - bitmasks */
|
||||
/* bits 1:0 - Rsvd */
|
||||
/* bits 31:2 - Doorbell Array Offset */
|
||||
#define DBOFF_MASK (0xfffffffc)
|
||||
#define DBOFF_MASK GENMASK(31, 2)
|
||||
|
||||
/* RTSOFF - run_regs_off - bitmasks */
|
||||
/* bits 4:0 - Rsvd */
|
||||
/* bits 31:5 - Runtime Register Space Offse */
|
||||
#define RTSOFF_MASK (~0x1f)
|
||||
#define RTSOFF_MASK GENMASK(31, 5)
|
||||
|
||||
/* HCCPARAMS2 - hcc_params2 - bitmasks */
|
||||
/* bit 0 - U3 Entry Capability */
|
||||
|
|
|
|||
|
|
@ -628,8 +628,8 @@ int dbc_tty_init(void)
|
|||
dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW |
|
||||
TTY_DRIVER_DYNAMIC_DEV);
|
||||
if (IS_ERR(dbc_tty_driver)) {
|
||||
idr_destroy(&dbc_tty_minors);
|
||||
return PTR_ERR(dbc_tty_driver);
|
||||
ret = PTR_ERR(dbc_tty_driver);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dbc_tty_driver->driver_name = "dbc_serial";
|
||||
|
|
@ -649,20 +649,27 @@ int dbc_tty_init(void)
|
|||
ret = tty_register_driver(dbc_tty_driver);
|
||||
if (ret) {
|
||||
pr_err("Can't register dbc tty driver\n");
|
||||
tty_driver_kref_put(dbc_tty_driver);
|
||||
idr_destroy(&dbc_tty_minors);
|
||||
goto fail_put;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
fail_put:
|
||||
tty_driver_kref_put(dbc_tty_driver);
|
||||
fail:
|
||||
idr_destroy(&dbc_tty_minors);
|
||||
dbc_tty_driver = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dbc_tty_exit(void)
|
||||
{
|
||||
if (dbc_tty_driver) {
|
||||
tty_unregister_driver(dbc_tty_driver);
|
||||
tty_driver_kref_put(dbc_tty_driver);
|
||||
dbc_tty_driver = NULL;
|
||||
}
|
||||
if (IS_ERR_OR_NULL(dbc_tty_driver))
|
||||
return;
|
||||
|
||||
tty_unregister_driver(dbc_tty_driver);
|
||||
tty_driver_kref_put(dbc_tty_driver);
|
||||
idr_destroy(&dbc_tty_minors);
|
||||
dbc_tty_driver = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-debugfs.h"
|
||||
|
|
@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci)
|
|||
xhci->debugfs_root, "reg-cap");
|
||||
|
||||
xhci_debugfs_regset(xhci,
|
||||
HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)),
|
||||
FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)),
|
||||
xhci_op_regs, ARRAY_SIZE(xhci_op_regs),
|
||||
xhci->debugfs_root, "reg-op");
|
||||
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
goto put_usb3_hcd;
|
||||
|
||||
if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
|
||||
if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4)
|
||||
xhci->shared_hcd->can_do_streams = 1;
|
||||
|
||||
ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf,
|
|||
|
||||
if ((xhci->quirks & XHCI_LPM_SUPPORT)) {
|
||||
reg = readl(&xhci->cap_regs->hcs_params3);
|
||||
ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg);
|
||||
ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg));
|
||||
ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg);
|
||||
ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg));
|
||||
}
|
||||
|
||||
if (wLength < le16_to_cpu(bos->wTotalLength))
|
||||
|
|
@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|||
}
|
||||
/* In spec software should not attempt to suspend
|
||||
* a port unless the port reports that it is in the
|
||||
* enabled (PED = ‘1’,PLS < ‘3’) state.
|
||||
* enabled (PED = '1',PLS < '3') state.
|
||||
*/
|
||||
portsc = xhci_portsc_readl(port);
|
||||
if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) ||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-trace.h"
|
||||
|
|
@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
|
|||
for (i = 0; i < 31; i++) {
|
||||
dev->eps[i].ep_index = i;
|
||||
dev->eps[i].vdev = dev;
|
||||
dev->eps[i].xhci = xhci;
|
||||
INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list);
|
||||
INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list);
|
||||
}
|
||||
|
|
@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
|
|||
return -ENOMEM;
|
||||
|
||||
virt_dev->eps[ep_index].skip = false;
|
||||
virt_dev->eps[ep_index].next_uframe = -1;
|
||||
ep_ring = virt_dev->eps[ep_index].new_ring;
|
||||
xhci_ring_init(xhci, ep_ring);
|
||||
|
||||
|
|
@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
|
|||
addr, port_offset, port_count, major_revision);
|
||||
/* Port count includes the current port offset */
|
||||
if (port_offset == 0 || (port_offset + port_count - 1) > num_ports)
|
||||
/* WTF? "Valid values are ‘1’ to MaxPorts" */
|
||||
/* WTF? "Valid values are '1' to MaxPorts" */
|
||||
return;
|
||||
|
||||
port_cap = &xhci->port_caps[xhci->num_port_caps++];
|
||||
|
|
@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags)
|
|||
if (!segs)
|
||||
segs = ERST_DEFAULT_SEGS;
|
||||
|
||||
max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2));
|
||||
max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2;
|
||||
segs = min(segs, max_segs);
|
||||
|
||||
ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev));
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci)
|
|||
* MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream,
|
||||
* and it's 3 when support it.
|
||||
*/
|
||||
if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4)
|
||||
if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4)
|
||||
xhci->quirks |= XHCI_BROKEN_STREAMS;
|
||||
}
|
||||
|
||||
|
|
@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
usb3_hcd = xhci_get_usb3_hcd(xhci);
|
||||
if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 &&
|
||||
if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 &&
|
||||
!(xhci->quirks & XHCI_BROKEN_STREAMS))
|
||||
usb3_hcd->can_do_streams = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-trace.h"
|
||||
|
|
@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|||
}
|
||||
|
||||
usb3_hcd = xhci_get_usb3_hcd(xhci);
|
||||
if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4)
|
||||
if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) &&
|
||||
GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4)
|
||||
usb3_hcd->can_do_streams = 1;
|
||||
|
||||
/* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s
|
|||
}
|
||||
|
||||
usb3_hcd = xhci_get_usb3_hcd(xhci);
|
||||
if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 &&
|
||||
if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 &&
|
||||
!(xhci->quirks & XHCI_BROKEN_STREAMS))
|
||||
usb3_hcd->can_do_streams = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-trace.h"
|
||||
|
||||
|
|
@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
|
|||
* pointer command pending because the device can choose to start any
|
||||
* stream once the endpoint is on the HW schedule.
|
||||
*/
|
||||
if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) ||
|
||||
(ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT))
|
||||
if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED |
|
||||
EP_CLEARING_TT | EP_DROP_PENDING))
|
||||
return;
|
||||
|
||||
trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id));
|
||||
|
|
@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
|
|||
}
|
||||
|
||||
/* Ring the doorbell for any rings with pending URBs */
|
||||
static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
|
||||
unsigned int slot_id,
|
||||
unsigned int ep_index)
|
||||
void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id,
|
||||
unsigned int ep_index)
|
||||
{
|
||||
unsigned int stream_id;
|
||||
struct xhci_virt_ep *ep;
|
||||
|
|
@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
|
|||
}
|
||||
}
|
||||
|
||||
void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci,
|
||||
unsigned int slot_id,
|
||||
unsigned int ep_index)
|
||||
{
|
||||
ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
}
|
||||
|
||||
static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci,
|
||||
unsigned int slot_id,
|
||||
unsigned int ep_index)
|
||||
|
|
@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev,
|
|||
return le64_to_cpu(ep_ctx->deq);
|
||||
}
|
||||
|
||||
static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
|
||||
unsigned int slot_id, unsigned int ep_index,
|
||||
unsigned int stream_id, struct xhci_td *td)
|
||||
/*
|
||||
* Move the endpoint dequeue pointer to the next queued TD on ring->td_list or
|
||||
* to enqueue if no TDs are queued (empty ring)
|
||||
* All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list
|
||||
* before calling this function
|
||||
*/
|
||||
static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci,
|
||||
struct xhci_virt_ep *ep,
|
||||
unsigned int stream_id)
|
||||
{
|
||||
struct xhci_virt_device *dev = xhci->devs[slot_id];
|
||||
struct xhci_virt_ep *ep = &dev->eps[ep_index];
|
||||
struct xhci_ring *ep_ring;
|
||||
struct xhci_command *cmd;
|
||||
struct xhci_segment *new_seg;
|
||||
union xhci_trb *new_deq;
|
||||
int new_cycle;
|
||||
struct xhci_ring *ring;
|
||||
struct xhci_td *td;
|
||||
dma_addr_t addr;
|
||||
u64 hw_dequeue;
|
||||
bool hw_dequeue_found = false;
|
||||
bool td_last_trb_found = false;
|
||||
int new_cycle;
|
||||
u32 trb_sct = 0;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id,
|
||||
ep_index, stream_id);
|
||||
if (!ep_ring) {
|
||||
ring = xhci_virt_ep_to_ring(xhci, ep, stream_id);
|
||||
if (!ring) {
|
||||
xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n",
|
||||
stream_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK;
|
||||
new_seg = ep_ring->deq_seg;
|
||||
new_deq = ep_ring->dequeue;
|
||||
new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE;
|
||||
|
||||
/*
|
||||
* Walk the ring until both the next TRB and hw_dequeue are found (don't
|
||||
* move hw_dequeue back if it went forward due to a HW bug). Cycle state
|
||||
* is loaded from a known good TRB, track later toggles to maintain it.
|
||||
*/
|
||||
do {
|
||||
if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq)
|
||||
== (dma_addr_t)hw_dequeue) {
|
||||
hw_dequeue_found = true;
|
||||
if (td_last_trb_found)
|
||||
break;
|
||||
}
|
||||
if (new_deq == td->end_trb)
|
||||
td_last_trb_found = true;
|
||||
|
||||
if (td_last_trb_found && trb_is_link(new_deq) &&
|
||||
link_trb_toggles_cycle(new_deq))
|
||||
new_cycle ^= 0x1;
|
||||
|
||||
next_trb(&new_seg, &new_deq);
|
||||
|
||||
/* Search wrapped around, bail out */
|
||||
if (new_deq == ep->ring->dequeue) {
|
||||
xhci_err(xhci, "Error: Failed finding new dequeue state\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
} while (!hw_dequeue_found || !td_last_trb_found);
|
||||
|
||||
/* Don't update the ring cycle state for the producer (us). */
|
||||
addr = xhci_trb_virt_to_dma(new_seg, new_deq);
|
||||
if (addr == 0) {
|
||||
xhci_warn(xhci, "Can't find dma of new dequeue ptr\n");
|
||||
xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((ep->ep_state & SET_DEQ_PENDING)) {
|
||||
xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n",
|
||||
&addr);
|
||||
xhci_warn(xhci, "Set TR Deq already pending\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* This function gets called from contexts where it cannot sleep */
|
||||
cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC);
|
||||
if (!cmd) {
|
||||
xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr);
|
||||
xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move dequeue to the beginning of next td, or to enqueue if ring is
|
||||
* empty. Avoid moving dequeue to a link trb (empty ring) as it causes
|
||||
* issues on some hosts. In that case advance the enqueue to next segment
|
||||
* before moving dequeue to it
|
||||
*/
|
||||
|
||||
if (list_empty(&ring->td_list)) {
|
||||
if (trb_is_link(ring->enqueue))
|
||||
inc_enq_past_link(xhci, ring, 0);
|
||||
ep->queued_deq_seg = ring->enq_seg;
|
||||
ep->queued_deq_ptr = ring->enqueue;
|
||||
new_cycle = ring->cycle_state;
|
||||
} else {
|
||||
td = list_first_entry(&ring->td_list, struct xhci_td, td_list);
|
||||
ep->queued_deq_seg = td->start_seg;
|
||||
ep->queued_deq_ptr = td->start_trb;
|
||||
new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE;
|
||||
}
|
||||
|
||||
addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr);
|
||||
if (addr == 0) {
|
||||
xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n",
|
||||
ep->queued_deq_seg, ep->queued_deq_ptr);
|
||||
ret = -EINVAL;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (stream_id)
|
||||
trb_sct = SCT_FOR_TRB(SCT_PRI_TR);
|
||||
ret = queue_command(xhci, cmd,
|
||||
lower_32_bits(addr) | trb_sct | new_cycle,
|
||||
upper_32_bits(addr),
|
||||
STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) |
|
||||
EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false);
|
||||
if (ret < 0) {
|
||||
xhci_free_command(xhci, cmd);
|
||||
return ret;
|
||||
}
|
||||
ep->queued_deq_seg = new_seg;
|
||||
ep->queued_deq_ptr = new_deq;
|
||||
STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) |
|
||||
EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false);
|
||||
if (ret < 0)
|
||||
goto err_out;
|
||||
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
|
||||
"Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle);
|
||||
|
||||
/* Stop the TD queueing code from ringing the doorbell until
|
||||
* this command completes. The HC won't set the dequeue pointer
|
||||
* if the ring is running, and ringing the doorbell starts the
|
||||
* ring running.
|
||||
/*
|
||||
* Stop the TD queueing code from ringing the doorbell until this
|
||||
* command completes. The HC won't set the dequeue pointer if the ring
|
||||
* is running, and ringing the doorbell starts the ring.
|
||||
*/
|
||||
ep->ep_state |= SET_DEQ_PENDING;
|
||||
xhci_ring_cmd_db(xhci);
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
xhci_free_command(xhci, cmd);
|
||||
ep->queued_deq_seg = NULL;
|
||||
ep->queued_deq_ptr = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* flip_cycle means flip the cycle bit of all but the first and last TRB.
|
||||
|
|
@ -934,7 +916,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh
|
|||
}
|
||||
|
||||
/* Complete the cancelled URBs we unlinked from td_list. */
|
||||
static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep)
|
||||
static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
|
||||
{
|
||||
struct xhci_ring *ring;
|
||||
struct xhci_td *td, *tmp_td;
|
||||
|
|
@ -942,17 +924,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep)
|
|||
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list,
|
||||
cancelled_td_list) {
|
||||
|
||||
ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb);
|
||||
ring = xhci_urb_to_transfer_ring(xhci, td->urb);
|
||||
|
||||
if (td->cancel_status == TD_CLEARED) {
|
||||
xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n",
|
||||
xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n",
|
||||
__func__, td->urb);
|
||||
xhci_td_cleanup(ep->xhci, td, ring, td->status);
|
||||
xhci_td_cleanup(xhci, td, ring, td->status);
|
||||
} else {
|
||||
xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
|
||||
xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
|
||||
__func__, td->urb, td->cancel_status);
|
||||
}
|
||||
if (ep->xhci->xhc_state & XHCI_STATE_DYING)
|
||||
if (xhci->xhc_state & XHCI_STATE_DYING)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -986,15 +968,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
|
|||
struct xhci_td *td,
|
||||
enum xhci_ep_reset_type reset_type)
|
||||
{
|
||||
struct xhci_port *rhub_port = ep->vdev->rhub_port;
|
||||
unsigned int slot_id = ep->vdev->slot_id;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Avoid resetting endpoint if link is inactive. Can cause host hang.
|
||||
* Device will be reset soon to recover the link so don't do anything
|
||||
* Avoid resetting endpoint if link is inactive or device disonnected.
|
||||
* Can cause host hang.
|
||||
* Device will be reset to recover an inactive link, so don't do anything
|
||||
*/
|
||||
if (ep->vdev->flags & VDEV_PORT_ERROR)
|
||||
if (rhub_port->link_inactive || !rhub_port->connected) {
|
||||
ep->ep_state |= EP_DROP_PENDING;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* add td to cancelled list and let reset ep handler take care of it */
|
||||
if (reset_type == EP_HARD_RESET) {
|
||||
|
|
@ -1031,15 +1017,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
|
|||
* only call this when ring is not in a running state
|
||||
*/
|
||||
|
||||
static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
|
||||
static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
|
||||
{
|
||||
struct xhci_hcd *xhci;
|
||||
struct xhci_td *td = NULL;
|
||||
struct xhci_td *tmp_td = NULL;
|
||||
struct xhci_td *cached_td = NULL;
|
||||
struct xhci_ring *ring;
|
||||
u64 hw_deq;
|
||||
unsigned int slot_id = ep->vdev->slot_id;
|
||||
int err;
|
||||
|
||||
/*
|
||||
|
|
@ -1049,8 +1033,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
|
|||
if (ep->ep_state & SET_DEQ_PENDING)
|
||||
return 0;
|
||||
|
||||
xhci = ep->xhci;
|
||||
|
||||
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) {
|
||||
xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
|
||||
"Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p",
|
||||
|
|
@ -1064,6 +1046,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
|
|||
td->urb, td->urb->stream_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* device disconnected or link error, ep will be dropped */
|
||||
if (ep->ep_state & EP_DROP_PENDING) {
|
||||
td->cancel_status = TD_CLEARED;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a ring stopped on the TD we need to cancel then we have to
|
||||
* move the xHC endpoint ring dequeue pointer past this TD.
|
||||
|
|
@ -1115,9 +1104,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
|
|||
if (!cached_td)
|
||||
return 0;
|
||||
|
||||
err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index,
|
||||
cached_td->urb->stream_id,
|
||||
cached_td);
|
||||
err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id);
|
||||
|
||||
if (err) {
|
||||
/* Failed to move past cached td, just set cached TDs to no-op */
|
||||
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) {
|
||||
|
|
@ -1145,23 +1133,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
|
|||
*
|
||||
* Call under xhci->lock on a stopped endpoint.
|
||||
*/
|
||||
void xhci_process_cancelled_tds(struct xhci_virt_ep *ep)
|
||||
void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
|
||||
{
|
||||
xhci_invalidate_cancelled_tds(ep);
|
||||
xhci_giveback_invalidated_tds(ep);
|
||||
xhci_invalidate_cancelled_tds(xhci, ep);
|
||||
xhci_giveback_invalidated_tds(xhci, ep);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the TD the endpoint ring halted on.
|
||||
* Only call for non-running rings without streams.
|
||||
*/
|
||||
static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep)
|
||||
static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep)
|
||||
{
|
||||
struct xhci_td *td;
|
||||
u64 hw_deq;
|
||||
|
||||
if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */
|
||||
hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0);
|
||||
hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0);
|
||||
hw_deq &= TR_DEQ_PTR_MASK;
|
||||
td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list);
|
||||
if (trb_in_td(td, hw_deq))
|
||||
|
|
@ -1236,7 +1224,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id,
|
|||
reset_type = EP_SOFT_RESET;
|
||||
} else {
|
||||
reset_type = EP_HARD_RESET;
|
||||
td = find_halted_td(ep);
|
||||
td = find_halted_td(xhci, ep);
|
||||
if (td)
|
||||
td->status = -EPROTO;
|
||||
}
|
||||
|
|
@ -1294,13 +1282,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id,
|
|||
}
|
||||
}
|
||||
|
||||
/* link is inactive or disconnected, ep is not running and shouldn't be restarted */
|
||||
if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected)
|
||||
ep->ep_state |= EP_DROP_PENDING;
|
||||
|
||||
/* will queue a set TR deq if stopped on a cancelled, uncleared TD */
|
||||
xhci_invalidate_cancelled_tds(ep);
|
||||
xhci_invalidate_cancelled_tds(xhci, ep);
|
||||
ep->ep_state &= ~EP_STOP_CMD_PENDING;
|
||||
|
||||
/* Otherwise ring the doorbell(s) to restart queued transfers */
|
||||
xhci_giveback_invalidated_tds(ep);
|
||||
ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
xhci_giveback_invalidated_tds(xhci, ep);
|
||||
xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
}
|
||||
|
||||
static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring)
|
||||
|
|
@ -1521,14 +1513,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
|
|||
/* HW cached TDs cleared from cache, give them back */
|
||||
list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list,
|
||||
cancelled_td_list) {
|
||||
ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb);
|
||||
ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb);
|
||||
if (td->cancel_status == TD_CLEARING_CACHE) {
|
||||
td->cancel_status = TD_CLEARED;
|
||||
xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n",
|
||||
xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n",
|
||||
__func__, td->urb);
|
||||
xhci_td_cleanup(ep->xhci, td, ep_ring, td->status);
|
||||
xhci_td_cleanup(xhci, td, ep_ring, td->status);
|
||||
} else {
|
||||
xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
|
||||
xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n",
|
||||
__func__, td->urb, td->cancel_status);
|
||||
}
|
||||
}
|
||||
|
|
@ -1539,17 +1531,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
|
|||
|
||||
/* Check for deferred or newly cancelled TDs */
|
||||
if (!list_empty(&ep->cancelled_td_list)) {
|
||||
xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n",
|
||||
xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n",
|
||||
__func__);
|
||||
xhci_invalidate_cancelled_tds(ep);
|
||||
xhci_invalidate_cancelled_tds(xhci, ep);
|
||||
/* Try to restart the endpoint if all is done */
|
||||
ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
/* Start giving back any TDs invalidated above */
|
||||
xhci_giveback_invalidated_tds(ep);
|
||||
xhci_giveback_invalidated_tds(xhci, ep);
|
||||
} else {
|
||||
/* Restart any rings with pending URBs */
|
||||
xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__);
|
||||
ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__);
|
||||
xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1575,16 +1567,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id,
|
|||
"Ignoring reset ep completion code of %u", cmd_comp_code);
|
||||
|
||||
/* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */
|
||||
xhci_invalidate_cancelled_tds(ep);
|
||||
xhci_invalidate_cancelled_tds(xhci, ep);
|
||||
|
||||
/* Clear our internal halted state */
|
||||
ep->ep_state &= ~EP_HALTED;
|
||||
|
||||
xhci_giveback_invalidated_tds(ep);
|
||||
xhci_giveback_invalidated_tds(xhci, ep);
|
||||
|
||||
/* if this was a soft reset, then restart */
|
||||
if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP)
|
||||
ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
|
||||
}
|
||||
|
||||
static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command,
|
||||
|
|
@ -1992,13 +1984,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci)
|
|||
static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
|
||||
{
|
||||
struct xhci_virt_device *vdev = NULL;
|
||||
struct usb_hcd *hcd;
|
||||
u32 port_id;
|
||||
u32 portsc, cmd_reg;
|
||||
unsigned int hcd_portnum;
|
||||
struct xhci_bus_state *bus_state;
|
||||
bool bogus_port_status = false;
|
||||
struct xhci_port *port;
|
||||
struct usb_hcd *hcd;
|
||||
bool bogus_port_status = false;
|
||||
unsigned int hcd_portnum;
|
||||
u32 cmd_reg;
|
||||
u32 port_id;
|
||||
u32 portsc;
|
||||
u32 pls;
|
||||
|
||||
/* Port status change events always have a successful completion code */
|
||||
if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS)
|
||||
|
|
@ -2025,7 +2019,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
|
|||
vdev = xhci->devs[port->slot_id];
|
||||
|
||||
/* We might get interrupts after shared_hcd is removed */
|
||||
if (port->rhub == &xhci->usb3_rhub && xhci->shared_hcd == NULL) {
|
||||
if (port->rhub == &xhci->usb3_rhub && xhci_get_usb3_hcd(xhci) == NULL) {
|
||||
xhci_dbg(xhci, "ignore port event for removed USB3 hcd\n");
|
||||
bogus_port_status = true;
|
||||
goto cleanup;
|
||||
|
|
@ -2035,6 +2029,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
|
|||
bus_state = &port->rhub->bus_state;
|
||||
hcd_portnum = port->hcd_portnum;
|
||||
portsc = xhci_portsc_readl(port);
|
||||
pls = portsc & PORT_PLS_MASK;
|
||||
|
||||
xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n",
|
||||
hcd->self.busnum, hcd_portnum + 1, port_id, portsc);
|
||||
|
|
@ -2046,11 +2041,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event)
|
|||
usb_hcd_resume_root_hub(hcd);
|
||||
}
|
||||
|
||||
if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) {
|
||||
if (!(portsc & PORT_RESET))
|
||||
vdev->flags |= VDEV_PORT_ERROR;
|
||||
} else if (vdev && portsc & PORT_RC) {
|
||||
vdev->flags &= ~VDEV_PORT_ERROR;
|
||||
/*
|
||||
* Tag broken links to avoid retries while hub driver sorts it out.
|
||||
* Link status is not relible while port is in reset.
|
||||
*/
|
||||
if (!(portsc & PORT_RESET)) {
|
||||
port->link_inactive = (pls == XDEV_INACTIVE);
|
||||
port->connected = !!(portsc & PORT_CONNECT);
|
||||
}
|
||||
|
||||
if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) {
|
||||
|
|
@ -2600,6 +2597,17 @@ static bool xhci_spurious_success_tx_event(struct xhci_hcd *xhci,
|
|||
}
|
||||
}
|
||||
|
||||
static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, dma_addr_t dma)
|
||||
{
|
||||
struct xhci_td *td;
|
||||
|
||||
if (dma)
|
||||
list_for_each_entry(td, &ep_ring->td_list, td_list)
|
||||
if (trb_in_td(td, dma))
|
||||
return td;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this function returns an error condition, it means it got a Transfer
|
||||
* event with a corrupted Slot ID, Endpoint ID, or TRB DMA address.
|
||||
|
|
@ -2794,8 +2802,11 @@ static int handle_tx_event(struct xhci_hcd *xhci,
|
|||
xhci_dequeue_td(xhci, td, ep_ring, td->status);
|
||||
}
|
||||
|
||||
/* If the TRB pointer is NULL, missed TDs will be skipped on the next event */
|
||||
if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !ep_trb_dma)
|
||||
/*
|
||||
* We don't know how many TDs were missed when ep_trb_dma is zero (as permitted by
|
||||
* xHCI 1.0) or bogus. Bail out leaving ep->skip set, next event will sort it out.
|
||||
*/
|
||||
if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !find_td_by_dma(ep_ring, ep_trb_dma))
|
||||
return 0;
|
||||
|
||||
if (list_empty(&ep_ring->td_list)) {
|
||||
|
|
@ -3948,88 +3959,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci,
|
|||
/* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */
|
||||
static int xhci_ist_microframes(struct xhci_hcd *xhci)
|
||||
{
|
||||
int ist = HCS_IST_VALUE(xhci->hcs_params2);
|
||||
int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2);
|
||||
|
||||
if (xhci->hcs_params2 & HCS_IST_UNIT)
|
||||
ist *= 8;
|
||||
return ist;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates Frame ID field of the isochronous TRB identifies the
|
||||
* target frame that the Interval associated with this Isochronous
|
||||
* Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec.
|
||||
*
|
||||
* Returns actual frame id on success, negative value on error.
|
||||
*/
|
||||
static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci,
|
||||
struct urb *urb, int index)
|
||||
|
||||
static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb,
|
||||
struct xhci_virt_ep *ep, int i)
|
||||
{
|
||||
int start_frame, ist, ret = 0;
|
||||
int start_frame_id, end_frame_id, current_frame_id;
|
||||
if (urb->transfer_flags & URB_ISO_ASAP)
|
||||
return false;
|
||||
|
||||
if (urb->dev->speed == USB_SPEED_LOW ||
|
||||
urb->dev->speed == USB_SPEED_FULL)
|
||||
start_frame = urb->start_frame + index * urb->interval;
|
||||
else
|
||||
start_frame = (urb->start_frame + index * urb->interval) >> 3;
|
||||
if (xhci->hcc_params & HCC_CFC)
|
||||
return true;
|
||||
|
||||
/* set frame id for first TD of first URB in stream */
|
||||
if (ep->next_uframe == -1 && i == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if frame is in the valid frame window, including start and end.
|
||||
* If start > end then assume window wrapped around at a limit the frame
|
||||
* value won't exceed.
|
||||
*/
|
||||
static bool xhci_frame_in_range(u32 frame, u32 start, u32 end)
|
||||
{
|
||||
/* frame window end wrapped around */
|
||||
if (start > end)
|
||||
return frame >= start || frame <= end;
|
||||
|
||||
return frame >= start && frame <= end;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the urb->start_frame of the URB.
|
||||
*
|
||||
* Returns microframe index of first TD
|
||||
*/
|
||||
static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb,
|
||||
struct xhci_virt_ep *ep)
|
||||
{
|
||||
u32 curr_frame, start_uframe;
|
||||
u32 urb_start, urb_end;
|
||||
u32 win_start, win_end;
|
||||
bool frame_unit;
|
||||
int uinterval;
|
||||
u32 mfindex;
|
||||
int ist;
|
||||
|
||||
/* check if urb uses frame units instead of microframes */
|
||||
frame_unit = (urb->dev->speed == USB_SPEED_FULL ||
|
||||
urb->dev->speed == USB_SPEED_LOW);
|
||||
|
||||
uinterval = urb->interval;
|
||||
if (frame_unit)
|
||||
uinterval *= 8;
|
||||
|
||||
/* get current microframe index and isoc scheduling threshold */
|
||||
mfindex = readl(&xhci->run_regs->microframe_index);
|
||||
ist = xhci_ist_microframes(xhci);
|
||||
|
||||
/* Software shall not schedule an Isoch TD with a Frame ID value that
|
||||
* is less than the Start Frame ID or greater than the End Frame ID,
|
||||
* where:
|
||||
*
|
||||
* End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048
|
||||
* Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048
|
||||
*
|
||||
* Both the End Frame ID and Start Frame ID values are calculated
|
||||
* in microframes. When software determines the valid Frame ID value;
|
||||
* The End Frame ID value should be rounded down to the nearest Frame
|
||||
* boundary, and the Start Frame ID value should be rounded up to the
|
||||
* nearest Frame boundary.
|
||||
*/
|
||||
current_frame_id = readl(&xhci->run_regs->microframe_index);
|
||||
start_frame_id = roundup(current_frame_id + ist + 1, 8);
|
||||
end_frame_id = rounddown(current_frame_id + 895 * 8, 8);
|
||||
/* calculate valid frame window, in frame units, see xhci 4.11.2.5 */
|
||||
curr_frame = MFINDEX_TO_FRAME(mfindex);
|
||||
win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES;
|
||||
win_end = (curr_frame + 895) % MAX_FRAMES;
|
||||
|
||||
start_frame &= 0x7ff;
|
||||
start_frame_id = (start_frame_id >> 3) & 0x7ff;
|
||||
end_frame_id = (end_frame_id >> 3) & 0x7ff;
|
||||
|
||||
if (start_frame_id < end_frame_id) {
|
||||
if (start_frame > end_frame_id ||
|
||||
start_frame < start_frame_id)
|
||||
ret = -EINVAL;
|
||||
} else if (start_frame_id > end_frame_id) {
|
||||
if ((start_frame > end_frame_id &&
|
||||
start_frame < start_frame_id))
|
||||
ret = -EINVAL;
|
||||
/* Is this the first URB starting the whole isoc data flow? */
|
||||
if (ep->next_uframe < 0) {
|
||||
/* align first URB to next interval boundary, or at last to full frame */
|
||||
start_uframe = mfindex + ist + XHCI_CFC_DELAY;
|
||||
start_uframe = roundup(start_uframe, 8);
|
||||
start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
/* URB is mid stream and expected to handle the next frame */
|
||||
start_uframe = ep->next_uframe;
|
||||
urb_start = start_uframe / 8;
|
||||
urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8;
|
||||
urb_end %= MAX_FRAMES;
|
||||
|
||||
if (index == 0) {
|
||||
if (ret == -EINVAL || start_frame == start_frame_id) {
|
||||
start_frame = start_frame_id + 1;
|
||||
if (urb->dev->speed == USB_SPEED_LOW ||
|
||||
urb->dev->speed == USB_SPEED_FULL)
|
||||
urb->start_frame = start_frame;
|
||||
else
|
||||
urb->start_frame = start_frame << 3;
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
if (!xhci_frame_in_range(urb_start, win_start, win_end))
|
||||
xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n",
|
||||
urb, urb_start, win_start, win_end);
|
||||
|
||||
if (ret) {
|
||||
xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n",
|
||||
start_frame, current_frame_id, index,
|
||||
start_frame_id, end_frame_id);
|
||||
xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n");
|
||||
return ret;
|
||||
if (!xhci_frame_in_range(urb_end, win_start, win_end))
|
||||
xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n",
|
||||
urb, urb_start, win_start, win_end);
|
||||
}
|
||||
/* set urb->start_frame */
|
||||
urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe;
|
||||
|
||||
return start_frame;
|
||||
return start_uframe;
|
||||
}
|
||||
|
||||
/* Check if we should generate event interrupt for a TD in an isoc URB */
|
||||
|
|
@ -4069,7 +4094,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
|
|||
int i, j;
|
||||
bool more_trbs_coming;
|
||||
struct xhci_virt_ep *xep;
|
||||
int frame_id;
|
||||
int uinterval = urb->interval;
|
||||
int start_uframe;
|
||||
|
||||
xep = &xhci->devs[slot_id]->eps[ep_index];
|
||||
ep_ring = xhci->devs[slot_id]->eps[ep_index].ring;
|
||||
|
|
@ -4085,6 +4111,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
|
|||
start_cycle = ep_ring->cycle_state;
|
||||
|
||||
urb_priv = urb->hcpriv;
|
||||
|
||||
if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW)
|
||||
uinterval = urb->interval * 8;
|
||||
|
||||
start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep);
|
||||
|
||||
/* Queue the TRBs for each TD, even if they are zero-length */
|
||||
for (i = 0; i < num_tds; i++) {
|
||||
unsigned int total_pkt_count, max_pkt;
|
||||
|
|
@ -4116,14 +4148,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
|
|||
goto cleanup;
|
||||
}
|
||||
td = &urb_priv->td[i];
|
||||
/* use SIA as default, if frame id is used overwrite it */
|
||||
sia_frame_id = TRB_SIA;
|
||||
if (!(urb->transfer_flags & URB_ISO_ASAP) &&
|
||||
(xhci->hcc_params & HCC_CFC)) {
|
||||
frame_id = xhci_get_isoc_frame_id(xhci, urb, i);
|
||||
if (frame_id >= 0)
|
||||
sia_frame_id = TRB_FRAME_ID(frame_id);
|
||||
|
||||
|
||||
/* Choose SIA or frame ID based scheduling for this TD */
|
||||
if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) {
|
||||
sia_frame_id = (start_uframe + i * uinterval) / 8;
|
||||
sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES);
|
||||
} else {
|
||||
sia_frame_id = TRB_SIA;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set isoc specific data for the first TRB in a TD.
|
||||
* Prevent HW from getting the TRBs by keeping the cycle state
|
||||
|
|
@ -4202,9 +4236,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
|
|||
}
|
||||
}
|
||||
|
||||
/* store the next frame id */
|
||||
if (xhci->hcc_params & HCC_CFC)
|
||||
xep->next_frame_id = urb->start_frame + num_tds * urb->interval;
|
||||
xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES;
|
||||
|
||||
if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) {
|
||||
if (xhci->quirks & XHCI_AMD_PLL_FIX)
|
||||
|
|
@ -4251,11 +4283,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
|
|||
struct xhci_virt_device *xdev;
|
||||
struct xhci_ring *ep_ring;
|
||||
struct xhci_ep_ctx *ep_ctx;
|
||||
int start_frame;
|
||||
struct xhci_virt_ep *xep;
|
||||
int num_tds, num_trbs, i;
|
||||
int ret;
|
||||
struct xhci_virt_ep *xep;
|
||||
int ist;
|
||||
|
||||
xdev = xhci->devs[slot_id];
|
||||
xep = &xhci->devs[slot_id]->eps[ep_index];
|
||||
|
|
@ -4281,38 +4311,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
|
|||
*/
|
||||
check_interval(urb, ep_ctx);
|
||||
|
||||
/* Calculate the start frame and put it in urb->start_frame. */
|
||||
if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) {
|
||||
if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) {
|
||||
urb->start_frame = xep->next_frame_id;
|
||||
goto skip_start_over;
|
||||
}
|
||||
}
|
||||
|
||||
start_frame = readl(&xhci->run_regs->microframe_index);
|
||||
start_frame &= 0x3fff;
|
||||
/*
|
||||
* Round up to the next frame and consider the time before trb really
|
||||
* gets scheduled by hardare.
|
||||
* Check if this starts the isoc data flow. Relies on hw setting ep ctx
|
||||
* state after doorbell ring. Consider adding list_empty(td_list) check
|
||||
*/
|
||||
ist = xhci_ist_microframes(xhci);
|
||||
start_frame += ist + XHCI_CFC_DELAY;
|
||||
start_frame = roundup(start_frame, 8);
|
||||
|
||||
/*
|
||||
* Round up to the next ESIT (Endpoint Service Interval Time) if ESIT
|
||||
* is greate than 8 microframes.
|
||||
*/
|
||||
if (urb->dev->speed == USB_SPEED_LOW ||
|
||||
urb->dev->speed == USB_SPEED_FULL) {
|
||||
start_frame = roundup(start_frame, urb->interval << 3);
|
||||
urb->start_frame = start_frame >> 3;
|
||||
} else {
|
||||
start_frame = roundup(start_frame, urb->interval);
|
||||
urb->start_frame = start_frame;
|
||||
}
|
||||
|
||||
skip_start_over:
|
||||
if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING)
|
||||
xep->next_uframe = -1;
|
||||
|
||||
return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* Copyright (C) 2014 Google, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
|
@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra)
|
|||
u32 value;
|
||||
|
||||
cap_regs = tegra->regs;
|
||||
op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase));
|
||||
op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)),
|
||||
|
||||
ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000);
|
||||
|
||||
|
|
@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
|||
goto remove_usb2;
|
||||
}
|
||||
|
||||
if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
|
||||
if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4)
|
||||
xhci->shared_hcd->can_do_streams = 1;
|
||||
|
||||
err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <linux/dmi.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb/xhci-sideband.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "xhci.h"
|
||||
#include "xhci-trace.h"
|
||||
|
|
@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
|
|||
goto free_priv;
|
||||
}
|
||||
|
||||
if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
|
||||
if (xhci->devs[slot_id]->rhub_port->link_inactive) {
|
||||
xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
|
||||
ret = -ENODEV;
|
||||
goto free_priv;
|
||||
|
|
@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
|
|||
}
|
||||
|
||||
/* In this case no commands are pending but the endpoint is stopped */
|
||||
if (ep->ep_state & EP_CLEARING_TT) {
|
||||
if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) {
|
||||
/* and cancelled TDs can be given back right away */
|
||||
xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n",
|
||||
urb->dev->slot_id, ep_index, ep->ep_state);
|
||||
xhci_process_cancelled_tds(ep);
|
||||
xhci_process_cancelled_tds(xhci, ep);
|
||||
} else {
|
||||
/* Otherwise, queue a new Stop Endpoint command */
|
||||
command = xhci_alloc_command(xhci, false, GFP_ATOMIC);
|
||||
|
|
@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci,
|
|||
* level page entries), but that's an optional feature for xHCI host
|
||||
* controllers. xHCs must support at least 4 stream IDs.
|
||||
*/
|
||||
max_streams = HCC_MAX_PSA(xhci->hcc_params);
|
||||
max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params);
|
||||
if (*num_stream_ctxs > max_streams) {
|
||||
xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n",
|
||||
max_streams);
|
||||
|
|
@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev,
|
|||
|
||||
/* MaxPSASize value 0 (2 streams) means streams are not supported */
|
||||
if ((xhci->quirks & XHCI_BROKEN_STREAMS) ||
|
||||
HCC_MAX_PSA(xhci->hcc_params) < 4) {
|
||||
GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) {
|
||||
xhci_dbg(xhci, "xHCI controller does not support streams.\n");
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
|
@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd,
|
|||
xhci_get_slot_state(xhci, virt_dev->out_ctx));
|
||||
xhci_dbg(xhci, "Not freeing device rings.\n");
|
||||
/* Don't treat this as an error. May change my mind later. */
|
||||
virt_dev->flags = 0;
|
||||
ret = 0;
|
||||
goto command_cleanup;
|
||||
case COMP_SUCCESS:
|
||||
|
|
@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd,
|
|||
}
|
||||
/* If necessary, update the number of active TTs on this root port */
|
||||
xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps);
|
||||
virt_dev->flags = 0;
|
||||
ret = 0;
|
||||
|
||||
command_cleanup:
|
||||
for (i = 0; i < EP_CTX_PER_DEV; i++)
|
||||
virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING;
|
||||
|
||||
xhci_free_command(xhci, reset_device_cmd);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci,
|
|||
int besl_device = 0;
|
||||
u32 field;
|
||||
|
||||
u2del = HCS_U2_LATENCY(xhci->hcs_params3);
|
||||
u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3);
|
||||
field = le32_to_cpu(udev->bos->ext_cap->bmAttributes);
|
||||
|
||||
if (field & USB_BESL_SUPPORT) {
|
||||
|
|
@ -5433,6 +5435,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
|
|||
struct device *dev = hcd->self.sysdev;
|
||||
int retval;
|
||||
u32 hcs_params1;
|
||||
u32 hc_capbase;
|
||||
|
||||
/* Accept arbitrarily long scatter-gather lists */
|
||||
hcd->self.sg_tablesize = ~0;
|
||||
|
|
@ -5453,27 +5456,33 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
|
|||
mutex_init(&xhci->mutex);
|
||||
xhci->main_hcd = hcd;
|
||||
xhci->cap_regs = hcd->regs;
|
||||
xhci->op_regs = hcd->regs +
|
||||
HC_LENGTH(readl(&xhci->cap_regs->hc_capbase));
|
||||
hc_capbase = readl(&xhci->cap_regs->hc_capbase);
|
||||
if (hc_capbase == U32_MAX) {
|
||||
xhci_warn(xhci, "Host controller not accessible, removed?\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase);
|
||||
|
||||
xhci->run_regs = hcd->regs +
|
||||
(readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK);
|
||||
/* Cache read-only capability registers */
|
||||
hcs_params1 = readl(&xhci->cap_regs->hcs_params1);
|
||||
xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2);
|
||||
xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3);
|
||||
xhci->hci_version = HC_VERSION(readl(&xhci->cap_regs->hc_capbase));
|
||||
xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase);
|
||||
xhci->hcc_params = readl(&xhci->cap_regs->hcc_params);
|
||||
if (xhci->hci_version > 0x100)
|
||||
xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2);
|
||||
|
||||
xhci->dma_mask_bits = 64;
|
||||
xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS);
|
||||
xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS);
|
||||
xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS);
|
||||
xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS);
|
||||
|
||||
/* xhci-plat or xhci-pci might have set max_interrupters already */
|
||||
if (!xhci->max_interrupters)
|
||||
xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS);
|
||||
else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1))
|
||||
xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1);
|
||||
xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS);
|
||||
else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1))
|
||||
xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1);
|
||||
|
||||
xhci->quirks |= quirks;
|
||||
|
||||
|
|
@ -5514,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
|
|||
* DMA_BIT_MASK(32)) in this xhci_gen_setup().
|
||||
*/
|
||||
if (xhci->quirks & XHCI_NO_64BIT_SUPPORT)
|
||||
xhci->hcc_params &= ~BIT(0);
|
||||
xhci->hcc_params &= ~HCC_64BIT_ADDR;
|
||||
|
||||
/*
|
||||
* Set dma_mask and coherent_dma_mask to 64-bits if xHC supports
|
||||
|
|
|
|||
|
|
@ -290,6 +290,11 @@ struct xhci_run_regs {
|
|||
struct xhci_intr_reg ir_set[1024];
|
||||
};
|
||||
|
||||
/* Bits [13:3] of the microframe index equals the 1ms frame index */
|
||||
#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff)
|
||||
#define MAX_FRAMES 2048
|
||||
#define MAX_UFRAMES (MAX_FRAMES * 8)
|
||||
|
||||
/**
|
||||
* struct doorbell_array
|
||||
*
|
||||
|
|
@ -677,9 +682,9 @@ struct xhci_virt_ep {
|
|||
#define EP_SOFT_CLEAR_TOGGLE BIT(7)
|
||||
/* usb_hub_clear_tt_buffer is in progress */
|
||||
#define EP_CLEARING_TT BIT(8)
|
||||
#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */
|
||||
/* ---- Related to URB cancellation ---- */
|
||||
struct list_head cancelled_td_list;
|
||||
struct xhci_hcd *xhci;
|
||||
/* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue
|
||||
* command. We'll need to update the ring's dequeue segment and dequeue
|
||||
* pointer after the command completes.
|
||||
|
|
@ -699,7 +704,7 @@ struct xhci_virt_ep {
|
|||
struct list_head bw_endpoint_list;
|
||||
unsigned long stop_time;
|
||||
/* Isoch Frame ID checking storage */
|
||||
int next_frame_id;
|
||||
int next_uframe;
|
||||
/* Use new Isoch TRB layout needed for extended TBC support */
|
||||
bool use_extended_tbc;
|
||||
/* set if this endpoint is controlled via sideband access*/
|
||||
|
|
@ -753,14 +758,6 @@ struct xhci_virt_device {
|
|||
struct xhci_port *rhub_port;
|
||||
struct xhci_interval_bw_table *bw_table;
|
||||
struct xhci_tt_bw_info *tt_info;
|
||||
/*
|
||||
* flags for state tracking based on events and issued commands.
|
||||
* Software can not rely on states from output contexts because of
|
||||
* latency between events and xHC updating output context values.
|
||||
* See xhci 1.1 section 4.8.3 for more details
|
||||
*/
|
||||
unsigned long flags;
|
||||
#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */
|
||||
|
||||
/* The current max exit latency for the enabled USB3 link states. */
|
||||
u16 current_mel;
|
||||
|
|
@ -1480,6 +1477,8 @@ struct xhci_port {
|
|||
int hcd_portnum;
|
||||
struct xhci_hub *rhub;
|
||||
struct xhci_port_cap *port_cap;
|
||||
unsigned int link_inactive:1;
|
||||
unsigned int connected:1;
|
||||
unsigned int lpm_incapable:1;
|
||||
unsigned long resume_timestamp;
|
||||
bool rexit_active;
|
||||
|
|
@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len);
|
|||
unsigned int xhci_num_trbs_free(struct xhci_ring *ring);
|
||||
int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
|
||||
int suspend, gfp_t gfp_flags);
|
||||
void xhci_process_cancelled_tds(struct xhci_virt_ep *ep);
|
||||
void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep);
|
||||
void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
|
||||
struct xhci_interrupter *ir,
|
||||
bool clear_ehb);
|
||||
|
|
|
|||
|
|
@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request,
|
|||
address, data, iobuf, CYPRESS_MAX_REQSIZE,
|
||||
USB_CTRL_GET_TIMEOUT);
|
||||
/* we must not process garbage */
|
||||
if (retval < 2)
|
||||
if (retval < 2) {
|
||||
if (retval >= 0)
|
||||
retval = -EIO;
|
||||
goto err_buf;
|
||||
}
|
||||
|
||||
/* store returned data (more READs to be added) */
|
||||
switch (request) {
|
||||
|
|
@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr,
|
|||
dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num);
|
||||
|
||||
result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0);
|
||||
|
||||
dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
return sprintf(buf, "%d", cyp->port[port_num]);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user