我正在编写一个小型 Python 模块,用于控制使用Cypress CY7C65631 (EZ-USB HX2LP) 芯片的 USB HUB。它所在的电路板有用于 VBUS 控制的外部 IC(所有启用和过流信号都连接到 HUB IC)和 GPIO Expander,让我可以监控上述线路上设置的逻辑值。
它运行在 Ubuntu 20.04.6 LTS 上,内核为 5.8(由 x86 主板制造商提供)。集线器由 PyUSB 的 ctrl 传输控制。与集线器的通信正常,系统正确识别集线器:
:~$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 04b4:6560 Cypress Semiconductor Corp. CY7C65640 USB-2.0 "TetraHub"
Bus 001 Device 017: ID 0781:55b1 SanDisk Corp. Dell KB216 Wired Keyboard
Bus 001 Device 003: ID 04b4:6560 Cypress Semiconductor Corp. CY7C65640 USB-2.0 "TetraHub"
Bus 001 Device 002: ID 413c:2113 Dell Computer Corp. Dell KB216 Wired Keyboard
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
为了测试目的,我将一个 U 盘(包含一些数据)连接到其中一个端口。我现在想要实现的是:
- 启用一切并读取数据
- 只关闭单个端口,然后观察操作系统是否没有看到 U 盘设备
- 重新启用端口,查看设备自动识别并重新读取数据。
当我尝试进行上述测试时,我可以观察到端口已正确关闭,但一段时间后(时间会有所不同,但一般在 0.5 秒到 2 秒之间)端口又打开了。我怀疑这与 USB 驱动程序或负责 USB 设备电源管理的内核模块有关,但我不知道它到底是什么以及如何禁用它(或更改我控制端口的方式)。
我从测试中删除了第 3 点,首先验证我是否能够成功关闭端口。以下是我用来运行测试的日志和部分代码。
Hub 的描述符:
:~$ lsusb -vvv -d 04b4:6560
Bus 001 Device 003: ID 04b4:6560 Cypress Semiconductor Corp. CY7C65640 USB-2.0 "TetraHub"
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 9 Hub
bDeviceSubClass 0
bDeviceProtocol 1 Single TT
bMaxPacketSize0 64
idVendor 0x04b4 Cypress Semiconductor Corp.
idProduct 0x6560 CY7C65640 USB-2.0 "TetraHub"
bcdDevice 9.15
iManufacturer 0
iProduct 2 EXTERNAL USB HUB
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0019
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 174mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0001 1x 1 bytes
bInterval 12
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 4
wHubCharacteristic 0x0089
Per-port power switching
Per-port overcurrent protection
TT think time 8 FS bits
Port indicators
bPwrOn2PwrGood 50 * 2 milli seconds
bHubContrCurrent 100 milli Ampere
DeviceRemovable 0x00
PortPwrCtrlMask 0xff
Hub Port Status:
Port 1: 0000.0100 power
Port 2: 0000.0503 highspeed power enable connect
Port 3: 0000.0100 power
Port 4: 0000.0100 power
Device Qualifier (for other device speed):
bLength 10
bDescriptorType 6
bcdUSB 2.00
bDeviceClass 9 Hub
bDeviceSubClass 0
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 64
bNumConfigurations 1
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0001
Self Powered
测试相关的部分dmesg
(端口断电瞬间):
usb 1-3.2: USB disconnect, device number 16
[ +0.763930] usb 1-3.2: new high-speed USB device number 17 using xhci_hcd
[ +0.102990] usb 1-3.2: New USB device found, idVendor=0781, idProduct=55b1, bcdDevice= 1.10
[ +0.000009] usb 1-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ +0.000005] usb 1-3.2: Product: SanDisk 3.2 Gen1
[ +0.000004] usb 1-3.2: Manufacturer: SanDisk
[ +0.000004] usb 1-3.2: SerialNumber: A20019FE2628A724
[ +0.001528] usb-storage 1-3.2:1.0: USB Mass Storage device detected
[ +0.000650] scsi host2: usb-storage 1-3.2:1.0
[ +1.012410] scsi 2:0:0:0: Direct-Access SanDisk SanDisk 3.2 Gen1 DL17 PQ: 0 ANSI: 6
[ +0.000876] sd 2:0:0:0: Attached scsi generic sg0 type 0
[ +0.000414] sd 2:0:0:0: [sda] 126124032 512-byte logical blocks: (64.6 GB/60.1 GiB)
[ +0.000524] sd 2:0:0:0: [sda] Write Protect is off
[ +0.000006] sd 2:0:0:0: [sda] Mode Sense: 45 00 00 00
[ +0.000618] sd 2:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
[ +0.082726] sda: sda1 sda2
[ +0.002845] sd 2:0:0:0: [sda] Attached SCSI removable disk
测试代码的部分内容:
# b is the instance of the object abstracing the connected board
port_num = 2
print(b.external_usb_hub.get_port_status(port_num))
b.external_usb_hub.set_port_power(port_num, is_up=False)
print(b.external_usb_hub.get_port_status(port_num))
t = 0.
dt = 0.1
for _ in range(25):
# read_hub_port_sig_n_ena reads the state of the input bit corresponding
# to the ENA_n signal connecting the HUB Controller and the IC managing
# the power on downstream ports. Signal ACTIVE means VBUS output is present.
print(f"T = {round(t, 2)}: ", b.read_hub_port_sig_n_ena("external",port_num))
sleep(dt)
t += dt
class HUB:
# get_port_status(...) is a beautified version of hub's method:
def get_port_status_raw(self, port_num):
return self.ctrl_transfer(
bmRequestType=0xA3,
bRequest=BRequest.GET_STATUS,
wValue=0x0,
wIndex=port_num,
wLength=0x4,
)
def ctrl_transfer(
self,
bmRequestType,
bRequest,
wValue,
wIndex,
wLength=None,
data=None,
data_or_wLength=None,
):
# Args verification omitted for clarity
# self.dev is the instance returned by the usb.core.find(...)
resp = self.dev.ctrl_transfer(
bmRequestType=bmRequestType,
bRequest=bRequest,
wValue=wValue,
wIndex=wIndex,
data_or_wLength=data_or_wLength,
)
# Prevent timeouts with stacked xfers -- quick 'n dirty fix
sleep(0.2)
return resp
输出:
# Before disabling the port:
{'PortStatus.PORT_CONNECTION': 1, 'PortStatus.PORT_ENABLE': 0, 'PortStatus.PORT_SUSPEND': 1, 'PortStatus.PORT_OVER_CURRENT': 0, 'PortStatus.PORT_RESET': 0, 'PortStatus.PORT_POWER': 1, 'PortStatus.PORT_LOW_SPEED': 1, 'PortStatus.PORT_HIGH_SPEED': 0, 'PortStatus.PORT_TEST': 0, 'PortStatus.PORT_INDICATOR': 0, 'PortChange.C_PORT_CONNECTION': 0, 'PortChange.C_PORT_ENABLE': 0, 'PortChange.C_PORT_SUSPEND': 0, 'PortChange.C_PORT_OVER_CURRENT': 0, 'PortChange.C_PORT_RESET': 0}
# Right after disabling the port:
{'PortStatus.PORT_CONNECTION': 0, 'PortStatus.PORT_ENABLE': 0, 'PortStatus.PORT_SUSPEND': 0, 'PortStatus.PORT_OVER_CURRENT': 0, 'PortStatus.PORT_RESET': 0, 'PortStatus.PORT_POWER': 0, 'PortStatus.PORT_LOW_SPEED': 0, 'PortStatus.PORT_HIGH_SPEED': 0, 'PortStatus.PORT_TEST': 0, 'PortStatus.PORT_INDICATOR': 0, 'PortChange.C_PORT_CONNECTION': 0, 'PortChange.C_PORT_ENABLE': 0, 'PortChange.C_PORT_SUSPEND': 0, 'PortChange.C_PORT_OVER_CURRENT': 0, 'PortChange.C_PORT_RESET': 0}
T = 0.0: GPIOExpState.INACTIVE
(...)
T = 1.2: GPIOExpState.INACTIVE
# Here something toggles behind my back the power back on, port becomes enabled and device discovered
T = 1.3: GPIOExpState.ACTIVE
(...)
要在 Python 下执行此操作,无需提升权限: