The update hook installs the NPU, like every other setup script
`hooks/preinstall` runs `setup-gstreamer.sh` and `setup-rkaiq.sh` from the release on every update, never fatally, with their reports in the update log. This branch added a third setup script, packaged it, and never wired it in — so a release that ships a duck detector also shipped a manual SSH step before the model in it could run, on every board, for ever. The hook's own comment already says why that is wrong: "This is what the split between provisioning and updating is for. `provision.sh` installs the stack on a new board; every board provisioned before that existed ... is fixed by an ordinary update instead of by somebody remembering a command." `install_npu` is `install_gstreamer` with different words. A board with no NPU still walks, still streams and still sees — on its CPU, via the `.onnx`, which is why `DetectParams::models()` is a list rather than a choice. The header's claim that this is "deliberately not run by the update hook" goes with it. The reason it gave was that the runtime is a download, and `setup-gstreamer.sh` downloads Rockchip's MPP and RGA debs from the same hook. Three things found while wiring it up, each from a board rather than from reading: - **The overlay was not pinned to its script.** `setup-npu.sh` compiles rk3568-npu-enable.dts from beside itself, exactly as `setup-rkaiq.sh` compiles its LD_PRELOAD shim — and only the shim had a test saying it must be packaged. Without the .dts the hook installs the runtime, cannot find the overlay, and leaves the node disabled: an update that succeeded, a detector on the CPU for ever, and one warning in a log. `the_npu_overlay_travels_with_its_script` now fails the build instead. - **`rknn_init` named two causes and not the one every stock board hits.** A disabled device-tree node is not "a model built for another platform" nor "a driver older than the runtime", and the runtime's own line for it — "failed to open rknpu module, need to insmod rknpu dirver!" — sends people after a module that is built in. It reads the node now and says which of the three it is. - **`mediad.service` named the wrong device.** `/dev/dri/renderD129` is panfrost on a Radxa Zero 3, and renderD128 is rockchip-drm, both held before the NPU binds at all. The `render` group is still required; the number never was. Assisted-by: Claude:claude-opus-5
This commit is contained in:
parent
6006231c3c
commit
15afc9baf1
@ -30,11 +30,16 @@ carrying the scale into the decoder and getting it wrong once, quietly.
|
||||
The driver is the gate: it is part of the vendor kernel, mainline has none, and nothing in userspace
|
||||
can work around its absence.
|
||||
|
||||
**An ordinary `robotctl update` does this.** `hooks/preinstall` runs the release's own copy beside
|
||||
`setup-gstreamer.sh` and `setup-rkaiq.sh`, never fatally, with its report in the update log — so a
|
||||
board provisioned before the NPU existed is fixed by an update rather than by somebody remembering
|
||||
a command. Running it by hand is a retry:
|
||||
|
||||
```bash
|
||||
sudo sh /opt/robot/daemon/current/scripts/setup-npu.sh
|
||||
```
|
||||
|
||||
**Expect it to ask for a reboot the first time.** Armbian ships `npu@fde40000` as
|
||||
**Expect the first update carrying this to ask for a reboot.** Armbian ships `npu@fde40000` as
|
||||
`status = "disabled"` on every Radxa Zero 3, so a stock board has the hardware, the kernel and the
|
||||
driver and still no NPU. The script writes the overlay that fixes it and says so; the node binds on
|
||||
the next boot. `--no-enable-node` installs only the runtime, and `dmesg | grep rknpu` is how you
|
||||
|
||||
@ -148,6 +148,35 @@ pub struct Model {
|
||||
// does not support.
|
||||
unsafe impl Send for Model {}
|
||||
|
||||
/// Why `rknn_init` failed, in the order the causes actually occur.
|
||||
///
|
||||
/// **The device tree first, because it is the one every board starts in.** Armbian ships
|
||||
/// `npu@fde40000` as `status = "disabled"` on the Radxa Zero 3, so a robot that nobody has run
|
||||
/// `setup-npu.sh` on has the hardware, the kernel, the driver and the runtime, and still no NPU —
|
||||
/// and the runtime's own log line for it is "failed to open rknpu module, need to insmod rknpu
|
||||
/// dirver!", which sends people looking for a module that is built in.
|
||||
///
|
||||
/// The two causes this used to name are real and are still here, but they are what is left once
|
||||
/// there is a device to talk to at all. Naming them first cost a bring-up session.
|
||||
fn why_init_failed() -> String {
|
||||
match std::fs::read("/proc/device-tree/npu@fde40000/status") {
|
||||
Ok(bytes) if bytes.starts_with(b"disabled") => {
|
||||
"The NPU is DISABLED in this board's device \
|
||||
tree, which is how Armbian ships the Radxa Zero 3 — so the driver never bound and \
|
||||
there is nothing to open. Enable it and reboot: sudo sh \
|
||||
/opt/robot/daemon/current/scripts/setup-npu.sh"
|
||||
.to_owned()
|
||||
}
|
||||
// No node at all: not an RK3566, or a kernel whose device tree does not describe one.
|
||||
Err(_) => "There is no npu@fde40000 in this board's device tree at all — a kernel that is \
|
||||
not the Armbian vendor one is the usual cause, and mainline has no rknpu driver."
|
||||
.to_owned(),
|
||||
_ => "The node is enabled, so this is the model or the driver: a model built for another \
|
||||
platform, or an NPU driver older than the runtime, are the two usual causes."
|
||||
.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
/// Load `librknnrt.so`, then the model, and ask the model what shape it wants.
|
||||
pub fn open(path: &Path) -> Result<Self> {
|
||||
@ -208,9 +237,9 @@ impl Model {
|
||||
);
|
||||
if code != 0 || context.is_null() {
|
||||
bail!(
|
||||
"rknn_init failed ({code}) on {}. A model built for another platform, or an \
|
||||
NPU driver older than the runtime, are the two usual causes.",
|
||||
path.display()
|
||||
"rknn_init failed ({code}) on {}. {}",
|
||||
path.display(),
|
||||
why_init_failed()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -176,6 +176,37 @@ install_rkaiq() {
|
||||
fi
|
||||
}
|
||||
|
||||
# The NPU: the runtime `duck-detect` dlopens, and the device-tree node it needs to reach one.
|
||||
#
|
||||
# Same terms as the two above, and here for the same reason they are: the script owns the pinned
|
||||
# runtime tag, the overlay, and the driver report, and a robot should not need somebody to remember
|
||||
# a command before the model in this very release can run. A board provisioned before the NPU
|
||||
# existed is fixed by an ordinary update.
|
||||
#
|
||||
# **It can ask for a reboot, and that is why this says so rather than doing it.** Armbian ships
|
||||
# `npu@fde40000` disabled on every Radxa Zero 3, so the first update carrying this enables the node
|
||||
# and the NPU binds on the next boot. Until then the detector runs on the CPU via the `.onnx`, which
|
||||
# is the whole reason `DetectParams::models()` is a list rather than a choice.
|
||||
#
|
||||
# Never fatal, for the reason in the header: a board with no NPU still walks, still streams, and
|
||||
# still sees — just on its CPU.
|
||||
install_npu() {
|
||||
script=scripts/setup-npu.sh
|
||||
if [ ! -f "$script" ]; then
|
||||
say "no ${script} in this release; skipping the NPU"
|
||||
return 0
|
||||
fi
|
||||
|
||||
say "checking the NPU runtime"
|
||||
if sh "$script"; then
|
||||
say "NPU runtime ready"
|
||||
else
|
||||
say "the NPU did not finish setting up — the duck detector will run on the CPU, which is
|
||||
slower and warmer but works, and everything else is unaffected. To retry:
|
||||
sudo sh /opt/robot/daemon/current/scripts/setup-npu.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
have="$(installed_onnx)"
|
||||
|
||||
if at_least "$have" "$ONNX_FLOOR"; then
|
||||
@ -196,3 +227,4 @@ fi
|
||||
|
||||
install_gstreamer
|
||||
install_rkaiq
|
||||
install_npu
|
||||
|
||||
@ -57,9 +57,11 @@ ExecStart=/opt/robot/daemon/current/bin/mediad
|
||||
# padd's and tofd's sockets are handed to the `robot` group by those daemons themselves, so the
|
||||
# pad tap and the depth stream are reachable through the same membership.
|
||||
#
|
||||
# **`render` is the NPU.** The rknpu driver registers as a DRM device — `/dev/dri/renderD129` on
|
||||
# this board, `root:render` mode 660 — so the duck detector cannot open the NPU without this group,
|
||||
# and the failure is an `rknn_init` that returns a number rather than anything about permissions.
|
||||
# **`render` is the NPU.** The rknpu driver registers as a DRM device, `root:render` mode 660, so
|
||||
# the duck detector cannot open the NPU without this group — and the failure is an `rknn_init` that
|
||||
# returns a number rather than anything about permissions. Which `/dev/dri/renderD*` it takes is not
|
||||
# fixed and is not worth writing down: on a Radxa Zero 3 the display and Mali already hold 128 and
|
||||
# 129 before the NPU binds at all, so a number recorded here would name somebody else's device.
|
||||
# Membership costs nothing on a board whose NPU is disabled; it is the difference between working
|
||||
# and not on one where it is enabled.
|
||||
User=mediad
|
||||
|
||||
@ -26,11 +26,13 @@
|
||||
#
|
||||
# Idempotent: the runtime is only downloaded when it is missing or a different version is asked for.
|
||||
#
|
||||
# **Deliberately not run by provisioning or the update hook**, and for one reason only: the runtime
|
||||
# is a download. Provisioning that fetches from github.com is provisioning that fails in a room with
|
||||
# no route to it, and a robot that will not come up because a blob it does not yet need was
|
||||
# unreachable is a bad trade. The device-tree half has no such excuse — it is offline, idempotent
|
||||
# and needed by every board — which is why it happens here by default rather than on request.
|
||||
# **Run by `hooks/preinstall` on every update**, beside `setup-gstreamer.sh` and `setup-rkaiq.sh`
|
||||
# and on the same terms: never fatally, with its report in the update log. That is what the split
|
||||
# between provisioning and updating is for — a board provisioned before the NPU existed is fixed by
|
||||
# an ordinary update rather than by somebody remembering a command, and a release that ships a model
|
||||
# should not also ship a manual step before the model can run.
|
||||
#
|
||||
# Running it by hand is then a retry, not the mechanism.
|
||||
set -e
|
||||
|
||||
# Keep in step with `[workspace.metadata.rknpu]` in Cargo.toml — a test asserts they agree.
|
||||
|
||||
@ -1303,6 +1303,39 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// `setup-npu.sh` compiles a device-tree overlay from a .dts beside it, so the .dts has to be
|
||||
/// packaged too.
|
||||
///
|
||||
/// The same shape as `the_rkaiq_shim_travels_with_its_script`, and not covered by
|
||||
/// `every_script_the_hooks_run_is_packaged` for the same reason: the overlay is not a script
|
||||
/// anything runs, it is a source the script compiles. Packaged without it, the hook installs
|
||||
/// the runtime, cannot find the .dts, and leaves the NPU node disabled — so the detector runs
|
||||
/// on the CPU for ever and the log line saying why is one warning in an update that succeeded.
|
||||
#[test]
|
||||
fn the_npu_overlay_travels_with_its_script() {
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("xtask/ has a parent");
|
||||
|
||||
const OVERLAY: &str = "deploy/overlays/rk3568-npu-enable.dts";
|
||||
assert!(root.join(OVERLAY).exists(), "{OVERLAY} is missing");
|
||||
|
||||
let script = std::fs::read_to_string(root.join("scripts/setup-npu.sh")).unwrap();
|
||||
assert!(
|
||||
script.contains("rk3568-npu-enable.dts"),
|
||||
"setup-npu.sh must name the overlay source it compiles"
|
||||
);
|
||||
|
||||
for workflow in PACKAGING_SITES {
|
||||
let text = std::fs::read_to_string(root.join(workflow))
|
||||
.unwrap_or_else(|e| panic!("{workflow}: {e}"));
|
||||
assert!(
|
||||
text.contains(&format!("={OVERLAY}")),
|
||||
"{workflow} packages setup-npu.sh but not {OVERLAY}, which it cannot run without"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `setup-npu.sh` pins the NPU runtime, and Cargo.toml pins it too.
|
||||
///
|
||||
/// Third instance of the same trap — after ONNX Runtime and the GStreamer plugins — and the
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user