JTAG Cable Confinement (rr lease confine)
True OS-level exclusivity on a JTAG/FTDI cable for the lease holder — so a stray
or foreign hw_server (Vivado's, or another agent's) cannot grab a cable
that someone else has leased and corrupt a bring-up.
TL;DR — This is an optional hardening layer on top of
rr lease. It has external system dependencies (a Linux kernel BPF feature,bpftool,clang, and a one-time root grant) that are outsidepip. Without it,rr leasestill works with its advisory teeth and a rogue-holder detector; with it, a foreign process is blocked by the kernel. See External dependencies before enabling.
Why this exists
rr lease arbitrates exclusive access to a bench board and its entourage (serial
console, JTAG cable, TFTP slot, bench IP). Its baseline teeth are an advisory
flock on the cable's lock handle — that blocks any flock-aware tool (rr
itself, a flock(1)-wrapped xsdb/openocd).
But a libusb hw_server (the daemon Vivado/Vitis talks to) opens the FTDI
device directly and never takes that lock. So advisory flock can detect, but
not block, a rogue daemon. On a shared bench where every agent runs as the same
OS user, file permissions can't separate them either.
Cable confinement closes that gap with the kernel's cgroup device controller
(the same mechanism SLURM uses to give a job exclusive GPU access): the leased
cable is permitted only for the lease holder's process group; everyone else gets
a hard EPERM when they try to open the device node.
External dependencies
This feature reaches outside the Python package into the host kernel and system tools. All of these must be present on the bench host:
| Dependency | Why | Check |
|---|---|---|
| Linux cgroup v2 | Device control is done via an eBPF cgroup/dev program attached to a cgroup. | stat -fc %T /sys/fs/cgroup → cgroup2fs |
BPF_PROG_TYPE_CGROUP_DEVICE kernel support | The guard program type. Standard in mainline ≥ 4.15. | bpftool feature probe | grep cgroup_device (as root) |
| Kernel BTF | Lets clang/bpftool build the BTF-typed map. | ls /sys/kernel/btf/vmlinux |
bpftool | Loads/attaches the program and updates the pinned map. | command -v bpftool (Debian/Ubuntu: apt install linux-tools-common linux-tools-$(uname -r)) |
clang with a bpf target | Compiles the guard object (no libbpf-dev needed — the source is self-contained). | clang -print-targets | grep bpf |
| Root, once | Loading an eBPF device program needs CAP_BPF. On benches with kernel.unprivileged_bpf_disabled=2 (common, and recommended), only root can call bpf(). | id / your sudoers setup |
Why root is unavoidable: there is no unprivileged path to device confinement on a hardened cgroup-v2 host. The grant/revoke of a cable to a lease, the program load, and the attach all require
CAP_BPF. The design keeps that privilege behind a single, argument-validated helper so you whitelist one command, not an opensudo bpftool.
If any dependency is missing, rr lease automatically falls back to advisory
flock + the /proc detector (see Graceful degradation) —
nothing breaks, you just don't get kernel-enforced blocking.
You'll be nudged. Whenever you run a board command (
rr program,rr hw,rr ila,rr linux push-sw) and the leased cable isn't confined,rrprints a one-line tip pointing you atrr lease confine install/setup. Silence it on a bench that can't run the guard withexport RR_NO_CONFINE_HINT=1.
One-time setup
# 1. Render the privileged helper + compile the eBPF guard (UNPRIVILEGED).
# Installs to ~/.local/lib/routertl/ and prints the one sudoers line to add.
rr lease confine install
# 2. Add the single printed line via visudo, e.g.:
# <you> ALL=(root) NOPASSWD: /home/<you>/.local/lib/routertl/rr-jtag-confine
sudo visudo -f /etc/sudoers.d/rr-jtag-confine
# 3. Attach the guard (the ONE sudo call). Idempotent.
rr lease confine setup
# 4. Confirm.
rr lease confine status
After step 3, every rr lease acquire automatically confines its cable(s),
and the matching rr program / rr hw commands run inside the lease's cgroup so
they're permitted while a foreign daemon is denied. No per-command flags.
Persistence: the attach + pinned map live until reboot or
rr lease confine teardown. After a reboot, re-runrr lease confine setup(or wire it into a boot-time unit). Theinstallstep (and the sudoers line) is permanent.
Hardened multi-user benches: a
NOPASSWDon a helper you can edit is a convenience, not a new trust boundary (you're already a sudoer). For a bench with distinct sudoers, install the helper root-owned under/usr/local/liband whitelist that path instead, so the privileged binary is immutable to the caller.
Day-to-day usage
Once set up, confinement is transparent:
rr lease acquire kv260 --purpose "bring-up" # cable auto-confined to this lease
export RR_LEASE_HOLDER='<printed-token>'
rr hw psu-init # runs inside the lease cgroup
rr hw peek 0xA0000000 # → "🔒 peek: cgroup-confined to the leased cable"
rr lease release # confinement torn down with the lease
Useful commands:
rr lease conflicts # who (if anyone) is gripping the leased cable's USB node
rr lease confine status # guard attachment + current cable→holder map
rr lease confine teardown # detach + unpin the guard (disable the feature)
rr lease conflicts works even without the guard set up — it scans /proc
and lists any process holding the cable's /dev/bus/usb/... node open, including
a libusb hw_server that advisory flock can't see.
How it works
┌─ user@<uid>.service ──────────────── (eBPF cgroup/dev guard attached here) │ ├─ <agent A shell>.scope id = 42 │ │ └─ rr-jtag-<leaseA>.confine id = 77 ← holder of cable X │ │ └─ xsct → hw_server (cgroup id 77) ── open(cable X) ✅ │ └─ <agent B shell>.scope id = 99 │ └─ rogue hw_server (cgroup id 99) ── open(cable X) ⛔ EPERM
- A small
cgroup/deveBPF program is attached atuser@<uid>.service— the common ancestor of every same-user process, so it covers a foreign daemon no matter which session launched it. - A pinned hash map holds
{ cable device key → holder's cgroup id }. The program allows a USB device access only if it's unmanaged (no lease on that cable) or the accessing process's cgroup id matches the holder. Otherwise: deny. rr leasecreates a per-lease cgroup, grants the cable to it, and migrates the hardware tooling (xsct/hw_server) into it before exec. On release the grant is revoked and the cgroup removed. A crashed holder self-heals: the nextacquireupserts the grant.
The grant is scoped to a single cable's device node, so leasing one board never restricts another board's cable on the same bench.
Graceful degradation
| State | Behaviour |
|---|---|
| Guard set up | Foreign access to a leased cable is blocked by the kernel (EPERM). |
| Guard not set up | rr lease uses advisory flock teeth; rr hw … runs the /proc detector and refuses loudly if a foreign process already holds the leased cable (no silent failure). |
Missing bpftool/clang/BTF/root | rr lease confine install/setup report what's missing; leases keep working in the degraded mode above. |
Confinement never silently believes it's protecting you when it isn't — a hw
command prints 🔒 cgroup-confined when the guard is active, and a warning when
it's only advisory.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
rr lease confine setup says setup failed | The sudoers line isn't active. Re-check sudo -n -l ~/.local/lib/routertl/rr-jtag-confine lists the helper. |
bpftool: command not found (in helper) | Install linux-tools-$(uname -r) (Debian/Ubuntu) or your distro's bpftool. |
eBPF compile failed during install | Install clang with BPF target support; confirm /sys/kernel/btf/vmlinux exists. |
A legit rr hw command gets denied | Confirm it ran through rr (the pure-routertl flow) so it's migrated into the lease cgroup. A raw xsct you launched yourself is — correctly — treated as foreign. |
Cable still reachable by others after acquire | Run rr lease confine status — if it shows not set up, you're in degraded mode; run setup. |
| After reboot, confinement is off | Re-run rr lease confine setup (attach + pins don't persist across reboot). |
See also
rr lease --help— the full bench-coordination command set (acquire / status / release / preempt / run / conflicts / confine).RR_NO_LEASE=1— the logged escape hatch for a single-owner bench.