u2xbar 0.1.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.
- u2xbar-0.1.0/LICENSE +21 -0
- u2xbar-0.1.0/PKG-INFO +235 -0
- u2xbar-0.1.0/README.md +216 -0
- u2xbar-0.1.0/pyproject.toml +37 -0
- u2xbar-0.1.0/setup.cfg +4 -0
- u2xbar-0.1.0/src/u2xbar/__init__.py +25 -0
- u2xbar-0.1.0/src/u2xbar/__main__.py +6 -0
- u2xbar-0.1.0/src/u2xbar/acceptance.py +350 -0
- u2xbar-0.1.0/src/u2xbar/blockread.py +151 -0
- u2xbar-0.1.0/src/u2xbar/cli.py +812 -0
- u2xbar-0.1.0/src/u2xbar/dfu.py +127 -0
- u2xbar-0.1.0/src/u2xbar/discovery.py +61 -0
- u2xbar-0.1.0/src/u2xbar/errors.py +39 -0
- u2xbar-0.1.0/src/u2xbar/kernlog.py +172 -0
- u2xbar-0.1.0/src/u2xbar/protocol.py +234 -0
- u2xbar-0.1.0/src/u2xbar/records.py +212 -0
- u2xbar-0.1.0/src/u2xbar/topo.py +87 -0
- u2xbar-0.1.0/src/u2xbar/unit.py +180 -0
- u2xbar-0.1.0/src/u2xbar.egg-info/PKG-INFO +235 -0
- u2xbar-0.1.0/src/u2xbar.egg-info/SOURCES.txt +32 -0
- u2xbar-0.1.0/src/u2xbar.egg-info/dependency_links.txt +1 -0
- u2xbar-0.1.0/src/u2xbar.egg-info/entry_points.txt +2 -0
- u2xbar-0.1.0/src/u2xbar.egg-info/requires.txt +1 -0
- u2xbar-0.1.0/src/u2xbar.egg-info/top_level.txt +1 -0
- u2xbar-0.1.0/tests/test_acceptance.py +357 -0
- u2xbar-0.1.0/tests/test_cli.py +447 -0
- u2xbar-0.1.0/tests/test_dfu.py +178 -0
- u2xbar-0.1.0/tests/test_discovery.py +80 -0
- u2xbar-0.1.0/tests/test_kernlog.py +165 -0
- u2xbar-0.1.0/tests/test_protocol.py +236 -0
- u2xbar-0.1.0/tests/test_readtest.py +204 -0
- u2xbar-0.1.0/tests/test_sim_integration.py +1081 -0
- u2xbar-0.1.0/tests/test_topo.py +104 -0
- u2xbar-0.1.0/tests/test_unit.py +201 -0
u2xbar-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mete Balci
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
u2xbar-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: u2xbar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Control tool and library for the u2xbar USB crossbar
|
|
5
|
+
Author: Mete Balci
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/metebalci/u2xbar-sw
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: System :: Hardware :: Universal Serial Bus (USB)
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pyserial>=3.5
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# u2xbar
|
|
21
|
+
|
|
22
|
+
Control tool and Python library for u2xbar, a USB 2.0 crossbar that connects
|
|
23
|
+
USB devices to computers under program control. The tool speaks the unit's
|
|
24
|
+
control protocol (`docs/protocol.md` in the u2xbar-sw repository) on its
|
|
25
|
+
control port, a USB CDC ACM serial port.
|
|
26
|
+
|
|
27
|
+
## Terms
|
|
28
|
+
|
|
29
|
+
- A host port is where a computer connects.
|
|
30
|
+
- A device port is where a peripheral connects.
|
|
31
|
+
- A hub port is a downstream port of one of the unit's internal hubs. Hub
|
|
32
|
+
port p leads to device port p.
|
|
33
|
+
- A connection is a device port assigned to a host port.
|
|
34
|
+
- The control port is the unit's own USB port for the control protocol.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
pip install u2xbar
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
It needs Python 3.10 or later and pyserial. On Linux, the user needs access to
|
|
43
|
+
the control port (usually the group `dialout`).
|
|
44
|
+
|
|
45
|
+
## Finding a unit
|
|
46
|
+
|
|
47
|
+
The tool looks for a serial port with USB ID 16C0:27DD whose serial number
|
|
48
|
+
starts with `u2xbar.metebalci.com:`. The part after the colon is the unit's
|
|
49
|
+
unique ID. With several units, `--serial <unique ID>` chooses one. `--port
|
|
50
|
+
<path>` uses that port and skips the search; it also works on the firmware's
|
|
51
|
+
debug console.
|
|
52
|
+
|
|
53
|
+
## Command line
|
|
54
|
+
|
|
55
|
+
The commands follow the protocol:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
u2xbar version
|
|
59
|
+
u2xbar list
|
|
60
|
+
u2xbar connect 3 2 # device port 3 to host port 2
|
|
61
|
+
u2xbar disconnect 3 # or: disconnect all
|
|
62
|
+
u2xbar dev 3 vbus off # dev <d> vbus|data on|off, dev <d> host <h>|none
|
|
63
|
+
u2xbar host 1 detach # host <h> vbus|data on|off, attach, detach
|
|
64
|
+
u2xbar hub 1 boost 0,2,2,2,2 # or: hub <h> boost <p>|up|all <0-3>, reset, regs
|
|
65
|
+
u2xbar param discharge_ms 30
|
|
66
|
+
u2xbar events on
|
|
67
|
+
u2xbar reset
|
|
68
|
+
u2xbar dfu # sends the command only; see "Firmware update"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The tool adds:
|
|
72
|
+
|
|
73
|
+
- `status [--watch] [--interval S] [--count N]` reads the whole state.
|
|
74
|
+
- `measure [--interval S] [--count N] [--csv]` reads the sensors; the CSV has
|
|
75
|
+
a time column in UTC.
|
|
76
|
+
- `raw <line>` sends one protocol line and prints the reply.
|
|
77
|
+
- `shell` sends the lines you type and prints replies and events.
|
|
78
|
+
- `run <script>` runs a file of protocol lines (or `-` for stdin). `#` starts
|
|
79
|
+
a comment, and `sleep <seconds>` waits on the computer. The whole script is
|
|
80
|
+
checked before the first line is sent. The run stops at the first `err`
|
|
81
|
+
unless `--keep-going` is given.
|
|
82
|
+
- `topo` (Linux) shows where each host port's hub and the device on each
|
|
83
|
+
device port sit in this computer's USB tree: bus path, VID:PID, speed and
|
|
84
|
+
product. It finds the hubs by their serial number `<unique ID>-H<h>`.
|
|
85
|
+
|
|
86
|
+
`--json` prints records as JSON, one document per line. Events go to stderr,
|
|
87
|
+
except in `shell` and `run`.
|
|
88
|
+
|
|
89
|
+
Exit codes: 0 ok, 1 the unit answered `err` or a test failed, 2 usage error,
|
|
90
|
+
3 no unit, an I/O error (the port, a timeout, or a reply that breaks the
|
|
91
|
+
protocol), or a test that cannot start.
|
|
92
|
+
|
|
93
|
+
## Firmware update
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
u2xbar dfu --image u2xbar.bin [--dfu-util PATH] [--wait S]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The tool reads the unit's `version`, sends `dfu`, and waits for the STM32
|
|
100
|
+
system bootloader (USB 0483:DF11) to appear in sysfs (Linux). It then runs
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
dfu-util -d 0483:df11 -p <bus path> -a 0 -s 0x08000000:leave -D u2xbar.bin
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
with the bus path of the bootloader that appeared, so another board in DFU
|
|
107
|
+
mode is left alone. When dfu-util is done, the tool waits for the unit to come
|
|
108
|
+
back on its control port (found by its unique ID, or at `--port`) and prints
|
|
109
|
+
its `version`. dfu-util is a separate program; the tool checks that it exists
|
|
110
|
+
before it sends `dfu`. `--wait` (10 s) bounds each of the two waits.
|
|
111
|
+
|
|
112
|
+
## Acceptance tests
|
|
113
|
+
|
|
114
|
+
The tests check a unit on this computer (Linux). Each host port to be tested
|
|
115
|
+
must be connected to this computer, so that `topo` finds its hub.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
u2xbar test enum [--hosts H,...] [--devices D,...] [--timeout S]
|
|
119
|
+
[--allow-full-speed D,...] [--no-kernel-log]
|
|
120
|
+
u2xbar test moves --count N --seed S [--hosts H,...] [--devices D,...] [--timeout S]
|
|
121
|
+
u2xbar test read --device /dev/sdX --size GB [--baseline MB/s] [--margin PERCENT] [--force]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`test enum` and `test moves` start with a first pass: every selected device
|
|
125
|
+
port is connected to the first selected host port, and the device that
|
|
126
|
+
appears on each hub port is recorded. A device is known by its VID:PID and
|
|
127
|
+
serial number, or, without a serial number, by its VID:PID and hub port. A
|
|
128
|
+
device port whose device does not enumerate (`ufp=1` and no USB device) is
|
|
129
|
+
reported.
|
|
130
|
+
|
|
131
|
+
- `test enum` connects each device to each selected host port, one at a
|
|
132
|
+
time. Each step passes if the device appears on that host port's hub at its
|
|
133
|
+
hub port within `--timeout` (5 s), at 480M (or 12M on the device ports
|
|
134
|
+
named in `--allow-full-speed`), on no other hub, is gone after the
|
|
135
|
+
disconnect, and the kernel log has no new USB error (priority err or worse
|
|
136
|
+
from `usb`, `hub` or a host controller driver). The kernel log is read from
|
|
137
|
+
`/dev/kmsg` or, without the right to read it, from `journalctl -k`;
|
|
138
|
+
`--no-kernel-log` skips the check. The tool prints a table and the
|
|
139
|
+
failures.
|
|
140
|
+
- `test moves` makes N random moves (`connect d h` to another host port) of
|
|
141
|
+
the device ports that have devices. After each move the same device must
|
|
142
|
+
appear on the new host port within `--timeout` and on no other host port.
|
|
143
|
+
The moves depend only on the seed and the ports, so a run can be repeated.
|
|
144
|
+
The tool prints each move, a summary (moves, failures, retries, the slowest
|
|
145
|
+
arrival) and every failure. A failed step or move does not stop either
|
|
146
|
+
test; the exit code is 1 at the end.
|
|
147
|
+
- `test read` reads the first `--size` GB (10^9 bytes) of a block device in
|
|
148
|
+
4 MiB reads, with `O_DIRECT` where the file system allows it and otherwise
|
|
149
|
+
with the page cache dropped through `posix_fadvise`, and prints the rate in
|
|
150
|
+
MB/s (10^6 bytes per second). With `--baseline`, the rate measured through
|
|
151
|
+
a direct cable, it passes if it is at most `--margin` (3 %) slower. It only
|
|
152
|
+
reads, and it refuses a device that is mounted (or one of its partitions)
|
|
153
|
+
unless `--force` is given. It needs no unit.
|
|
154
|
+
|
|
155
|
+
A full-speed device that comes up as low speed or not at all gets one retry
|
|
156
|
+
(disconnect and connect again), which is counted and reported: the USB2517
|
|
157
|
+
can detect a full-speed device as low speed when the port reset comes early
|
|
158
|
+
in a frame (errata DS80000628A, Module 4). In `test enum` this holds for the
|
|
159
|
+
device ports in `--allow-full-speed`.
|
|
160
|
+
|
|
161
|
+
`--timeout` of a test is the time a device may take to appear or go; the
|
|
162
|
+
time per protocol command is set with `--timeout` before the word `test`.
|
|
163
|
+
`--poll` (0.05 s) is the time between scans of the USB tree.
|
|
164
|
+
|
|
165
|
+
## Library
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from u2xbar import Unit
|
|
169
|
+
|
|
170
|
+
with Unit() as unit: # or Unit(serial="..."), Unit(port="/dev/ttyACM0")
|
|
171
|
+
print(unit.version())
|
|
172
|
+
unit.connect(3, 2)
|
|
173
|
+
for port in unit.list():
|
|
174
|
+
print(port.port, port.host, port.vbus, port.data)
|
|
175
|
+
print(unit.measure().i5_ma)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Each protocol command has a method, and `command(line)` sends any line.
|
|
179
|
+
Results are dataclasses; a value the unit gives as `-` is `None`. An `err`
|
|
180
|
+
reply raises `UnitError`, which carries the code. Events go to the
|
|
181
|
+
`on_event` callback, or wait for `poll_events()`.
|
|
182
|
+
|
|
183
|
+
## Tests
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
uv venv <venv>
|
|
187
|
+
VIRTUAL_ENV=<venv> uv pip install -e . --group test
|
|
188
|
+
<venv>/bin/python -m pytest
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The tests run the tool against a fake unit on a pseudo-terminal and need no
|
|
192
|
+
hardware. For the acceptance tests, a fake world ties the fake unit's
|
|
193
|
+
connection commands to a fake sysfs tree, with devices that are slow, come up
|
|
194
|
+
as low speed, appear on the wrong hub, stay behind, or never appear; a fake
|
|
195
|
+
kernel log, a file as the block device, and a fake dfu-util complete it. One
|
|
196
|
+
test reads this computer's kernel log and sysfs; it runs only with
|
|
197
|
+
`U2XBAR_REAL_SYSTEM=1`.
|
|
198
|
+
|
|
199
|
+
### Integration tests
|
|
200
|
+
|
|
201
|
+
The integration tests (`tests/test_sim_integration.py`) run the tool against
|
|
202
|
+
the firmware simulator `u2xbar-sim` (`sim/` in the u2xbar-sw repository): the
|
|
203
|
+
firmware's core and protocol on the board model of its PC tests, served on a
|
|
204
|
+
pseudo-terminal in real time. They run only when `U2XBAR_SIM` names the
|
|
205
|
+
simulator binary:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
cmake -S ../sim -B <build> -G Ninja
|
|
209
|
+
cmake --build <build>
|
|
210
|
+
U2XBAR_SIM=<build>/u2xbar-sim <venv>/bin/python -m pytest tests/test_sim_integration.py
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Each test starts its own simulator, with its pty file in the test's
|
|
214
|
+
temporary directory, and stops it at the end, also when the test fails. A
|
|
215
|
+
test drives the simulated unit through the command line (`python -m u2xbar
|
|
216
|
+
--port <pty>`, one process per command) or through `Unit`; most tests run
|
|
217
|
+
both ways. The simulator's `state` query checks the simulated hardware: for
|
|
218
|
+
example, that a connected device port's switch selects the hub of its host
|
|
219
|
+
port and that its VBUS is on, or that the discharge FET comes on only after
|
|
220
|
+
VBUS went off. Injections change the simulated world: a device attached, a
|
|
221
|
+
fault that lasts past `fault_off_ms`, a computer that removes VBUS, the 5 V
|
|
222
|
+
rail, a chip that does not answer. The tests cover every command, the
|
|
223
|
+
parameters and their bounds, the error codes `syntax`, `range`, `state` and
|
|
224
|
+
`hw` (the simulator cannot make an I2C transfer time out), events, `run`,
|
|
225
|
+
`raw`, `shell`, `measure --csv`, `--json` and the exit codes. A hardware-rule
|
|
226
|
+
violation of the board model fails the test.
|
|
227
|
+
|
|
228
|
+
`scripts/check.sh` in the repository builds the simulator and runs these
|
|
229
|
+
tests with the other checks.
|
|
230
|
+
|
|
231
|
+
## How it was written
|
|
232
|
+
|
|
233
|
+
The code and documentation are written with
|
|
234
|
+
[Claude Code](https://claude.com/claude-code), using Anthropic's Claude Opus
|
|
235
|
+
5.5 model.
|
u2xbar-0.1.0/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# u2xbar
|
|
2
|
+
|
|
3
|
+
Control tool and Python library for u2xbar, a USB 2.0 crossbar that connects
|
|
4
|
+
USB devices to computers under program control. The tool speaks the unit's
|
|
5
|
+
control protocol (`docs/protocol.md` in the u2xbar-sw repository) on its
|
|
6
|
+
control port, a USB CDC ACM serial port.
|
|
7
|
+
|
|
8
|
+
## Terms
|
|
9
|
+
|
|
10
|
+
- A host port is where a computer connects.
|
|
11
|
+
- A device port is where a peripheral connects.
|
|
12
|
+
- A hub port is a downstream port of one of the unit's internal hubs. Hub
|
|
13
|
+
port p leads to device port p.
|
|
14
|
+
- A connection is a device port assigned to a host port.
|
|
15
|
+
- The control port is the unit's own USB port for the control protocol.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
pip install u2xbar
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It needs Python 3.10 or later and pyserial. On Linux, the user needs access to
|
|
24
|
+
the control port (usually the group `dialout`).
|
|
25
|
+
|
|
26
|
+
## Finding a unit
|
|
27
|
+
|
|
28
|
+
The tool looks for a serial port with USB ID 16C0:27DD whose serial number
|
|
29
|
+
starts with `u2xbar.metebalci.com:`. The part after the colon is the unit's
|
|
30
|
+
unique ID. With several units, `--serial <unique ID>` chooses one. `--port
|
|
31
|
+
<path>` uses that port and skips the search; it also works on the firmware's
|
|
32
|
+
debug console.
|
|
33
|
+
|
|
34
|
+
## Command line
|
|
35
|
+
|
|
36
|
+
The commands follow the protocol:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
u2xbar version
|
|
40
|
+
u2xbar list
|
|
41
|
+
u2xbar connect 3 2 # device port 3 to host port 2
|
|
42
|
+
u2xbar disconnect 3 # or: disconnect all
|
|
43
|
+
u2xbar dev 3 vbus off # dev <d> vbus|data on|off, dev <d> host <h>|none
|
|
44
|
+
u2xbar host 1 detach # host <h> vbus|data on|off, attach, detach
|
|
45
|
+
u2xbar hub 1 boost 0,2,2,2,2 # or: hub <h> boost <p>|up|all <0-3>, reset, regs
|
|
46
|
+
u2xbar param discharge_ms 30
|
|
47
|
+
u2xbar events on
|
|
48
|
+
u2xbar reset
|
|
49
|
+
u2xbar dfu # sends the command only; see "Firmware update"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The tool adds:
|
|
53
|
+
|
|
54
|
+
- `status [--watch] [--interval S] [--count N]` reads the whole state.
|
|
55
|
+
- `measure [--interval S] [--count N] [--csv]` reads the sensors; the CSV has
|
|
56
|
+
a time column in UTC.
|
|
57
|
+
- `raw <line>` sends one protocol line and prints the reply.
|
|
58
|
+
- `shell` sends the lines you type and prints replies and events.
|
|
59
|
+
- `run <script>` runs a file of protocol lines (or `-` for stdin). `#` starts
|
|
60
|
+
a comment, and `sleep <seconds>` waits on the computer. The whole script is
|
|
61
|
+
checked before the first line is sent. The run stops at the first `err`
|
|
62
|
+
unless `--keep-going` is given.
|
|
63
|
+
- `topo` (Linux) shows where each host port's hub and the device on each
|
|
64
|
+
device port sit in this computer's USB tree: bus path, VID:PID, speed and
|
|
65
|
+
product. It finds the hubs by their serial number `<unique ID>-H<h>`.
|
|
66
|
+
|
|
67
|
+
`--json` prints records as JSON, one document per line. Events go to stderr,
|
|
68
|
+
except in `shell` and `run`.
|
|
69
|
+
|
|
70
|
+
Exit codes: 0 ok, 1 the unit answered `err` or a test failed, 2 usage error,
|
|
71
|
+
3 no unit, an I/O error (the port, a timeout, or a reply that breaks the
|
|
72
|
+
protocol), or a test that cannot start.
|
|
73
|
+
|
|
74
|
+
## Firmware update
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
u2xbar dfu --image u2xbar.bin [--dfu-util PATH] [--wait S]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The tool reads the unit's `version`, sends `dfu`, and waits for the STM32
|
|
81
|
+
system bootloader (USB 0483:DF11) to appear in sysfs (Linux). It then runs
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
dfu-util -d 0483:df11 -p <bus path> -a 0 -s 0x08000000:leave -D u2xbar.bin
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
with the bus path of the bootloader that appeared, so another board in DFU
|
|
88
|
+
mode is left alone. When dfu-util is done, the tool waits for the unit to come
|
|
89
|
+
back on its control port (found by its unique ID, or at `--port`) and prints
|
|
90
|
+
its `version`. dfu-util is a separate program; the tool checks that it exists
|
|
91
|
+
before it sends `dfu`. `--wait` (10 s) bounds each of the two waits.
|
|
92
|
+
|
|
93
|
+
## Acceptance tests
|
|
94
|
+
|
|
95
|
+
The tests check a unit on this computer (Linux). Each host port to be tested
|
|
96
|
+
must be connected to this computer, so that `topo` finds its hub.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
u2xbar test enum [--hosts H,...] [--devices D,...] [--timeout S]
|
|
100
|
+
[--allow-full-speed D,...] [--no-kernel-log]
|
|
101
|
+
u2xbar test moves --count N --seed S [--hosts H,...] [--devices D,...] [--timeout S]
|
|
102
|
+
u2xbar test read --device /dev/sdX --size GB [--baseline MB/s] [--margin PERCENT] [--force]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`test enum` and `test moves` start with a first pass: every selected device
|
|
106
|
+
port is connected to the first selected host port, and the device that
|
|
107
|
+
appears on each hub port is recorded. A device is known by its VID:PID and
|
|
108
|
+
serial number, or, without a serial number, by its VID:PID and hub port. A
|
|
109
|
+
device port whose device does not enumerate (`ufp=1` and no USB device) is
|
|
110
|
+
reported.
|
|
111
|
+
|
|
112
|
+
- `test enum` connects each device to each selected host port, one at a
|
|
113
|
+
time. Each step passes if the device appears on that host port's hub at its
|
|
114
|
+
hub port within `--timeout` (5 s), at 480M (or 12M on the device ports
|
|
115
|
+
named in `--allow-full-speed`), on no other hub, is gone after the
|
|
116
|
+
disconnect, and the kernel log has no new USB error (priority err or worse
|
|
117
|
+
from `usb`, `hub` or a host controller driver). The kernel log is read from
|
|
118
|
+
`/dev/kmsg` or, without the right to read it, from `journalctl -k`;
|
|
119
|
+
`--no-kernel-log` skips the check. The tool prints a table and the
|
|
120
|
+
failures.
|
|
121
|
+
- `test moves` makes N random moves (`connect d h` to another host port) of
|
|
122
|
+
the device ports that have devices. After each move the same device must
|
|
123
|
+
appear on the new host port within `--timeout` and on no other host port.
|
|
124
|
+
The moves depend only on the seed and the ports, so a run can be repeated.
|
|
125
|
+
The tool prints each move, a summary (moves, failures, retries, the slowest
|
|
126
|
+
arrival) and every failure. A failed step or move does not stop either
|
|
127
|
+
test; the exit code is 1 at the end.
|
|
128
|
+
- `test read` reads the first `--size` GB (10^9 bytes) of a block device in
|
|
129
|
+
4 MiB reads, with `O_DIRECT` where the file system allows it and otherwise
|
|
130
|
+
with the page cache dropped through `posix_fadvise`, and prints the rate in
|
|
131
|
+
MB/s (10^6 bytes per second). With `--baseline`, the rate measured through
|
|
132
|
+
a direct cable, it passes if it is at most `--margin` (3 %) slower. It only
|
|
133
|
+
reads, and it refuses a device that is mounted (or one of its partitions)
|
|
134
|
+
unless `--force` is given. It needs no unit.
|
|
135
|
+
|
|
136
|
+
A full-speed device that comes up as low speed or not at all gets one retry
|
|
137
|
+
(disconnect and connect again), which is counted and reported: the USB2517
|
|
138
|
+
can detect a full-speed device as low speed when the port reset comes early
|
|
139
|
+
in a frame (errata DS80000628A, Module 4). In `test enum` this holds for the
|
|
140
|
+
device ports in `--allow-full-speed`.
|
|
141
|
+
|
|
142
|
+
`--timeout` of a test is the time a device may take to appear or go; the
|
|
143
|
+
time per protocol command is set with `--timeout` before the word `test`.
|
|
144
|
+
`--poll` (0.05 s) is the time between scans of the USB tree.
|
|
145
|
+
|
|
146
|
+
## Library
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from u2xbar import Unit
|
|
150
|
+
|
|
151
|
+
with Unit() as unit: # or Unit(serial="..."), Unit(port="/dev/ttyACM0")
|
|
152
|
+
print(unit.version())
|
|
153
|
+
unit.connect(3, 2)
|
|
154
|
+
for port in unit.list():
|
|
155
|
+
print(port.port, port.host, port.vbus, port.data)
|
|
156
|
+
print(unit.measure().i5_ma)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Each protocol command has a method, and `command(line)` sends any line.
|
|
160
|
+
Results are dataclasses; a value the unit gives as `-` is `None`. An `err`
|
|
161
|
+
reply raises `UnitError`, which carries the code. Events go to the
|
|
162
|
+
`on_event` callback, or wait for `poll_events()`.
|
|
163
|
+
|
|
164
|
+
## Tests
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
uv venv <venv>
|
|
168
|
+
VIRTUAL_ENV=<venv> uv pip install -e . --group test
|
|
169
|
+
<venv>/bin/python -m pytest
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The tests run the tool against a fake unit on a pseudo-terminal and need no
|
|
173
|
+
hardware. For the acceptance tests, a fake world ties the fake unit's
|
|
174
|
+
connection commands to a fake sysfs tree, with devices that are slow, come up
|
|
175
|
+
as low speed, appear on the wrong hub, stay behind, or never appear; a fake
|
|
176
|
+
kernel log, a file as the block device, and a fake dfu-util complete it. One
|
|
177
|
+
test reads this computer's kernel log and sysfs; it runs only with
|
|
178
|
+
`U2XBAR_REAL_SYSTEM=1`.
|
|
179
|
+
|
|
180
|
+
### Integration tests
|
|
181
|
+
|
|
182
|
+
The integration tests (`tests/test_sim_integration.py`) run the tool against
|
|
183
|
+
the firmware simulator `u2xbar-sim` (`sim/` in the u2xbar-sw repository): the
|
|
184
|
+
firmware's core and protocol on the board model of its PC tests, served on a
|
|
185
|
+
pseudo-terminal in real time. They run only when `U2XBAR_SIM` names the
|
|
186
|
+
simulator binary:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
cmake -S ../sim -B <build> -G Ninja
|
|
190
|
+
cmake --build <build>
|
|
191
|
+
U2XBAR_SIM=<build>/u2xbar-sim <venv>/bin/python -m pytest tests/test_sim_integration.py
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Each test starts its own simulator, with its pty file in the test's
|
|
195
|
+
temporary directory, and stops it at the end, also when the test fails. A
|
|
196
|
+
test drives the simulated unit through the command line (`python -m u2xbar
|
|
197
|
+
--port <pty>`, one process per command) or through `Unit`; most tests run
|
|
198
|
+
both ways. The simulator's `state` query checks the simulated hardware: for
|
|
199
|
+
example, that a connected device port's switch selects the hub of its host
|
|
200
|
+
port and that its VBUS is on, or that the discharge FET comes on only after
|
|
201
|
+
VBUS went off. Injections change the simulated world: a device attached, a
|
|
202
|
+
fault that lasts past `fault_off_ms`, a computer that removes VBUS, the 5 V
|
|
203
|
+
rail, a chip that does not answer. The tests cover every command, the
|
|
204
|
+
parameters and their bounds, the error codes `syntax`, `range`, `state` and
|
|
205
|
+
`hw` (the simulator cannot make an I2C transfer time out), events, `run`,
|
|
206
|
+
`raw`, `shell`, `measure --csv`, `--json` and the exit codes. A hardware-rule
|
|
207
|
+
violation of the board model fails the test.
|
|
208
|
+
|
|
209
|
+
`scripts/check.sh` in the repository builds the simulator and runs these
|
|
210
|
+
tests with the other checks.
|
|
211
|
+
|
|
212
|
+
## How it was written
|
|
213
|
+
|
|
214
|
+
The code and documentation are written with
|
|
215
|
+
[Claude Code](https://claude.com/claude-code), using Anthropic's Claude Opus
|
|
216
|
+
5.5 model.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 Mete Balci
|
|
3
|
+
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = ["setuptools>=77"]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "u2xbar"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "Control tool and library for the u2xbar USB crossbar"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
authors = [{ name = "Mete Balci" }]
|
|
16
|
+
requires-python = ">=3.10"
|
|
17
|
+
dependencies = ["pyserial>=3.5"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Topic :: System :: Hardware :: Universal Serial Bus (USB)",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Repository = "https://github.com/metebalci/u2xbar-sw"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
u2xbar = "u2xbar.cli:run"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
test = ["pytest>=7"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
u2xbar-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 Mete Balci
|
|
3
|
+
|
|
4
|
+
"""Control tool and library for the u2xbar USB crossbar."""
|
|
5
|
+
|
|
6
|
+
from .discovery import FoundUnit, find_unit, list_units
|
|
7
|
+
from .errors import (
|
|
8
|
+
NoUnitError, ProtocolError, RequestError, SeveralUnitsError, U2xbarError, UnitError,
|
|
9
|
+
UnitTimeout,
|
|
10
|
+
)
|
|
11
|
+
from .protocol import Client, Event, Record
|
|
12
|
+
from .records import (
|
|
13
|
+
DevPort, HostDetach, HostPort, HubBoost, Measurement, Param, RegBlock, Status,
|
|
14
|
+
UnitInfo, Version,
|
|
15
|
+
)
|
|
16
|
+
from .unit import Unit
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Client", "DevPort", "Event", "FoundUnit", "HostDetach", "HostPort", "HubBoost",
|
|
22
|
+
"Measurement", "NoUnitError", "Param", "ProtocolError", "Record", "RegBlock",
|
|
23
|
+
"RequestError", "SeveralUnitsError", "Status", "U2xbarError", "Unit", "UnitError",
|
|
24
|
+
"UnitInfo", "UnitTimeout", "Version", "find_unit", "list_units",
|
|
25
|
+
]
|