Estou escrevendo um pequeno módulo Python que controlará o HUB USB que usa chips Cypress CY7C65631 (EZ-USB HX2LP). A placa em que ele está tem um IC externo usado para controle VBUS (com todos os sinais de habilitação e sobrecorrente conectados ao IC HUB) e um GPIO Expander que me permite monitorar os valores lógicos definidos nas ditas linhas.
Ele roda no Ubuntu 20.04.6 LTS com kernel 5.8 (fornecido pelo fabricante da placa com x86). O hub é controlado com transferências ctrl do PyUSB. A comunicação com o hub funciona corretamente e o hub é reconhecido corretamente pelo sistema:
:~$ 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
Para fins de teste, conectei um pendrive (com alguns dados) a uma das portas. O que estou tentando fazer agora é:
- habilite tudo e leia os dados
- desligue apenas aquela porta e veja se o sistema operacional não vê o dispositivo pendrive
- reative a porta, veja o dispositivo reconhecer automaticamente e reler os dados.
Quando tento fazer o teste, posso observar que a porta está devidamente desligada, mas depois de algum tempo (varia, mas geralmente parece ser entre 0,5 s e 2 s) a porta é ligada. Suspeito que tenha algo a ver com os drivers USB ou o módulo do kernel responsável pelo gerenciamento de energia dos dispositivos USB, mas não tenho ideia do que exatamente é e como desabilitá-lo (ou alterar a maneira como controlo as portas).
Eu me livrei do ponto 3) do teste, para primeiro verificar se eu sou capaz de desligar a porta com sucesso. Aqui estão os logs e parte do código que eu uso para executar o teste.
Descritor do 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
Parte dmesg
relacionada ao teste (o momento de desligar a porta):
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
Partes do código do teste:
# 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
Saída:
# 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
(...)