657ff90c54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
158 lines
7.3 KiB
Markdown
158 lines
7.3 KiB
Markdown
---
|
||
type: report
|
||
date: 2026-10-11
|
||
---
|
||
# USB link test: plain cable vs PD hub
|
||
|
||
Does the PD hub degrade the tablet ⇄ Orin USB (NCM) link? Measured from the app
|
||
(the only place on the tablet whose sockets use the link) with the link test,
|
||
`lib/linktest.dart` + `tool/linktest_server.py`.
|
||
|
||
## Short version
|
||
|
||
- **No measurable effect of the PD hub**, with or without the charger connected.
|
||
All three setups: 0% loss at every size and rate up to 500 packets/s, the same
|
||
latencies, 0 TCP retransmits.
|
||
- **The loss seen earlier the same day did not come back.** Before these runs,
|
||
ICMP ping from the Orin lost 2.5–32% at 20 packets/s through the hub. After a
|
||
replug, the same ping sweep is 0% through the hub too. That connection was in
|
||
a bad state; the hub is not shown to be the cause. Cause unknown; worth
|
||
watching for (the soak below is the app-shaped check).
|
||
- **The link is asymmetric:** tablet → Orin ~22 MB/s, Orin → tablet 1.9–5.6 MB/s
|
||
and variable, with ~30 ms RTT under load and no loss (the Orin's gadget TX side
|
||
queues rather than drops).
|
||
- **App-shaped traffic is fine:** 30 s at 10 packets/s, longest silence ≤ 144 ms,
|
||
against a 5 s heartbeat timeout.
|
||
- **adb push through the SSH tunnel stays slow (0.14 MB/s, 223 Orin retransmits
|
||
per 20 MB) on a healthy link**, while the app's TCP over the same link runs at
|
||
5.6 MB/s with none. Specific to the adbd connection; not the hub, not routing
|
||
(`tcp_fwmark_accept=1`). Unexplained.
|
||
|
||
## Results
|
||
|
||
Link USB 2.0 high-speed in all runs. TCP: 5 s each way; UDP: 3 s per cell, RTT
|
||
p50 / p99 / max in ms.
|
||
|
||
| | Plain C-to-C | Hub + charger | Hub, no charger |
|
||
|---|---|---|---|
|
||
| UDP loss, 64 B and 1400 B, 10–500 /s | 0% | 0% | 0% |
|
||
| UDP 64 B 10/s RTT | 4.1 / 24.2 / 24.2 | 4.0 / 10.5 / 10.5 | 4.1 / 5.6 / 5.6 |
|
||
| UDP 1400 B 500/s RTT | 2.8 / 7.3 / 39.7 | 2.7 / 9.4 / 42.5 | 2.7 / 16.6 / 60.2 |
|
||
| Soak 64 B 10/s 30 s: loss, max gap | 0%, 144 ms | 0%, 135 ms | 0%, 104 ms |
|
||
| TCP Orin → tablet | 1.85 MB/s | 4.06 MB/s | 5.57 MB/s |
|
||
| TCP tablet → Orin | 21.8 MB/s | 22.4 MB/s | 22.6 MB/s |
|
||
| Orin TCP retransmits | 0 | 0 | 0 |
|
||
| ICMP ping from the Orin, 20/s, 56–1472 B | 0% | 0% | 0% |
|
||
|
||
For reference, the same test over WiFi to the Orin's LAN address: 0% loss, about
|
||
25 MB/s each way.
|
||
|
||
Raw records: `~/linktest/results.jsonl` on the car Orin; tabulate with
|
||
`python3 tool/linktest_summary.py ~/linktest/results.jsonl [label…]`.
|
||
|
||
## Running it
|
||
|
||
On the Orin: `python3 linktest_server.py` (from `tool/`, copied to `~/linktest/`).
|
||
On the tablet: **Link test** in the link bar, or remotely:
|
||
|
||
```bash
|
||
adb -s localhost:15555 shell am start -n dev.yukart.display/.MainActivity --es linktest <label>[@<host>]
|
||
```
|
||
|
||
The host defaults to the gateway's. A run takes about 75 s and reports each
|
||
result to the Orin as it goes.
|
||
|
||
## Download in the car setup (PD hub + charger)
|
||
|
||
Two identical download series (`dl:`), then a burst-vs-idle series (`bi:`). Raw
|
||
records under the labels `car-dl1`, `car-dl2`, `car-bi1`.
|
||
|
||
**Paced streams are clean.** 30 frames/s at 0.5, 1, 2 and 4 MB/s, 10 s each, in
|
||
both series: every frame arrived, the full rate was delivered, and queueing never
|
||
exceeded 44 ms. A camera-like stream up to 4 MB/s (32 Mbit/s) fits.
|
||
|
||
**Unpaced traffic is not:**
|
||
|
||
| | Series 1 | Series 2 |
|
||
|---|---|---|
|
||
| Bulk, 5 × 10 s: rate while flowing | 5.6 MB/s | 5.6 MB/s |
|
||
| Bulk: longest stall per run | 1.8, 14.0, 3.6, 0.3, 0.8 s | 0.2, 3.7, 0.8, 0.8, 14.4 s |
|
||
| Bulk: Orin retransmits | 0 | 0 |
|
||
| Burst 64 KB after 1 s idle, p50 | 17 ms | 18 ms |
|
||
| Burst 256 KB, p50 | 12.1 s | 14.1 s |
|
||
| Burst 1 MB, p50 | 14.8 s | 13.8 s |
|
||
| Burst 4 MB, p50 | 4.5 s | 4.6 s |
|
||
| Burst connection: Orin retransmits, smoothed RTT, RTO | 50, 2.35 s, 10.8 s | 49, 2.39 s, 11.0 s |
|
||
|
||
**Not idle-related.** 256 KB bursts on fresh connections, 8 each:
|
||
|
||
| Idle before each burst | p50 | Bursts over 1 s | Orin retransmits, smoothed RTT |
|
||
|---|---|---|---|
|
||
| 0 | 12.6 s | 7 of 8 | 15, 1.6 s |
|
||
| 50 ms | 61 ms | 1 of 8 | 8, 1.8 s |
|
||
| 200 ms | 14.3 s | 6 of 8 | 13, 1.8 s |
|
||
| 1 s | 56 ms | 0 of 8 | 0, 16 ms |
|
||
| 1 s, 50/s UDP keepalive | 56 ms | 0 of 8 (3 near 0.5 s) | 5, 21 ms |
|
||
| 0 (repeat) | 13.6 s | 8 of 8 | 14, 1.5 s |
|
||
|
||
**Reading.** A fresh connection's first burst is fast. Then a few segments go
|
||
missing, and the Orin measures round trips of 1.5–2.4 s on a 3 ms link: packets
|
||
are being *held* for seconds, not only dropped. The smoothed RTT and the RTO
|
||
(10–16 s) inflate, and every later burst on that connection waits out the RTO.
|
||
Bulk stalls of up to 14 s with zero retransmits are the same holding, seen
|
||
without loss. Paced traffic at ≤ 4 MB/s never triggers it.
|
||
|
||
Nothing on the Orin's side records it: no kernel messages, `tx_dropped` and
|
||
`tx_errors` 0 on `usb1`. The packets vanish or wait inside the USB layer, in the
|
||
Orin's NCM gadget or the tablet's NCM host driver; the tablet's sysfs is not
|
||
readable without root.
|
||
|
||
**Consequences:**
|
||
|
||
- Anything the Orin sends to the tablet should be **paced**, not dumped at line
|
||
rate: video at a steady frame rate is fine; a TTS utterance should stream in
|
||
chunks rather than be written in one go (the protocol already plans chunks).
|
||
- A connection that has hit this is poisoned for many seconds (inflated RTO).
|
||
The heartbeat timeout will see it as a dead link and reconnect, which resets it.
|
||
- `adb push`/`install` is exactly an unpaced bulk transfer, which explains its
|
||
0.14 MB/s.
|
||
|
||
## Pacing fixes it
|
||
|
||
The Tegra kernel (5.15.148-tegra) ships without `fq`, `tbf`, `htb` or `fq_codel`,
|
||
so egress cannot be shaped per interface with `tc`. TCP's own pacing needs no
|
||
qdisc: the server capped each download socket with `SO_MAX_PACING_RATE` at
|
||
5 MB/s (`LINKTEST_PACE=5000000`), just under the ~5.6 MB/s the link carries
|
||
when it flows. Labels `pace5-bi`, `pace5-dl`:
|
||
|
||
| | Unpaced | Paced to 5 MB/s |
|
||
|---|---|---|
|
||
| Bulk, 5 × 10 s | 2.3–5.4 MB/s, stalls up to 14 s | 4.96–4.99 MB/s every run, longest stall 94 ms |
|
||
| Bulk, 250 ms windows | min 0, up to 56 empty | min 4.1 MB/s, none empty |
|
||
| Burst 256 KB, p50 | 12–14 s | 58–78 ms |
|
||
| Burst 1 MB / 4 MB, p50 | 14 s / 4.5 s | 225 ms / 863 ms |
|
||
| Burst-connection retransmits | ~50 | 0–1 |
|
||
| Idle gap before burst | erratic | no effect |
|
||
| Paced 30 fps streams, ≤ 4 MB/s | clean | clean |
|
||
|
||
**Every Orin → tablet sender should cap its socket at ~5 MB/s**
|
||
(`SO_MAX_PACING_RATE`, one `setsockopt`). For svc-gw that is the WebSocket
|
||
socket; for a camera relay, its stream socket. Senders that cannot be changed
|
||
(sshd for the adb tunnel) stay affected.
|
||
|
||
## No second data point
|
||
|
||
- **RNDIS:** with NCM removed from the gadget (RNDIS + ACM only), the Redmi
|
||
creates no network interface at all: its kernel has no RNDIS host driver, or
|
||
does not bind it. NCM is the only option for this tablet.
|
||
- **Kyocera Torque 5G (KYG01, Qualcomm lito, Android 13, kernel 4.19):** has
|
||
`cdc_ncm` but no RNDIS driver either. Neither through the PD hub nor on a
|
||
direct C-to-C cable did it enumerate the Orin: on the direct cable it reports
|
||
itself as host with a device attached, yet nothing appears on its bus and the
|
||
Orin's UDC stays `suspended`. Through the hub it charges and is not host at all.
|
||
Even enumerated, its Android 13 Ethernet service only accepts `eth\d+`, while
|
||
the NCM link would be named `usb0` (the Redmi's Android 16 accepts `usb\d+`).
|
||
|
||
So whether the holds come from the Orin's NCM gadget or the Redmi's host side is
|
||
still open; the ~5.6 MB/s downstream ceiling is likewise unattributed.
|