sdwire 0.3.1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sdwire-0.3.1 → sdwire-0.4.0}/PKG-INFO +17 -1
- {sdwire-0.3.1 → sdwire-0.4.0}/README.md +16 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/pyproject.toml +1 -1
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/backend/detect.py +7 -1
- sdwire-0.4.0/sdwire/backend/device/power.py +86 -0
- sdwire-0.4.0/sdwire/backend/device/sdwire.py +139 -0
- sdwire-0.3.1/sdwire/backend/device/sdwire.py +0 -99
- {sdwire-0.3.1 → sdwire-0.4.0}/LICENSE +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/__init__.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/backend/__init__.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/backend/block_device_utils.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/backend/device/sdwirec.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/backend/device/usb_device.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/backend/utils.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/constants.py +0 -0
- {sdwire-0.3.1 → sdwire-0.4.0}/sdwire/main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sdwire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: CLI application to interact with Badgerd SDWire devices
|
|
5
5
|
License: GPL-3
|
|
6
6
|
Author: Talha Can Havadar
|
|
@@ -111,3 +111,19 @@ bdgrd_sdwirec_522 [sd-wire::SRPOL]
|
|
|
111
111
|
❯ sdwire switch host
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### SDWire3 and Linux USB power management
|
|
115
|
+
|
|
116
|
+
On Linux, the SDWire3 only hands the SD card to the target while the USB device
|
|
117
|
+
is runtime-suspended. `sdwire switch target` enables this automatically
|
|
118
|
+
(`power/autosuspend_delay_ms=0`, `power/control=auto`) and verifies that
|
|
119
|
+
`power/runtime_status` becomes `suspended`; otherwise it exits with an error.
|
|
120
|
+
Writing to sysfs requires root, or install `udev/90-sdwire.rules`, which applies
|
|
121
|
+
the setting when the device is plugged in.
|
|
122
|
+
|
|
123
|
+
To do it by hand (replace `2-8` with your device's sysfs name, e.g. `5-1`):
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
echo 0 | sudo tee /sys/bus/usb/devices/2-8/power/autosuspend_delay_ms
|
|
127
|
+
echo auto | sudo tee /sys/bus/usb/devices/2-8/power/control
|
|
128
|
+
```
|
|
129
|
+
|
|
@@ -94,3 +94,19 @@ bdgrd_sdwirec_522 [sd-wire::SRPOL]
|
|
|
94
94
|
|
|
95
95
|
❯ sdwire switch host
|
|
96
96
|
```
|
|
97
|
+
|
|
98
|
+
### SDWire3 and Linux USB power management
|
|
99
|
+
|
|
100
|
+
On Linux, the SDWire3 only hands the SD card to the target while the USB device
|
|
101
|
+
is runtime-suspended. `sdwire switch target` enables this automatically
|
|
102
|
+
(`power/autosuspend_delay_ms=0`, `power/control=auto`) and verifies that
|
|
103
|
+
`power/runtime_status` becomes `suspended`; otherwise it exits with an error.
|
|
104
|
+
Writing to sysfs requires root, or install `udev/90-sdwire.rules`, which applies
|
|
105
|
+
the setting when the device is plugged in.
|
|
106
|
+
|
|
107
|
+
To do it by hand (replace `2-8` with your device's sysfs name, e.g. `5-1`):
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
echo 0 | sudo tee /sys/bus/usb/devices/2-8/power/autosuspend_delay_ms
|
|
111
|
+
echo auto | sudo tee /sys/bus/usb/devices/2-8/power/control
|
|
112
|
+
```
|
|
@@ -17,6 +17,8 @@ import usb.util
|
|
|
17
17
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
|
+
USB_HUB_CLASS = 9
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
def get_sdwirec_devices() -> List[SDWireC]:
|
|
22
24
|
"""Detect and return all connected SDWireC devices.
|
|
@@ -25,7 +27,11 @@ def get_sdwirec_devices() -> List[SDWireC]:
|
|
|
25
27
|
List of SDWireC device instances found on the system
|
|
26
28
|
"""
|
|
27
29
|
try:
|
|
28
|
-
|
|
30
|
+
# skip hubs: opening one blocks while the kernel enumerates a child port
|
|
31
|
+
found_devices = usb.core.find(
|
|
32
|
+
find_all=True,
|
|
33
|
+
custom_match=lambda d: d.bDeviceClass != USB_HUB_CLASS,
|
|
34
|
+
)
|
|
29
35
|
devices = list(found_devices or [])
|
|
30
36
|
except Exception as e:
|
|
31
37
|
log.debug("Error finding USB devices: %s", e)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Linux USB runtime power-management helpers for SDWire3.
|
|
2
|
+
|
|
3
|
+
The SDWire3 RTS5306E controller hands the SD card to the target side only when
|
|
4
|
+
the USB device is runtime-suspended, which requires ``power/control == auto``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
SYSFS_USB_DEVICES = "/sys/bus/usb/devices"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PowerManagementError(RuntimeError):
|
|
17
|
+
"""Raised when the device power state cannot be configured or verified."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UsbPower:
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
bus: int | None,
|
|
24
|
+
port_numbers,
|
|
25
|
+
root: str = SYSFS_USB_DEVICES,
|
|
26
|
+
):
|
|
27
|
+
self.path: str | None = None
|
|
28
|
+
if bus is not None and port_numbers:
|
|
29
|
+
name = f"{bus}-{'.'.join(map(str, port_numbers))}"
|
|
30
|
+
self.path = os.path.join(root, name)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def available(self) -> bool:
|
|
34
|
+
return self.path is not None and os.path.isdir(os.path.join(self.path, "power"))
|
|
35
|
+
|
|
36
|
+
def _file(self, name: str) -> str:
|
|
37
|
+
return os.path.join(self.path or "", "power", name)
|
|
38
|
+
|
|
39
|
+
def _read(self, name: str) -> str | None:
|
|
40
|
+
try:
|
|
41
|
+
with open(self._file(name)) as f:
|
|
42
|
+
return f.read().strip()
|
|
43
|
+
except OSError:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def _write(self, name: str, value: str) -> None:
|
|
47
|
+
try:
|
|
48
|
+
with open(self._file(name), "w") as f:
|
|
49
|
+
f.write(value)
|
|
50
|
+
except PermissionError as e:
|
|
51
|
+
raise PowerManagementError(
|
|
52
|
+
f"permission denied writing '{value}' to {self._file(name)}; "
|
|
53
|
+
"run as root or install the udev rule from the udev/ directory"
|
|
54
|
+
) from e
|
|
55
|
+
except OSError as e:
|
|
56
|
+
raise PowerManagementError(
|
|
57
|
+
f"failed to write '{value}' to {self._file(name)}: {e}"
|
|
58
|
+
) from e
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def control(self) -> str | None:
|
|
62
|
+
return self._read("control")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def runtime_status(self) -> str | None:
|
|
66
|
+
return self._read("runtime_status")
|
|
67
|
+
|
|
68
|
+
def enable_autosuspend(self) -> None:
|
|
69
|
+
"""Set autosuspend delay to 0 and power/control to auto."""
|
|
70
|
+
if not self.available:
|
|
71
|
+
raise PowerManagementError(
|
|
72
|
+
"cannot locate the sysfs power directory of the SDWire3 device"
|
|
73
|
+
)
|
|
74
|
+
if self._read("autosuspend_delay_ms") != "0":
|
|
75
|
+
self._write("autosuspend_delay_ms", "0")
|
|
76
|
+
if self.control != "auto":
|
|
77
|
+
self._write("control", "auto")
|
|
78
|
+
|
|
79
|
+
def wait_for_status(self, expected: str, timeout: float = 5.0) -> bool:
|
|
80
|
+
deadline = time.monotonic() + timeout
|
|
81
|
+
while True:
|
|
82
|
+
if self.runtime_status == expected:
|
|
83
|
+
return True
|
|
84
|
+
if time.monotonic() >= deadline:
|
|
85
|
+
return False
|
|
86
|
+
time.sleep(0.1)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import usb.core
|
|
4
|
+
import usb.util
|
|
5
|
+
|
|
6
|
+
from sdwire.backend.block_device_utils import map_usb_device_to_block_device
|
|
7
|
+
from sdwire.backend.device.power import PowerManagementError, UsbPower
|
|
8
|
+
from sdwire.backend.device.usb_device import PortInfo, USBDevice
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
SDWIRE_GENERATION_SDWIRE3 = 2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SDWire(USBDevice):
|
|
16
|
+
__block_dev = None
|
|
17
|
+
|
|
18
|
+
def __init__(self, port_info: PortInfo, generation: int):
|
|
19
|
+
super().__init__(port_info)
|
|
20
|
+
self.generation = generation
|
|
21
|
+
self._update_block_device()
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def power(self) -> UsbPower:
|
|
25
|
+
return UsbPower(
|
|
26
|
+
getattr(self.usb_device, "bus", None),
|
|
27
|
+
getattr(self.usb_device, "port_numbers", None),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def query_device_state(self):
|
|
31
|
+
"""Return True when in host mode, False when in target mode."""
|
|
32
|
+
if not self.usb_device:
|
|
33
|
+
raise RuntimeError("USB device not available")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
host_attached = bool(self.usb_device.is_kernel_driver_active(0))
|
|
37
|
+
except NotImplementedError as e:
|
|
38
|
+
raise RuntimeError("state query is not supported on this platform") from e
|
|
39
|
+
power = self.power
|
|
40
|
+
if (
|
|
41
|
+
not host_attached
|
|
42
|
+
and power.available
|
|
43
|
+
and power.runtime_status != "suspended"
|
|
44
|
+
):
|
|
45
|
+
log.warning(
|
|
46
|
+
"driver is detached but the device is not runtime-suspended "
|
|
47
|
+
"(power/control=%s, runtime_status=%s); the card is not switched to target",
|
|
48
|
+
power.control,
|
|
49
|
+
power.runtime_status,
|
|
50
|
+
)
|
|
51
|
+
return host_attached
|
|
52
|
+
|
|
53
|
+
def _release_usb_handle(self) -> None:
|
|
54
|
+
# an open usb handle keeps the device awake and prevents runtime suspend
|
|
55
|
+
usb.util.dispose_resources(self.usb_device)
|
|
56
|
+
|
|
57
|
+
def switch_ts(self) -> None:
|
|
58
|
+
if not self.usb_device:
|
|
59
|
+
raise RuntimeError("USB device not available")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
if self.usb_device.is_kernel_driver_active(0):
|
|
63
|
+
log.debug("already in ts mode")
|
|
64
|
+
return
|
|
65
|
+
self.usb_device.attach_kernel_driver(0)
|
|
66
|
+
self.usb_device.reset()
|
|
67
|
+
except (usb.core.USBError, NotImplementedError) as e:
|
|
68
|
+
if self.power.available:
|
|
69
|
+
raise
|
|
70
|
+
# platforms without sysfs (e.g. macOS) can't verify the switch,
|
|
71
|
+
# keep the previous non-fatal behaviour
|
|
72
|
+
log.warning("not able to switch to ts mode: %s", e)
|
|
73
|
+
self._release_usb_handle()
|
|
74
|
+
|
|
75
|
+
def switch_dut(self) -> None:
|
|
76
|
+
if not self.usb_device:
|
|
77
|
+
raise RuntimeError("USB device not available")
|
|
78
|
+
|
|
79
|
+
power = self.power
|
|
80
|
+
if power.available:
|
|
81
|
+
power.enable_autosuspend()
|
|
82
|
+
else:
|
|
83
|
+
log.debug("sysfs power control not found, cannot verify the switch")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
if self.usb_device.is_kernel_driver_active(0):
|
|
87
|
+
self.usb_device.detach_kernel_driver(0)
|
|
88
|
+
self.usb_device.reset()
|
|
89
|
+
except (usb.core.USBError, NotImplementedError) as e:
|
|
90
|
+
if power.available:
|
|
91
|
+
raise
|
|
92
|
+
log.warning("not able to switch to dut mode: %s", e)
|
|
93
|
+
self._release_usb_handle()
|
|
94
|
+
|
|
95
|
+
if power.available and not power.wait_for_status("suspended"):
|
|
96
|
+
raise PowerManagementError(
|
|
97
|
+
f"device did not runtime-suspend (power/control={power.control}, "
|
|
98
|
+
f"runtime_status={power.runtime_status}) at {power.path}; "
|
|
99
|
+
"the SD card is not connected to the target"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _update_block_device(self) -> None:
|
|
103
|
+
"""Update block device detection based on current device state."""
|
|
104
|
+
if not self.usb_device:
|
|
105
|
+
self.__block_dev = None
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
storage_device = self.storage_device
|
|
110
|
+
if storage_device is not None:
|
|
111
|
+
self.__block_dev = map_usb_device_to_block_device(storage_device)
|
|
112
|
+
log.debug(f"SDWire3: Found block device: {self.__block_dev}")
|
|
113
|
+
else:
|
|
114
|
+
self.__block_dev = None
|
|
115
|
+
except Exception as e:
|
|
116
|
+
log.debug(f"SDWire3: Block device detection failed: {e}")
|
|
117
|
+
self.__block_dev = None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def block_dev(self) -> str | None:
|
|
121
|
+
return self.__block_dev
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def storage_device(self) -> usb.core.Device | None:
|
|
125
|
+
"""Return the USB device that corresponds to the storage interface.
|
|
126
|
+
|
|
127
|
+
For SDWire3, this is the same device we control (direct media controller).
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
usb.core.Device: The USB device for storage, or None if not available
|
|
131
|
+
"""
|
|
132
|
+
return self.usb_device
|
|
133
|
+
|
|
134
|
+
def __str__(self) -> str:
|
|
135
|
+
block_dev_str = self.block_dev if self.block_dev is not None else "None"
|
|
136
|
+
return f"{self.serial_string:<30}[{int(self.manufacturer_string):04x}::{int(self.product_string):04x}]\t\t{block_dev_str}"
|
|
137
|
+
|
|
138
|
+
def __repr__(self) -> str:
|
|
139
|
+
return self.__str__()
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Optional
|
|
3
|
-
import usb.core
|
|
4
|
-
from sdwire.backend.device.usb_device import USBDevice, PortInfo
|
|
5
|
-
from sdwire.backend.block_device_utils import map_usb_device_to_block_device
|
|
6
|
-
|
|
7
|
-
log = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
SDWIRE_GENERATION_SDWIRE3 = 2
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class SDWire(USBDevice):
|
|
13
|
-
__block_dev = None
|
|
14
|
-
|
|
15
|
-
def __init__(self, port_info: PortInfo, generation: int):
|
|
16
|
-
super().__init__(port_info)
|
|
17
|
-
self.generation = generation
|
|
18
|
-
self._update_block_device()
|
|
19
|
-
|
|
20
|
-
def query_device_state(self):
|
|
21
|
-
if not self.usb_device:
|
|
22
|
-
log.error("USB device not available")
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
state = self.usb_device.is_kernel_driver_active(0)
|
|
27
|
-
return state
|
|
28
|
-
except Exception as e:
|
|
29
|
-
log.debug(
|
|
30
|
-
"not able to query device state. err: %s",
|
|
31
|
-
e,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
def switch_ts(self) -> None:
|
|
35
|
-
if not self.usb_device:
|
|
36
|
-
log.error("USB device not available")
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
self.usb_device.attach_kernel_driver(0)
|
|
41
|
-
self.usb_device.reset()
|
|
42
|
-
except Exception as e:
|
|
43
|
-
log.debug(
|
|
44
|
-
"not able to switch to ts mode. Device might be already in ts mode, err: %s",
|
|
45
|
-
e,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
def switch_dut(self) -> None:
|
|
49
|
-
if not self.usb_device:
|
|
50
|
-
log.error("USB device not available")
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
self.usb_device.detach_kernel_driver(0)
|
|
55
|
-
self.usb_device.reset()
|
|
56
|
-
except Exception as e:
|
|
57
|
-
log.debug(
|
|
58
|
-
"not able to switch to dut mode. Device might be already in dut mode, err: %s",
|
|
59
|
-
e,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
def _update_block_device(self) -> None:
|
|
63
|
-
"""Update block device detection based on current device state."""
|
|
64
|
-
if not self.usb_device:
|
|
65
|
-
self.__block_dev = None
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
try:
|
|
69
|
-
storage_device = self.storage_device
|
|
70
|
-
if storage_device is not None:
|
|
71
|
-
self.__block_dev = map_usb_device_to_block_device(storage_device)
|
|
72
|
-
log.debug(f"SDWire3: Found block device: {self.__block_dev}")
|
|
73
|
-
else:
|
|
74
|
-
self.__block_dev = None
|
|
75
|
-
except Exception as e:
|
|
76
|
-
log.debug(f"SDWire3: Block device detection failed: {e}")
|
|
77
|
-
self.__block_dev = None
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def block_dev(self) -> Optional[str]:
|
|
81
|
-
return self.__block_dev
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def storage_device(self) -> Optional[usb.core.Device]:
|
|
85
|
-
"""Return the USB device that corresponds to the storage interface.
|
|
86
|
-
|
|
87
|
-
For SDWire3, this is the same device we control (direct media controller).
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
usb.core.Device: The USB device for storage, or None if not available
|
|
91
|
-
"""
|
|
92
|
-
return self.usb_device
|
|
93
|
-
|
|
94
|
-
def __str__(self) -> str:
|
|
95
|
-
block_dev_str = self.block_dev if self.block_dev is not None else "None"
|
|
96
|
-
return f"{self.serial_string:<30}[{int(self.manufacturer_string):04x}::{int(self.product_string):04x}]\t\t{block_dev_str}"
|
|
97
|
-
|
|
98
|
-
def __repr__(self) -> str:
|
|
99
|
-
return self.__str__()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|