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:
Linus Torvalds 2026-08-25 10:44:46 -07:00
commit f4d50813c0
140 changed files with 3143 additions and 1780 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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";
};
};

View File

@ -50,6 +50,9 @@ properties:
- const: otg
- const: wakeup
iommus:
maxItems: 1
port:
$ref: /schemas/graph.yaml#/properties/port
description:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 集线器其实一点也不“标准”;也正因
如此,我们才能在标准集线器驱动中加入厂商特定的处理。

View File

@ -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``,说明驱动工作正常。

View File

@ -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`` 来手动触发驱动探测。
对于需要多个接口的驱动程序,应先授权所有必需接口,
然后再触发驱动探测。这样做可以避免副作用。
对于需要多个接口的驱动程序,应先授权所有必需接口,然后再触发驱动探测。
这样做可以避免副作用。

View File

@ -17,18 +17,17 @@ ChipIdea 高速双角色控制器驱动
1. 如何测试 OTG FSMHNP 和 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

View File

@ -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()`` 处理。返回后再确认
事件计数器,让中断最终消失。
- 设备级线程化处理程序
无。
- 端点中断的主处理程序
读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。
事件保存在每个端点的数据结构中。
还要注意,一旦把某项工作交给线程处理,
就不要再在主处理程序里处理它,
以免出现优先级反转之类的问题。
读取事件并尽量处理;凡是需要睡眠的操作都交给线程处理。事件保存在
每个端点的数据结构中。一旦某项工作已经交给线程处理,主处理程序里就
不要再碰它,以免出现优先级反转之类的问题。
- 端点中断的线程化处理程序
处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。
延迟:
延迟
不应增加延迟,因为中断线程具有较高优先级,
会在普通用户态任务之前运行
不应增加额外延迟,因为中断线程优先级较高,会在普通用户任务之前运行
(除非用户更改了调度优先级)。

View File

@ -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 Speed480 Mbit/sec60 MByte/sec
- “全速”Full Speed12 Mbit/sec1.5 MByte/sec
- “低速”Low Speed1.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 TranslatorTT
与 EHCI 控制器交互,该转换器将低速或全速事务转换为
高速分割事务,从而避免浪费传输带宽。
当 USB 1.1 设备插入 USB 2.0 集线器时,它们会通过集线器里的事务转换器
Transaction TranslatorTT与 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% 的周期性带宽。
后续将对此进行说明。

View File

@ -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

View File

@ -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 自身有 bugusbmon 输出的跟踪信息就未必能和真实的总线事务一一对应。
这和 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。
虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端,
因此上面的伪代码无需任何合并操作
虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端,因此上面的伪代
码无需做任何拼接

View File

@ -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.

View File

@ -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
=============== ====================================

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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 */
};

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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) {
/*

View File

@ -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

View File

@ -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)

View File

@ -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,
};

View File

@ -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);

View File

@ -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;

View File

@ -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 */

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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:

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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)) {

View File

@ -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,
};

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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",

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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; \
} \

View File

@ -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);
}

View File

@ -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; \
} \

View File

@ -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);
}

View File

@ -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);

View File

@ -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() */

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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.

View File

@ -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;
}

View File

@ -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");

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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");

View File

@ -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);

View File

@ -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) ||

View File

@ -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));

View File

@ -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;

View File

@ -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 */

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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