Files
app-flutter/docs/impl/reports/LINK-TEST-2026-10-11.md
T

7.3 KiB
Raw Blame History

type, date
type date
report 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:

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.