Initial RK1 media-center image project
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# RK1 runtime and hardware diagnostics
|
||||
|
||||
This directory installs the local RKNN C runtime and supplies the two commands
|
||||
used to qualify an RK3588 media image. It does not install RKNN Toolkit's model
|
||||
conversion environment, Python wheels, or `rknn_server`.
|
||||
|
||||
## Pinned RKNN runtime
|
||||
|
||||
`rknn-version.env` locks RKNN Toolkit2 v2.3.2 to commit
|
||||
`42aa1d426c0a9e0869b6374edba009f7208a1926`. The installer verifies the SHA-256
|
||||
of the ARM64 runtime, C header, RK3588 MobileNet model, demo image, and license
|
||||
before installing anything. A tag move or damaged download therefore fails the
|
||||
image build.
|
||||
|
||||
Run the installer inside the target chroot:
|
||||
|
||||
```sh
|
||||
runtime/install-rknn-runtime.sh
|
||||
```
|
||||
|
||||
Or install into a mounted root filesystem from an ARM64 build host:
|
||||
|
||||
```sh
|
||||
runtime/install-rknn-runtime.sh --rootfs /path/to/rootfs
|
||||
```
|
||||
|
||||
For an offline/reproducible build, provide the pinned checkout explicitly:
|
||||
|
||||
```sh
|
||||
runtime/install-rknn-runtime.sh \
|
||||
--rootfs /path/to/rootfs \
|
||||
--source-dir /path/to/rknn-toolkit2-v2.3.2
|
||||
```
|
||||
|
||||
The build host needs Bash, Git when downloading, an ARM64 C compiler,
|
||||
`coreutils`, and standard install utilities. Native compilation on the RK1 is
|
||||
the supported default; a cross-build can select a compiler with `--cc`.
|
||||
|
||||
Files are installed below `/opt/rknn/2.3.2`, with `/opt/rknn/current` as the
|
||||
stable link. The runtime path is registered in `/etc/ld.so.conf.d/rknn.conf`,
|
||||
and the inference test also embeds that path as an ELF rpath. The test uses a
|
||||
deterministic synthetic tensor and verifies model initialization, selected-core
|
||||
submission, and finite output. It is a hardware/runtime smoke test, not a
|
||||
MobileNet accuracy benchmark.
|
||||
|
||||
## Hardware self-test
|
||||
|
||||
The installed command inventories HDMI/EDID, Panthor or Mali, DRM render
|
||||
nodes, RKMPP decoder and encoder bindings, AV1, RGA2/RGA3, RKNN, HDMI ALSA,
|
||||
Ethernet link state, eMMC, NVMe, and the root filesystem:
|
||||
|
||||
```sh
|
||||
rk1-media-selftest
|
||||
rk1-media-selftest --quick
|
||||
rk1-media-selftest --json
|
||||
```
|
||||
|
||||
The normal run adds short Vulkan, H.264/HEVC/MJPEG encode, and NPU core
|
||||
0/1/2/combined workloads when their tools are installed. `--quick` performs no
|
||||
active workloads. To test 4K decoding and RKMPP-to-RGA zero-copy scaling, place
|
||||
licensed samples in a directory with names containing `h264`, `hevc`, `vp9`,
|
||||
and `av1`, then run:
|
||||
|
||||
```sh
|
||||
rk1-media-selftest --media-dir /usr/share/rk1-media/samples
|
||||
```
|
||||
|
||||
Missing tools and tests that cannot apply are `SKIP`; absent or disconnected
|
||||
hardware is normally `WARN`; an advertised capability whose active workload
|
||||
fails is `FAIL`. The exit status is zero unless a check fails. `--strict` also
|
||||
makes warnings return status 1. Invocation errors return status 2.
|
||||
|
||||
## Diagnostic bundle
|
||||
|
||||
Create a local archive suitable for troubleshooting a blank display or missing
|
||||
accelerator:
|
||||
|
||||
```sh
|
||||
sudo rk1-media-diagnostics
|
||||
sudo rk1-media-diagnostics --include-active-tests \
|
||||
--media-dir /usr/share/rk1-media/samples
|
||||
```
|
||||
|
||||
The collector records the JSON quick test, DRM connectors, decoded EDID,
|
||||
drivers, filtered kernel messages, relevant packages, ALSA devices, link state,
|
||||
and storage topology. It does not upload anything. It omits raw EDID, disk
|
||||
serials and UUIDs, addresses, user files, SSH material, and unfiltered journals,
|
||||
and applies basic redaction; review the bundle before sharing it.
|
||||
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
runtime/tests/run-tests.sh
|
||||
RKNN_SOURCE_DIR=/path/to/rknn-toolkit2-v2.3.2 \
|
||||
runtime/tests/run-tests.sh
|
||||
```
|
||||
|
||||
The first form tests syntax, absent-hardware handling, JSON, synthetic sysfs,
|
||||
and diagnostic collection. Supplying `RKNN_SOURCE_DIR` also compiles and stages
|
||||
the pinned runtime into a temporary root filesystem.
|
||||
Executable
+209
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install the pinned RKNN C runtime, test model, and RK1 diagnostic tools.
|
||||
|
||||
set -Eeuo pipefail
|
||||
umask 022
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
# shellcheck source=rknn-version.env
|
||||
source "$SCRIPT_DIR/rknn-version.env"
|
||||
|
||||
ROOTFS="/"
|
||||
SOURCE_DIR=""
|
||||
CC_BIN=${CC:-cc}
|
||||
KEEP_WORK=0
|
||||
WORK_DIR=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: install-rknn-runtime.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
--rootfs PATH Install below PATH (default: /).
|
||||
--source-dir PATH Use an existing rknn-toolkit2 v2.3.2 checkout.
|
||||
--cc COMMAND C compiler for the target (default: $CC or cc).
|
||||
--keep-work Preserve a downloaded checkout for inspection.
|
||||
-h, --help Show this help.
|
||||
|
||||
Without --source-dir, the script fetches only the required paths from the
|
||||
immutable upstream commit. Network access and git are then required.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'install-rknn-runtime: error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
note() {
|
||||
printf 'install-rknn-runtime: %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--rootfs)
|
||||
(($# >= 2)) || die "--rootfs requires a path"
|
||||
ROOTFS=$2
|
||||
shift 2
|
||||
;;
|
||||
--source-dir)
|
||||
(($# >= 2)) || die "--source-dir requires a path"
|
||||
SOURCE_DIR=$2
|
||||
shift 2
|
||||
;;
|
||||
--cc)
|
||||
(($# >= 2)) || die "--cc requires a compiler command"
|
||||
CC_BIN=$2
|
||||
shift 2
|
||||
;;
|
||||
--keep-work)
|
||||
KEEP_WORK=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -d "$ROOTFS" ]] || die "rootfs is not a directory: $ROOTFS"
|
||||
ROOTFS=$(readlink -f -- "$ROOTFS")
|
||||
[[ -n "$ROOTFS" ]] || die "could not resolve rootfs"
|
||||
|
||||
if [[ "$ROOTFS" == "/" && ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||
die "installing into / requires root"
|
||||
fi
|
||||
|
||||
for tool in install ln readlink sha256sum "$CC_BIN"; do
|
||||
command -v "$tool" >/dev/null 2>&1 || die "required command not found: $tool"
|
||||
done
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$WORK_DIR" && -d "$WORK_DIR" && $KEEP_WORK -eq 0 ]]; then
|
||||
rm -rf -- "$WORK_DIR"
|
||||
elif [[ -n "$WORK_DIR" && -d "$WORK_DIR" ]]; then
|
||||
note "preserved work directory: $WORK_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
verify_sha256() {
|
||||
local expected=$1
|
||||
local path=$2
|
||||
local actual
|
||||
[[ -f "$path" ]] || die "required upstream asset is missing: $path"
|
||||
actual=$(sha256sum -- "$path")
|
||||
actual=${actual%% *}
|
||||
[[ "$actual" == "$expected" ]] ||
|
||||
die "checksum mismatch for $path (expected $expected, got $actual)"
|
||||
}
|
||||
|
||||
if [[ -z "$SOURCE_DIR" ]]; then
|
||||
command -v git >/dev/null 2>&1 || die "git is required without --source-dir"
|
||||
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rknn-v232.XXXXXXXX")
|
||||
SOURCE_DIR="$WORK_DIR/source"
|
||||
note "fetching RKNN Toolkit2 commit $RKNN_COMMIT"
|
||||
git init -q "$SOURCE_DIR"
|
||||
git -C "$SOURCE_DIR" remote add origin "$RKNN_REPOSITORY"
|
||||
git -C "$SOURCE_DIR" config remote.origin.promisor true
|
||||
git -C "$SOURCE_DIR" config remote.origin.partialclonefilter blob:none
|
||||
git -C "$SOURCE_DIR" sparse-checkout init --cone
|
||||
git -C "$SOURCE_DIR" sparse-checkout set \
|
||||
rknpu2/runtime/Linux/librknn_api \
|
||||
rknpu2/examples/rknn_api_demo/model
|
||||
git -C "$SOURCE_DIR" fetch -q --depth 1 --filter=blob:none origin \
|
||||
"refs/tags/$RKNN_TAG"
|
||||
git -C "$SOURCE_DIR" checkout -q --detach FETCH_HEAD
|
||||
fi
|
||||
|
||||
SOURCE_DIR=$(readlink -f -- "$SOURCE_DIR")
|
||||
[[ -d "$SOURCE_DIR" ]] || die "source directory is not a directory"
|
||||
if [[ -d "$SOURCE_DIR/.git" ]]; then
|
||||
source_commit=$(git -C "$SOURCE_DIR" rev-parse HEAD)
|
||||
[[ "$source_commit" == "$RKNN_COMMIT" ]] ||
|
||||
die "source checkout is $source_commit, expected $RKNN_COMMIT"
|
||||
fi
|
||||
|
||||
HEADER="$SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/include/rknn_api.h"
|
||||
RUNTIME_SO="$SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so"
|
||||
MODEL="$SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn"
|
||||
DEMO_IMAGE="$SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg"
|
||||
LICENSE_FILE="$SOURCE_DIR/LICENSE"
|
||||
|
||||
verify_sha256 "$RKNN_HEADER_SHA256" "$HEADER"
|
||||
verify_sha256 "$RKNN_RUNTIME_AARCH64_SHA256" "$RUNTIME_SO"
|
||||
verify_sha256 "$RKNN_MOBILENET_RK3588_SHA256" "$MODEL"
|
||||
verify_sha256 "$RKNN_DEMO_IMAGE_SHA256" "$DEMO_IMAGE"
|
||||
verify_sha256 "$RKNN_LICENSE_SHA256" "$LICENSE_FILE"
|
||||
|
||||
if [[ -z "$WORK_DIR" ]]; then
|
||||
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rknn-v232-build.XXXXXXXX")
|
||||
fi
|
||||
TEST_BINARY="$WORK_DIR/rknn-inference-test"
|
||||
|
||||
note "building the ARM64 RKNN smoke test with $CC_BIN"
|
||||
"$CC_BIN" \
|
||||
-std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra -Wpedantic \
|
||||
-fstack-protector-strong -D_FORTIFY_SOURCE=2 \
|
||||
-I"$(dirname -- "$HEADER")" \
|
||||
"$SCRIPT_DIR/rknn-inference-test.c" \
|
||||
-L"$(dirname -- "$RUNTIME_SO")" -Wl,--as-needed \
|
||||
-Wl,-z,relro,-z,now -Wl,-rpath,/opt/rknn/current/lib \
|
||||
-lrknnrt -lm -o "$TEST_BINARY"
|
||||
|
||||
VERSION_ROOT="$ROOTFS/opt/rknn/$RKNN_VERSION"
|
||||
install -d -m 0755 \
|
||||
"$VERSION_ROOT/bin" \
|
||||
"$VERSION_ROOT/include" \
|
||||
"$VERSION_ROOT/lib" \
|
||||
"$VERSION_ROOT/share/models/rk3588" \
|
||||
"$VERSION_ROOT/share/demo" \
|
||||
"$VERSION_ROOT/share/licenses" \
|
||||
"$ROOTFS/etc/ld.so.conf.d" \
|
||||
"$ROOTFS/usr/local/bin" \
|
||||
"$ROOTFS/usr/local/sbin"
|
||||
|
||||
install -m 0755 "$TEST_BINARY" "$VERSION_ROOT/bin/rknn-inference-test"
|
||||
install -m 0644 "$HEADER" "$VERSION_ROOT/include/rknn_api.h"
|
||||
install -m 0644 "$RUNTIME_SO" "$VERSION_ROOT/lib/librknnrt.so"
|
||||
ln -sfn librknnrt.so "$VERSION_ROOT/lib/librknn_api.so"
|
||||
install -m 0644 "$MODEL" \
|
||||
"$VERSION_ROOT/share/models/rk3588/mobilenet_v1.rknn"
|
||||
install -m 0644 "$DEMO_IMAGE" "$VERSION_ROOT/share/demo/dog_224x224.jpg"
|
||||
install -m 0644 "$LICENSE_FILE" \
|
||||
"$VERSION_ROOT/share/licenses/rknn-toolkit2-LICENSE"
|
||||
|
||||
cat >"$VERSION_ROOT/manifest.env" <<EOF
|
||||
RKNN_VERSION=$RKNN_VERSION
|
||||
RKNN_TAG=$RKNN_TAG
|
||||
RKNN_COMMIT=$RKNN_COMMIT
|
||||
RKNN_REPOSITORY=$RKNN_REPOSITORY
|
||||
RKNN_RUNTIME_AARCH64_SHA256=$RKNN_RUNTIME_AARCH64_SHA256
|
||||
RKNN_MOBILENET_RK3588_SHA256=$RKNN_MOBILENET_RK3588_SHA256
|
||||
RKNN_DEMO_IMAGE_SHA256=$RKNN_DEMO_IMAGE_SHA256
|
||||
EOF
|
||||
chmod 0644 "$VERSION_ROOT/manifest.env"
|
||||
|
||||
ln -sfn "$RKNN_VERSION" "$ROOTFS/opt/rknn/current"
|
||||
ln -sfn /opt/rknn/current/bin/rknn-inference-test \
|
||||
"$ROOTFS/usr/local/bin/rknn-inference-test"
|
||||
printf '%s\n' '/opt/rknn/current/lib' >"$ROOTFS/etc/ld.so.conf.d/rknn.conf"
|
||||
chmod 0644 "$ROOTFS/etc/ld.so.conf.d/rknn.conf"
|
||||
|
||||
install -m 0755 "$SCRIPT_DIR/rk1-media-selftest" \
|
||||
"$ROOTFS/usr/local/bin/rk1-media-selftest"
|
||||
install -m 0755 "$SCRIPT_DIR/rk1-media-diagnostics" \
|
||||
"$ROOTFS/usr/local/sbin/rk1-media-diagnostics"
|
||||
|
||||
if command -v ldconfig >/dev/null 2>&1 &&
|
||||
[[ -e "$ROOTFS/etc/ld.so.conf" && -d "$ROOTFS/lib" ]]; then
|
||||
if ! ldconfig -r "$ROOTFS"; then
|
||||
note "warning: ldconfig failed; the smoke test still has an embedded rpath"
|
||||
fi
|
||||
fi
|
||||
|
||||
note "installed RKNN Runtime $RKNN_VERSION below $ROOTFS/opt/rknn/current"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Extract only the immutable RKNN assets needed by install-rknn-runtime.sh.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
# shellcheck source=rknn-version.env
|
||||
source "$SCRIPT_DIR/rknn-version.env"
|
||||
|
||||
[[ $# -eq 2 ]] || {
|
||||
echo "Usage: $0 PINNED-RKNN-CHECKOUT OUTPUT-DIRECTORY" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
SOURCE_DIR=$(readlink -f -- "$1")
|
||||
OUTPUT_DIR=$2
|
||||
[[ -d "$SOURCE_DIR/.git" ]] || {
|
||||
echo "Source must be a Git checkout: $SOURCE_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$(git -C "$SOURCE_DIR" rev-parse HEAD)" == "$RKNN_COMMIT" ]] || {
|
||||
echo "RKNN checkout is not pinned commit $RKNN_COMMIT" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ ! -e "$OUTPUT_DIR" ]] || {
|
||||
echo "Refusing to replace existing output: $OUTPUT_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
declare -a assets=(
|
||||
"rknpu2/runtime/Linux/librknn_api/include/rknn_api.h:$RKNN_HEADER_SHA256"
|
||||
"rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so:$RKNN_RUNTIME_AARCH64_SHA256"
|
||||
"rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn:$RKNN_MOBILENET_RK3588_SHA256"
|
||||
"rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg:$RKNN_DEMO_IMAGE_SHA256"
|
||||
"LICENSE:$RKNN_LICENSE_SHA256"
|
||||
)
|
||||
|
||||
for entry in "${assets[@]}"; do
|
||||
relative_path=${entry%%:*}
|
||||
expected_sha=${entry##*:}
|
||||
actual_sha=$(sha256sum "$SOURCE_DIR/$relative_path" | awk '{print $1}')
|
||||
[[ "$actual_sha" == "$expected_sha" ]] || {
|
||||
echo "Checksum mismatch: $relative_path" >&2
|
||||
exit 1
|
||||
}
|
||||
install -D -m 0644 "$SOURCE_DIR/$relative_path" "$OUTPUT_DIR/$relative_path"
|
||||
done
|
||||
|
||||
cat >"$OUTPUT_DIR/SOURCE.env" <<EOF
|
||||
RKNN_VERSION=$RKNN_VERSION
|
||||
RKNN_TAG=$RKNN_TAG
|
||||
RKNN_COMMIT=$RKNN_COMMIT
|
||||
RKNN_REPOSITORY=$RKNN_REPOSITORY
|
||||
EOF
|
||||
chmod 0644 "$OUTPUT_DIR/SOURCE.env"
|
||||
|
||||
echo "Created verified offline RKNN bundle: $OUTPUT_DIR"
|
||||
Executable
+437
@@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env bash
|
||||
# Collect a privacy-conscious, read-only RK1 media diagnostic bundle.
|
||||
|
||||
set -Eeuo pipefail
|
||||
export LC_ALL=C
|
||||
umask 077
|
||||
|
||||
OUTPUT=""
|
||||
DIRECTORY_OUTPUT=0
|
||||
ACTIVE_TESTS=0
|
||||
MEDIA_DIR=""
|
||||
TEMP_DIR=""
|
||||
|
||||
SYS_ROOT=${RK1_SYSFS_ROOT:-/sys}
|
||||
PROC_ROOT=${RK1_PROCFS_ROOT:-/proc}
|
||||
DEV_ROOT=${RK1_DEV_ROOT:-/dev}
|
||||
RKNN_HOME=${RKNN_HOME:-/opt/rknn/current}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: rk1-media-diagnostics [OPTIONS]
|
||||
|
||||
Options:
|
||||
--output PATH Output archive or directory path.
|
||||
--directory Write an unpacked directory instead of a .tar.gz.
|
||||
--include-active-tests Include short Vulkan, encode, and NPU workloads.
|
||||
--media-dir PATH Pass media samples to active decode/RGA tests.
|
||||
-h, --help Show this help.
|
||||
|
||||
The bundle is local only. It deliberately excludes environment variables,
|
||||
home-directory contents, SSH material, disk serials/UUIDs, MAC addresses,
|
||||
IP addresses, raw EDID, and unfiltered system journals. Review it before
|
||||
sharing it with anyone.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'rk1-media-diagnostics: %s\n' "$*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--output)
|
||||
(($# >= 2)) || die "--output requires a path"
|
||||
OUTPUT=$2
|
||||
shift 2
|
||||
;;
|
||||
--directory)
|
||||
DIRECTORY_OUTPUT=1
|
||||
shift
|
||||
;;
|
||||
--include-active-tests)
|
||||
ACTIVE_TESTS=1
|
||||
shift
|
||||
;;
|
||||
--media-dir)
|
||||
(($# >= 2)) || die "--media-dir requires a path"
|
||||
MEDIA_DIR=$2
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$MEDIA_DIR" && ! -d "$MEDIA_DIR" ]]; then
|
||||
die "media directory is not a directory: $MEDIA_DIR"
|
||||
fi
|
||||
|
||||
safe_host=$(hostname 2>/dev/null || printf rk1)
|
||||
safe_host=${safe_host//[^A-Za-z0-9._-]/_}
|
||||
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
if [[ -z "$OUTPUT" ]]; then
|
||||
if ((DIRECTORY_OUTPUT)); then
|
||||
OUTPUT="$PWD/rk1-media-diagnostics-${safe_host}-${timestamp}"
|
||||
else
|
||||
OUTPUT="$PWD/rk1-media-diagnostics-${safe_host}-${timestamp}.tar.gz"
|
||||
fi
|
||||
fi
|
||||
|
||||
output_parent=$(dirname -- "$OUTPUT")
|
||||
[[ -d "$output_parent" ]] || die "output parent does not exist: $output_parent"
|
||||
output_parent=$(cd -- "$output_parent" && pwd -P)
|
||||
OUTPUT="$output_parent/$(basename -- "$OUTPUT")"
|
||||
[[ ! -e "$OUTPUT" && ! -L "$OUTPUT" ]] || die "refusing to overwrite $OUTPUT"
|
||||
|
||||
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rk1-media-diagnostics.XXXXXXXX")
|
||||
REPORT="$TEMP_DIR/report"
|
||||
mkdir -p "$REPORT"
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
|
||||
rm -rf -- "$TEMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
sanitize_stream() {
|
||||
# Redact common MAC, IPv4, root-device, and static-IP command-line forms.
|
||||
sed -E \
|
||||
-e 's/([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}/<mac-redacted>/g' \
|
||||
-e 's/([[:space:]=]|^)([0-9]{1,3}\.){3}[0-9]{1,3}([[:space:]\/:]|$)/\1<ip-redacted>\3/g' \
|
||||
-e 's/(root=)[^[:space:]]+/\1<root-device-redacted>/g' \
|
||||
-e 's/(ip=)[^[:space:]]+/\1<ip-config-redacted>/g' \
|
||||
-e 's/(UUID|PARTUUID)=[A-Za-z0-9-]+/\1=<redacted>/g' \
|
||||
-e 's/([Ss]erial([ _-]?[Nn]umber)?[=:][[:space:]]*)[^[:space:],;]+/\1<redacted>/g'
|
||||
}
|
||||
|
||||
run_capture() {
|
||||
local destination=$1
|
||||
shift
|
||||
{
|
||||
printf '$'
|
||||
printf ' %q' "$@"
|
||||
printf '\n'
|
||||
if command -v "$1" >/dev/null 2>&1; then
|
||||
timeout 45s "$@" 2>&1 ||
|
||||
printf '[command exited %d]\n' "$?"
|
||||
else
|
||||
printf '[command unavailable: %s]\n' "$1"
|
||||
fi
|
||||
} | sanitize_stream >"$REPORT/$destination"
|
||||
}
|
||||
|
||||
append_command() {
|
||||
local destination=$1
|
||||
shift
|
||||
{
|
||||
printf '\n$'
|
||||
printf ' %q' "$@"
|
||||
printf '\n'
|
||||
if command -v "$1" >/dev/null 2>&1; then
|
||||
timeout 45s "$@" 2>&1 ||
|
||||
printf '[command exited %d]\n' "$?"
|
||||
else
|
||||
printf '[command unavailable: %s]\n' "$1"
|
||||
fi
|
||||
} | sanitize_stream >>"$REPORT/$destination"
|
||||
}
|
||||
|
||||
cat >"$REPORT/README.txt" <<'EOF'
|
||||
RK1 media diagnostic bundle
|
||||
|
||||
This is a read-only snapshot for diagnosing HDMI, GPU, RKMPP VPU, RGA,
|
||||
RKNPU, ALSA, Ethernet, and storage enumeration. Commands that were missing or
|
||||
permission-denied are recorded rather than treated as collector failures.
|
||||
|
||||
The collector attempts to redact IP and MAC addresses and omits raw EDID,
|
||||
disk serials/UUIDs, environment variables, home directories, SSH material,
|
||||
and unfiltered journals. Review every file before sharing the bundle.
|
||||
EOF
|
||||
|
||||
{
|
||||
printf 'collected_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
printf 'collector_version=1\n'
|
||||
printf 'active_tests=%s\n' "$ACTIVE_TESTS"
|
||||
printf 'kernel='
|
||||
uname -srvm 2>/dev/null || true
|
||||
printf 'architecture='
|
||||
uname -m 2>/dev/null || true
|
||||
if [[ -r /etc/os-release ]]; then
|
||||
printf '\n[os-release]\n'
|
||||
grep -E '^(NAME|VERSION|VERSION_ID|ID|ID_LIKE)=' /etc/os-release || true
|
||||
fi
|
||||
if [[ -r "$PROC_ROOT/device-tree/model" ]]; then
|
||||
printf '\nboard_model='
|
||||
tr -d '\000' <"$PROC_ROOT/device-tree/model" || true
|
||||
printf '\n'
|
||||
fi
|
||||
if [[ -r "$PROC_ROOT/cmdline" ]]; then
|
||||
printf '\n[kernel-command-line-redacted]\n'
|
||||
sanitize_stream <"$PROC_ROOT/cmdline"
|
||||
printf '\n'
|
||||
fi
|
||||
} >"$REPORT/system.txt"
|
||||
|
||||
SELFTEST=""
|
||||
if command -v rk1-media-selftest >/dev/null 2>&1; then
|
||||
SELFTEST=$(command -v rk1-media-selftest)
|
||||
elif [[ -x "$(dirname -- "${BASH_SOURCE[0]}")/rk1-media-selftest" ]]; then
|
||||
SELFTEST="$(dirname -- "${BASH_SOURCE[0]}")/rk1-media-selftest"
|
||||
fi
|
||||
|
||||
if [[ -n "$SELFTEST" ]]; then
|
||||
selftest_args=(--json)
|
||||
if ((ACTIVE_TESTS == 0)); then
|
||||
selftest_args+=(--quick)
|
||||
fi
|
||||
if [[ -n "$MEDIA_DIR" ]]; then
|
||||
selftest_args+=(--media-dir "$MEDIA_DIR")
|
||||
fi
|
||||
if ! timeout 600s "$SELFTEST" "${selftest_args[@]}" \
|
||||
>"$REPORT/selftest.json" 2>"$REPORT/selftest.stderr"; then
|
||||
printf 'self-test returned a nonzero status; inspect its JSON and stderr\n' \
|
||||
>"$REPORT/selftest-status.txt"
|
||||
fi
|
||||
else
|
||||
printf '{"error":"rk1-media-selftest is unavailable"}\n' \
|
||||
>"$REPORT/selftest.json"
|
||||
fi
|
||||
|
||||
# DRM connector details. EDID is represented by size, hash, and decoded text,
|
||||
# never by the raw binary blob.
|
||||
{
|
||||
shopt -s nullglob
|
||||
connectors=("$SYS_ROOT"/class/drm/card*-*)
|
||||
shopt -u nullglob
|
||||
if ((${#connectors[@]} == 0)); then
|
||||
printf 'No DRM connectors found.\n'
|
||||
fi
|
||||
for connector in "${connectors[@]}"; do
|
||||
[[ -d "$connector" ]] || continue
|
||||
printf '\n[%s]\n' "${connector##*/}"
|
||||
for attribute in status enabled dpms link_status; do
|
||||
if [[ -r "$connector/$attribute" ]]; then
|
||||
printf '%s=' "$attribute"
|
||||
tr -d '\000' <"$connector/$attribute" 2>/dev/null || true
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
if [[ -r "$connector/modes" ]]; then
|
||||
printf 'modes:\n'
|
||||
sed 's/^/ /' "$connector/modes" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -s "$connector/edid" ]]; then
|
||||
printf 'edid_size=%s\n' "$(wc -c <"$connector/edid")"
|
||||
printf 'edid_sha256=%s\n' \
|
||||
"$(sha256sum "$connector/edid" | awk '{print $1}')"
|
||||
if command -v edid-decode >/dev/null 2>&1; then
|
||||
printf 'decoded_edid:\n'
|
||||
timeout 15s edid-decode "$connector/edid" 2>&1 |
|
||||
sed 's/^/ /' || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
} | sanitize_stream >"$REPORT/drm-connectors.txt"
|
||||
|
||||
run_capture drm-modetest.txt modetest -c -p
|
||||
run_capture kernel-modules.txt lsmod
|
||||
|
||||
{
|
||||
printf '[platform GPU driver bindings]\n'
|
||||
for driver in panthor panfrost mali; do
|
||||
directory="$SYS_ROOT/bus/platform/drivers/$driver"
|
||||
[[ -d "$directory" ]] || continue
|
||||
printf '%s:\n' "$driver"
|
||||
find "$directory" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) \
|
||||
-printf ' %f\n' 2>/dev/null | sort
|
||||
done
|
||||
printf '\n[GPU devfreq]\n'
|
||||
shopt -s nullglob
|
||||
gpu_nodes=("$SYS_ROOT"/class/devfreq/*.gpu)
|
||||
shopt -u nullglob
|
||||
for node in "${gpu_nodes[@]}"; do
|
||||
printf '%s\n' "${node##*/}"
|
||||
for attribute in cur_freq min_freq max_freq governor available_frequencies; do
|
||||
if [[ -r "$node/$attribute" ]]; then
|
||||
printf ' %s=' "$attribute"
|
||||
tr -d '\000' <"$node/$attribute" 2>/dev/null || true
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
done
|
||||
} >"$REPORT/gpu-sysfs.txt"
|
||||
run_capture vulkan.txt vulkaninfo --summary
|
||||
append_command vulkan.txt eglinfo -B
|
||||
|
||||
{
|
||||
printf '[media and accelerator platform bindings]\n'
|
||||
for pattern in 'mpp*' 'rga*' 'rockchip-rga' 'RKNPU' 'rknpu'; do
|
||||
shopt -s nullglob
|
||||
directories=("$SYS_ROOT"/bus/platform/drivers/$pattern)
|
||||
shopt -u nullglob
|
||||
for directory in "${directories[@]}"; do
|
||||
[[ -d "$directory" ]] || continue
|
||||
printf '\n%s:\n' "${directory##*/}"
|
||||
find "$directory" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) \
|
||||
-printf ' %f\n' 2>/dev/null | sort
|
||||
done
|
||||
done
|
||||
printf '\n[device nodes]\n'
|
||||
shopt -s nullglob
|
||||
nodes=("$DEV_ROOT"/dri/* "$DEV_ROOT"/mpp_service \
|
||||
"$DEV_ROOT"/rga "$DEV_ROOT"/rknpu* "$DEV_ROOT"/mali*)
|
||||
shopt -u nullglob
|
||||
for node in "${nodes[@]}"; do
|
||||
stat -c '%A %U:%G %t:%T %n' "$node" 2>/dev/null || true
|
||||
done
|
||||
} >"$REPORT/accelerators.txt"
|
||||
|
||||
FFMPEG=""
|
||||
for candidate in /opt/rkmedia/bin/ffmpeg-rk /usr/local/bin/ffmpeg-rk; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
FFMPEG=$candidate
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$FFMPEG" ]] && command -v ffmpeg-rk >/dev/null 2>&1; then
|
||||
FFMPEG=$(command -v ffmpeg-rk)
|
||||
fi
|
||||
if [[ -n "$FFMPEG" ]]; then
|
||||
run_capture ffmpeg-rk.txt "$FFMPEG" -hide_banner -version
|
||||
append_command ffmpeg-rk.txt "$FFMPEG" -hide_banner -hwaccels
|
||||
{
|
||||
printf '\n[Rockchip encoders, decoders, and filters]\n'
|
||||
"$FFMPEG" -hide_banner -decoders 2>/dev/null |
|
||||
grep -Ei 'h264|hevc|vp9|av1|rkmpp' || true
|
||||
"$FFMPEG" -hide_banner -encoders 2>/dev/null |
|
||||
grep -Ei 'h264|hevc|mjpeg|rkmpp' || true
|
||||
"$FFMPEG" -hide_banner -filters 2>/dev/null |
|
||||
grep -Ei 'rkrga|drm' || true
|
||||
} >>"$REPORT/ffmpeg-rk.txt"
|
||||
else
|
||||
printf 'ffmpeg-rk is unavailable\n' >"$REPORT/ffmpeg-rk.txt"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '[RKNN installation]\n'
|
||||
if [[ -r "$RKNN_HOME/manifest.env" ]]; then
|
||||
sed -E 's#(REPOSITORY=).*#\1<upstream-url>#' "$RKNN_HOME/manifest.env"
|
||||
else
|
||||
printf 'manifest unavailable at %s\n' "$RKNN_HOME/manifest.env"
|
||||
fi
|
||||
for asset in \
|
||||
"$RKNN_HOME/lib/librknnrt.so" \
|
||||
"$RKNN_HOME/share/models/rk3588/mobilenet_v1.rknn" \
|
||||
"$RKNN_HOME/bin/rknn-inference-test"; do
|
||||
if [[ -r "$asset" ]]; then
|
||||
sha256sum "$asset"
|
||||
else
|
||||
printf 'missing: %s\n' "$asset"
|
||||
fi
|
||||
done
|
||||
printf '\n[NPU devfreq]\n'
|
||||
shopt -s nullglob
|
||||
npu_nodes=("$SYS_ROOT"/class/devfreq/*.npu)
|
||||
shopt -u nullglob
|
||||
for node in "${npu_nodes[@]}"; do
|
||||
printf '%s\n' "${node##*/}"
|
||||
for attribute in cur_freq min_freq max_freq governor available_frequencies; do
|
||||
if [[ -r "$node/$attribute" ]]; then
|
||||
printf ' %s=' "$attribute"
|
||||
tr -d '\000' <"$node/$attribute" 2>/dev/null || true
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
done
|
||||
} >"$REPORT/npu.txt"
|
||||
|
||||
{
|
||||
printf '[ALSA cards]\n'
|
||||
if [[ -r "$PROC_ROOT/asound/cards" ]]; then
|
||||
cat "$PROC_ROOT/asound/cards"
|
||||
else
|
||||
printf 'unavailable\n'
|
||||
fi
|
||||
} >"$REPORT/audio.txt"
|
||||
append_command audio.txt aplay -l
|
||||
append_command audio.txt aplay -L
|
||||
|
||||
{
|
||||
printf 'Interface state only; addresses and MACs are intentionally omitted.\n\n'
|
||||
shopt -s nullglob
|
||||
interfaces=("$SYS_ROOT"/class/net/*)
|
||||
shopt -u nullglob
|
||||
for interface in "${interfaces[@]}"; do
|
||||
name=${interface##*/}
|
||||
[[ "$name" == lo ]] && continue
|
||||
printf '[%s]\n' "$name"
|
||||
for attribute in operstate carrier speed duplex mtu; do
|
||||
if [[ -r "$interface/$attribute" ]]; then
|
||||
printf '%s=' "$attribute"
|
||||
tr -d '\000' <"$interface/$attribute" 2>/dev/null || true
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
printf '\n'
|
||||
done
|
||||
} >"$REPORT/network.txt"
|
||||
|
||||
run_capture storage.txt lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,TRAN
|
||||
{
|
||||
printf '\n[root filesystem]\n'
|
||||
if command -v findmnt >/dev/null 2>&1; then
|
||||
findmnt -n -o SOURCE,FSTYPE / 2>&1 | sanitize_stream
|
||||
else
|
||||
printf 'findmnt unavailable\n'
|
||||
fi
|
||||
} >>"$REPORT/storage.txt"
|
||||
run_capture usb.txt lsusb -t
|
||||
append_command usb.txt lspci -nnk
|
||||
|
||||
{
|
||||
if command -v dpkg-query >/dev/null 2>&1; then
|
||||
dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null |
|
||||
grep -Ei 'armbian|linux-image|linux-dtb|mesa|vulkan|kodi|ffmpeg|mpp|rga|rknn|alsa|libcec' |
|
||||
sort || true
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
rpm -qa 2>/dev/null |
|
||||
grep -Ei 'kernel|mesa|vulkan|kodi|ffmpeg|mpp|rga|rknn|alsa|libcec' |
|
||||
sort || true
|
||||
else
|
||||
printf 'supported package inventory tool unavailable\n'
|
||||
fi
|
||||
} >"$REPORT/packages.txt"
|
||||
|
||||
kernel_pattern='drm|hdmi|vop|edid|panthor|panfrost|mali|rknpu|npu|rkvdec|rkvenc|av1|mpp|rga|alsa|snd|cec|pcie|nvme|mmc'
|
||||
{
|
||||
printf '[filtered current-boot kernel messages]\n'
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
timeout 45s journalctl -b -k --no-pager 2>&1 |
|
||||
grep -Ei "$kernel_pattern" | tail -2500 || true
|
||||
elif command -v dmesg >/dev/null 2>&1; then
|
||||
dmesg --color=never 2>&1 |
|
||||
grep -Ei "$kernel_pattern" | tail -2500 || true
|
||||
else
|
||||
printf 'kernel log reader unavailable\n'
|
||||
fi
|
||||
} | sanitize_stream >"$REPORT/kernel-media.log"
|
||||
|
||||
if ((DIRECTORY_OUTPUT)); then
|
||||
mkdir -- "$OUTPUT"
|
||||
cp -a "$REPORT/." "$OUTPUT/"
|
||||
chmod -R go-rwx "$OUTPUT"
|
||||
else
|
||||
command -v tar >/dev/null 2>&1 || die "tar is required for archive output"
|
||||
tar -C "$REPORT" -czf "$OUTPUT" .
|
||||
chmod 0600 "$OUTPUT"
|
||||
fi
|
||||
|
||||
printf 'Diagnostic bundle written to %s\n' "$OUTPUT"
|
||||
Executable
+574
@@ -0,0 +1,574 @@
|
||||
#!/usr/bin/env bash
|
||||
# Read-only RK3588 media hardware inventory with optional short workloads.
|
||||
|
||||
set -uo pipefail
|
||||
export LC_ALL=C
|
||||
|
||||
JSON=0
|
||||
QUICK=0
|
||||
STRICT=0
|
||||
MEDIA_DIR=""
|
||||
|
||||
SYS_ROOT=${RK1_SYSFS_ROOT:-/sys}
|
||||
PROC_ROOT=${RK1_PROCFS_ROOT:-/proc}
|
||||
DEV_ROOT=${RK1_DEV_ROOT:-/dev}
|
||||
RKNN_HOME=${RKNN_HOME:-/opt/rknn/current}
|
||||
|
||||
declare -a CHECK_GROUP=()
|
||||
declare -a CHECK_NAME=()
|
||||
declare -a CHECK_STATUS=()
|
||||
declare -a CHECK_MESSAGE=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: rk1-media-selftest [OPTIONS]
|
||||
|
||||
Options:
|
||||
--json Emit one JSON document instead of readable text.
|
||||
--quick Inventory only; skip GPU, codec, and NPU workloads.
|
||||
--strict Return nonzero for warnings as well as failures.
|
||||
--media-dir PATH Exercise RKMPP decoding with named sample files.
|
||||
Recognized names contain h264, hevc, vp9, or av1.
|
||||
-h, --help Show this help.
|
||||
|
||||
The default run is non-destructive. It executes only short inference, Vulkan,
|
||||
and encoder probes when the relevant runtime and devices are present. It never
|
||||
changes clocks, governors, display modes, storage, or network configuration.
|
||||
EOF
|
||||
}
|
||||
|
||||
invocation_error() {
|
||||
printf 'rk1-media-selftest: %s\n' "$*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--json)
|
||||
JSON=1
|
||||
shift
|
||||
;;
|
||||
--quick)
|
||||
QUICK=1
|
||||
shift
|
||||
;;
|
||||
--strict)
|
||||
STRICT=1
|
||||
shift
|
||||
;;
|
||||
--media-dir)
|
||||
(($# >= 2)) || invocation_error "--media-dir requires a path"
|
||||
MEDIA_DIR=$2
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
invocation_error "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$MEDIA_DIR" && ! -d "$MEDIA_DIR" ]]; then
|
||||
invocation_error "media directory is not a directory: $MEDIA_DIR"
|
||||
fi
|
||||
|
||||
add_check() {
|
||||
CHECK_GROUP+=("$1")
|
||||
CHECK_NAME+=("$2")
|
||||
CHECK_STATUS+=("$3")
|
||||
CHECK_MESSAGE+=("$4")
|
||||
}
|
||||
|
||||
read_text() {
|
||||
local path=$1
|
||||
if [[ -r "$path" ]]; then
|
||||
tr -d '\000' <"$path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
short_message() {
|
||||
local value=$1
|
||||
value=${value//$'\r'/}
|
||||
value=${value//$'\n'/'; '}
|
||||
value=${value//$'\t'/ }
|
||||
printf '%s' "${value:0:320}"
|
||||
}
|
||||
|
||||
driver_binding_count() {
|
||||
local pattern=$1
|
||||
local directory entry base
|
||||
local count=0
|
||||
shopt -s nullglob
|
||||
for directory in "$SYS_ROOT"/bus/platform/drivers/$pattern; do
|
||||
[[ -d "$directory" ]] || continue
|
||||
for entry in "$directory"/*; do
|
||||
base=${entry##*/}
|
||||
case "$base" in
|
||||
bind|unbind|uevent|module|new_id|remove_id) continue ;;
|
||||
esac
|
||||
if [[ -L "$entry" || -d "$entry" ]]; then
|
||||
((count++))
|
||||
fi
|
||||
done
|
||||
done
|
||||
shopt -u nullglob
|
||||
printf '%d' "$count"
|
||||
}
|
||||
|
||||
find_sample() {
|
||||
local token=$1
|
||||
[[ -n "$MEDIA_DIR" ]] || return 1
|
||||
find "$MEDIA_DIR" -maxdepth 1 -type f \
|
||||
\( -iname "*${token}*.mkv" -o -iname "*${token}*.mp4" \
|
||||
-o -iname "*${token}*.webm" -o -iname "*${token}*.ts" \) \
|
||||
-print -quit 2>/dev/null
|
||||
}
|
||||
|
||||
# System identity
|
||||
machine=$(uname -m 2>/dev/null || printf unknown)
|
||||
if [[ "$machine" == "aarch64" ]]; then
|
||||
add_check system architecture pass "aarch64 userspace"
|
||||
else
|
||||
add_check system architecture fail "expected aarch64, found $machine"
|
||||
fi
|
||||
|
||||
model=$(read_text "$PROC_ROOT/device-tree/model")
|
||||
if [[ "$model" == *"Turing"*"RK1"* ]]; then
|
||||
add_check system board pass "$model"
|
||||
elif [[ -n "$model" ]]; then
|
||||
add_check system board warn "device-tree model is $model"
|
||||
else
|
||||
add_check system board warn "device-tree model is unavailable"
|
||||
fi
|
||||
|
||||
# HDMI / DRM connector state
|
||||
shopt -s nullglob
|
||||
hdmi_connectors=("$SYS_ROOT"/class/drm/card*-HDMI-A-*)
|
||||
shopt -u nullglob
|
||||
if ((${#hdmi_connectors[@]} == 0)); then
|
||||
add_check hdmi connector warn "no DRM HDMI connector is registered"
|
||||
else
|
||||
connected_count=0
|
||||
for connector in "${hdmi_connectors[@]}"; do
|
||||
connector_name=${connector##*/}
|
||||
state=$(read_text "$connector/status")
|
||||
case "$state" in
|
||||
connected)
|
||||
((connected_count++))
|
||||
add_check hdmi "$connector_name" pass "connected"
|
||||
if [[ -s "$connector/edid" ]]; then
|
||||
edid_bytes=$(wc -c <"$connector/edid" 2>/dev/null || printf 0)
|
||||
add_check hdmi "$connector_name-edid" pass \
|
||||
"$edid_bytes bytes of EDID data"
|
||||
else
|
||||
add_check hdmi "$connector_name-edid" warn \
|
||||
"connected but EDID is empty or unreadable"
|
||||
fi
|
||||
if [[ -r "$connector/modes" ]] &&
|
||||
grep -qx '3840x2160' "$connector/modes" 2>/dev/null; then
|
||||
add_check hdmi "$connector_name-4k" pass \
|
||||
"EDID advertises a 3840x2160 mode"
|
||||
else
|
||||
add_check hdmi "$connector_name-4k" warn \
|
||||
"3840x2160 is not present in the connector mode list"
|
||||
fi
|
||||
;;
|
||||
disconnected)
|
||||
add_check hdmi "$connector_name" warn \
|
||||
"disconnected (expected when no display is attached)"
|
||||
;;
|
||||
*)
|
||||
add_check hdmi "$connector_name" warn \
|
||||
"connector state is ${state:-unknown}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
hdmi_driver_count=$(driver_binding_count 'dwhdmi*')
|
||||
hdmi_phy_count=$(driver_binding_count '*hdptx*hdmi*')
|
||||
if ((hdmi_driver_count > 0 && hdmi_phy_count > 0)); then
|
||||
add_check hdmi drivers pass \
|
||||
"$hdmi_driver_count controller and $hdmi_phy_count HDMI PHY binding(s)"
|
||||
else
|
||||
add_check hdmi drivers warn \
|
||||
"HDMI controller bindings=$hdmi_driver_count, PHY bindings=$hdmi_phy_count"
|
||||
fi
|
||||
|
||||
# GPU / Vulkan
|
||||
gpu_driver=""
|
||||
for candidate in panthor panfrost mali; do
|
||||
if (($(driver_binding_count "$candidate") > 0)); then
|
||||
gpu_driver=$candidate
|
||||
break
|
||||
fi
|
||||
done
|
||||
case "$gpu_driver" in
|
||||
panthor)
|
||||
add_check gpu kernel-driver pass "Panthor is bound to the GPU"
|
||||
;;
|
||||
panfrost)
|
||||
add_check gpu kernel-driver warn "Panfrost is bound; the image expects Panthor"
|
||||
;;
|
||||
mali)
|
||||
add_check gpu kernel-driver warn "proprietary Mali kernel driver is bound"
|
||||
;;
|
||||
*)
|
||||
add_check gpu kernel-driver warn "no bound Mali GPU driver was found"
|
||||
;;
|
||||
esac
|
||||
|
||||
shopt -s nullglob
|
||||
gpu_devfreq=("$SYS_ROOT"/class/devfreq/*.gpu)
|
||||
render_nodes=("$DEV_ROOT"/dri/renderD*)
|
||||
shopt -u nullglob
|
||||
if ((${#gpu_devfreq[@]} > 0)); then
|
||||
add_check gpu devfreq pass "${#gpu_devfreq[@]} GPU devfreq device(s)"
|
||||
else
|
||||
add_check gpu devfreq warn "GPU devfreq node is missing"
|
||||
fi
|
||||
if ((${#render_nodes[@]} > 0)); then
|
||||
add_check gpu render-node pass "${#render_nodes[@]} DRM render node(s)"
|
||||
else
|
||||
add_check gpu render-node warn "no accessible DRM render node"
|
||||
fi
|
||||
|
||||
if ((QUICK)); then
|
||||
add_check gpu vulkan skip "active Vulkan probe disabled by --quick"
|
||||
elif command -v vulkaninfo >/dev/null 2>&1; then
|
||||
vulkan_output=$(timeout 20s vulkaninfo --summary 2>&1)
|
||||
vulkan_rc=$?
|
||||
if ((vulkan_rc == 0)); then
|
||||
gpu_name=$(printf '%s\n' "$vulkan_output" |
|
||||
sed -n 's/^[[:space:]]*deviceName[[:space:]]*=[[:space:]]*//p' |
|
||||
head -1)
|
||||
add_check gpu vulkan pass "${gpu_name:-vulkaninfo completed}"
|
||||
else
|
||||
add_check gpu vulkan fail \
|
||||
"vulkaninfo failed: $(short_message "$vulkan_output")"
|
||||
fi
|
||||
else
|
||||
add_check gpu vulkan skip "vulkaninfo is not installed"
|
||||
fi
|
||||
|
||||
# VPU bindings and FFmpeg integration
|
||||
decoder_bindings=$(driver_binding_count 'mpp_rkvdec*')
|
||||
encoder_bindings=$(driver_binding_count 'mpp_rkvenc*')
|
||||
av1_bindings=$(driver_binding_count '*av1*')
|
||||
if ((decoder_bindings > 0)); then
|
||||
add_check vpu decoder-driver pass "$decoder_bindings RKMPP decoder binding(s)"
|
||||
else
|
||||
add_check vpu decoder-driver warn "RKMPP decoder bindings are missing"
|
||||
fi
|
||||
if ((encoder_bindings > 0)); then
|
||||
add_check vpu encoder-driver pass "$encoder_bindings RKMPP encoder binding(s)"
|
||||
else
|
||||
add_check vpu encoder-driver warn "RKMPP encoder bindings are missing"
|
||||
fi
|
||||
if ((av1_bindings > 0)); then
|
||||
add_check vpu av1-driver pass "$av1_bindings AV1 decoder binding(s)"
|
||||
else
|
||||
add_check vpu av1-driver warn "a separate AV1 driver binding was not found"
|
||||
fi
|
||||
|
||||
FFMPEG=""
|
||||
for candidate in /opt/rkmedia/bin/ffmpeg-rk /usr/local/bin/ffmpeg-rk; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
FFMPEG=$candidate
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$FFMPEG" ]] && command -v ffmpeg-rk >/dev/null 2>&1; then
|
||||
FFMPEG=$(command -v ffmpeg-rk)
|
||||
fi
|
||||
|
||||
if [[ -z "$FFMPEG" ]]; then
|
||||
add_check vpu ffmpeg-rkmpp skip "ffmpeg-rk is not installed"
|
||||
add_check rga ffmpeg-filter skip "ffmpeg-rk is not installed"
|
||||
else
|
||||
hwaccels=$($FFMPEG -hide_banner -hwaccels 2>&1)
|
||||
if grep -qE '(^|[[:space:]])rkmpp($|[[:space:]])' <<<"$hwaccels"; then
|
||||
add_check vpu ffmpeg-rkmpp pass "$FFMPEG advertises the rkmpp hwaccel"
|
||||
else
|
||||
add_check vpu ffmpeg-rkmpp fail "$FFMPEG does not advertise rkmpp"
|
||||
fi
|
||||
|
||||
encoders=$($FFMPEG -hide_banner -encoders 2>&1)
|
||||
for codec in h264 hevc mjpeg; do
|
||||
if grep -q "${codec}_rkmpp" <<<"$encoders"; then
|
||||
add_check vpu "encode-$codec-capability" pass \
|
||||
"${codec}_rkmpp is registered"
|
||||
else
|
||||
add_check vpu "encode-$codec-capability" fail \
|
||||
"${codec}_rkmpp is not registered"
|
||||
fi
|
||||
done
|
||||
|
||||
filters=$($FFMPEG -hide_banner -filters 2>&1)
|
||||
if grep -q 'scale_rkrga' <<<"$filters"; then
|
||||
add_check rga ffmpeg-filter pass "scale_rkrga is registered"
|
||||
else
|
||||
add_check rga ffmpeg-filter fail "scale_rkrga is not registered"
|
||||
fi
|
||||
|
||||
if ((QUICK)); then
|
||||
add_check vpu encode-workload skip "active encoder probe disabled by --quick"
|
||||
else
|
||||
for codec in h264 hevc mjpeg; do
|
||||
encode_output=$(timeout 30s "$FFMPEG" -nostdin -v error \
|
||||
-f lavfi -i 'color=size=320x240:rate=30:duration=0.2' \
|
||||
-frames:v 3 -c:v "${codec}_rkmpp" -f null - 2>&1)
|
||||
encode_rc=$?
|
||||
if ((encode_rc == 0)); then
|
||||
add_check vpu "encode-$codec-workload" pass \
|
||||
"three frames submitted successfully"
|
||||
else
|
||||
add_check vpu "encode-$codec-workload" fail \
|
||||
"hardware encode failed: $(short_message "$encode_output")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -n "$MEDIA_DIR" && $QUICK -eq 0 ]]; then
|
||||
first_decode_sample=""
|
||||
for codec in h264 hevc vp9 av1; do
|
||||
sample=$(find_sample "$codec" || true)
|
||||
if [[ -z "$sample" ]]; then
|
||||
add_check vpu "decode-$codec-workload" skip \
|
||||
"no sample containing '$codec' in $MEDIA_DIR"
|
||||
continue
|
||||
fi
|
||||
[[ -n "$first_decode_sample" ]] || first_decode_sample=$sample
|
||||
decode_output=$(timeout 180s "$FFMPEG" -nostdin -v error \
|
||||
-hwaccel rkmpp -i "$sample" -map 0:v:0 -f null - 2>&1)
|
||||
decode_rc=$?
|
||||
if ((decode_rc == 0)); then
|
||||
add_check vpu "decode-$codec-workload" pass \
|
||||
"decoded ${sample##*/} through RKMPP"
|
||||
else
|
||||
add_check vpu "decode-$codec-workload" fail \
|
||||
"decode failed: $(short_message "$decode_output")"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$first_decode_sample" ]] && grep -q 'scale_rkrga' <<<"$filters"; then
|
||||
rga_output=$(timeout 60s "$FFMPEG" -nostdin -v error \
|
||||
-hwaccel rkmpp -hwaccel_output_format drm_prime \
|
||||
-i "$first_decode_sample" -map 0:v:0 -frames:v 30 \
|
||||
-vf 'scale_rkrga=w=1280:h=720:format=nv12' -f null - 2>&1)
|
||||
rga_rc=$?
|
||||
if ((rga_rc == 0)); then
|
||||
add_check rga workload pass "RKMPP-to-RGA scaling completed"
|
||||
else
|
||||
add_check rga workload fail \
|
||||
"RGA workload failed: $(short_message "$rga_output")"
|
||||
fi
|
||||
else
|
||||
add_check rga workload skip "no decode sample is available for RGA"
|
||||
fi
|
||||
elif [[ -z "$MEDIA_DIR" ]]; then
|
||||
add_check vpu decode-workload skip "use --media-dir to test hardware decoding"
|
||||
add_check rga workload skip "use --media-dir to test zero-copy RGA scaling"
|
||||
fi
|
||||
fi
|
||||
|
||||
rga2_bindings=$(driver_binding_count 'rga2')
|
||||
rga3_bindings=$(driver_binding_count 'rga3')
|
||||
if ((rga2_bindings + rga3_bindings > 0)); then
|
||||
add_check rga kernel-driver pass \
|
||||
"RGA2 bindings=$rga2_bindings, RGA3 bindings=$rga3_bindings"
|
||||
else
|
||||
add_check rga kernel-driver warn "no RGA2/RGA3 binding was found"
|
||||
fi
|
||||
|
||||
# NPU runtime and per-core inference
|
||||
npu_bindings=$(driver_binding_count 'rknpu')
|
||||
((npu_bindings += $(driver_binding_count 'RKNPU')))
|
||||
shopt -s nullglob
|
||||
npu_devfreq=("$SYS_ROOT"/class/devfreq/*.npu)
|
||||
shopt -u nullglob
|
||||
if ((npu_bindings > 0 || ${#npu_devfreq[@]} > 0)); then
|
||||
add_check npu kernel-driver pass \
|
||||
"RKNPU bindings=$npu_bindings, devfreq nodes=${#npu_devfreq[@]}"
|
||||
npu_present=1
|
||||
else
|
||||
add_check npu kernel-driver warn "RKNPU driver/device was not found"
|
||||
npu_present=0
|
||||
fi
|
||||
|
||||
runtime_so="$RKNN_HOME/lib/librknnrt.so"
|
||||
model_path="$RKNN_HOME/share/models/rk3588/mobilenet_v1.rknn"
|
||||
npu_test="$RKNN_HOME/bin/rknn-inference-test"
|
||||
if [[ -r "$runtime_so" ]]; then
|
||||
runtime_hash=$(sha256sum "$runtime_so" 2>/dev/null)
|
||||
runtime_hash=${runtime_hash%% *}
|
||||
if [[ "$runtime_hash" == \
|
||||
"d31fc19c85b85f6091b2bd0f6af9d962d5264a4e410bfb536402ec92bac738e8" ]]; then
|
||||
add_check npu runtime pass "RKNN Runtime 2.3.2 checksum matches"
|
||||
else
|
||||
add_check npu runtime fail "unexpected librknnrt.so checksum $runtime_hash"
|
||||
fi
|
||||
else
|
||||
add_check npu runtime warn "RKNN runtime is not installed at $runtime_so"
|
||||
fi
|
||||
|
||||
if ((QUICK)); then
|
||||
add_check npu inference skip "active inference disabled by --quick"
|
||||
elif ((npu_present == 0)); then
|
||||
add_check npu inference skip "NPU driver is absent; inference was not attempted"
|
||||
elif [[ ! -x "$npu_test" || ! -r "$model_path" ]]; then
|
||||
add_check npu inference skip "test binary or pinned RK3588 model is unavailable"
|
||||
else
|
||||
for core in 0 1 2 all; do
|
||||
npu_output=$(timeout 45s "$npu_test" --model "$model_path" \
|
||||
--core "$core" --iterations 1 2>&1)
|
||||
npu_rc=$?
|
||||
if ((npu_rc == 0)) && grep -q 'RKNN_RESULT status=pass' <<<"$npu_output"; then
|
||||
add_check npu "core-$core" pass "$(short_message "$npu_output")"
|
||||
else
|
||||
add_check npu "core-$core" fail \
|
||||
"inference failed: $(short_message "$npu_output")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# HDMI ALSA devices
|
||||
asound_cards=$(read_text "$PROC_ROOT/asound/cards")
|
||||
if [[ -n "$asound_cards" ]] && grep -qiE 'hdmi|rockchiphdmi' <<<"$asound_cards"; then
|
||||
add_check audio hdmi-card pass "an HDMI ALSA card is registered"
|
||||
elif [[ -n "$asound_cards" ]]; then
|
||||
add_check audio hdmi-card warn "ALSA cards exist, but none is labeled HDMI"
|
||||
else
|
||||
add_check audio hdmi-card warn "ALSA card inventory is unavailable"
|
||||
fi
|
||||
if command -v aplay >/dev/null 2>&1; then
|
||||
aplay_output=$(aplay -l 2>&1)
|
||||
if grep -qi 'hdmi' <<<"$aplay_output"; then
|
||||
add_check audio pcm-device pass "aplay lists an HDMI PCM device"
|
||||
else
|
||||
add_check audio pcm-device warn \
|
||||
"aplay did not list an HDMI PCM device: $(short_message "$aplay_output")"
|
||||
fi
|
||||
else
|
||||
add_check audio pcm-device skip "aplay is not installed"
|
||||
fi
|
||||
|
||||
# Link state only: no pings, DNS requests, or network mutations.
|
||||
shopt -s nullglob
|
||||
interfaces=("$SYS_ROOT"/class/net/*)
|
||||
shopt -u nullglob
|
||||
interface_names=()
|
||||
up_names=()
|
||||
for interface in "${interfaces[@]}"; do
|
||||
name=${interface##*/}
|
||||
[[ "$name" == lo ]] && continue
|
||||
interface_names+=("$name")
|
||||
state=$(read_text "$interface/operstate")
|
||||
carrier=$(read_text "$interface/carrier")
|
||||
if [[ "$state" == up || "$carrier" == 1 ]]; then
|
||||
up_names+=("$name")
|
||||
fi
|
||||
done
|
||||
if ((${#interface_names[@]} == 0)); then
|
||||
add_check network interfaces warn "no non-loopback interface was found"
|
||||
elif ((${#up_names[@]} > 0)); then
|
||||
add_check network link pass "up: ${up_names[*]}"
|
||||
else
|
||||
add_check network link warn \
|
||||
"interfaces present but down: ${interface_names[*]}"
|
||||
fi
|
||||
|
||||
# Root filesystem, eMMC, and NVMe presence. No serial numbers are read.
|
||||
if command -v findmnt >/dev/null 2>&1; then
|
||||
root_source=$(findmnt -n -o SOURCE / 2>/dev/null || true)
|
||||
root_fstype=$(findmnt -n -o FSTYPE / 2>/dev/null || true)
|
||||
if [[ -n "$root_source" ]]; then
|
||||
add_check storage rootfs pass "$root_source ($root_fstype)"
|
||||
else
|
||||
add_check storage rootfs warn "root filesystem source is unavailable"
|
||||
fi
|
||||
else
|
||||
add_check storage rootfs skip "findmnt is not installed"
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
emmc_devices=("$SYS_ROOT"/block/mmcblk*)
|
||||
nvme_devices=("$SYS_ROOT"/block/nvme*n1)
|
||||
shopt -u nullglob
|
||||
if ((${#emmc_devices[@]} > 0)); then
|
||||
add_check storage emmc pass "${#emmc_devices[@]} MMC block device(s)"
|
||||
else
|
||||
add_check storage emmc warn "no MMC/eMMC block device was found"
|
||||
fi
|
||||
if ((${#nvme_devices[@]} > 0)); then
|
||||
add_check storage nvme pass "${#nvme_devices[@]} NVMe namespace(s)"
|
||||
else
|
||||
add_check storage nvme warn "no NVMe namespace was found"
|
||||
fi
|
||||
|
||||
pass_count=0
|
||||
warn_count=0
|
||||
fail_count=0
|
||||
skip_count=0
|
||||
for status in "${CHECK_STATUS[@]}"; do
|
||||
case "$status" in
|
||||
pass) ((pass_count++)) ;;
|
||||
warn) ((warn_count++)) ;;
|
||||
fail) ((fail_count++)) ;;
|
||||
skip) ((skip_count++)) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ((fail_count > 0)); then
|
||||
overall=fail
|
||||
elif ((warn_count > 0)); then
|
||||
overall=warn
|
||||
else
|
||||
overall=pass
|
||||
fi
|
||||
|
||||
json_escape() {
|
||||
local value=$1
|
||||
value=${value//\\/\\\\}
|
||||
value=${value//\"/\\\"}
|
||||
value=${value//$'\n'/\\n}
|
||||
value=${value//$'\r'/\\r}
|
||||
value=${value//$'\t'/\\t}
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
if ((JSON)); then
|
||||
printf '{"schema_version":1,"overall":"%s","quick":%s,' \
|
||||
"$overall" "$([[ $QUICK -eq 1 ]] && printf true || printf false)"
|
||||
printf '"summary":{"pass":%d,"warn":%d,"fail":%d,"skip":%d},' \
|
||||
"$pass_count" "$warn_count" "$fail_count" "$skip_count"
|
||||
printf '"checks":['
|
||||
for ((i = 0; i < ${#CHECK_NAME[@]}; ++i)); do
|
||||
((i == 0)) || printf ','
|
||||
printf '{"group":"%s","name":"%s","status":"%s","message":"%s"}' \
|
||||
"$(json_escape "${CHECK_GROUP[i]}")" \
|
||||
"$(json_escape "${CHECK_NAME[i]}")" \
|
||||
"${CHECK_STATUS[i]}" \
|
||||
"$(json_escape "${CHECK_MESSAGE[i]}")"
|
||||
done
|
||||
printf ']}\n'
|
||||
else
|
||||
printf '%-9s %-24s %-6s %s\n' GROUP CHECK STATUS DETAIL
|
||||
printf '%-9s %-24s %-6s %s\n' '---------' '------------------------' \
|
||||
'------' '------'
|
||||
for ((i = 0; i < ${#CHECK_NAME[@]}; ++i)); do
|
||||
printf '%-9s %-24s %-6s %s\n' \
|
||||
"${CHECK_GROUP[i]}" "${CHECK_NAME[i]}" \
|
||||
"${CHECK_STATUS[i]^^}" "${CHECK_MESSAGE[i]}"
|
||||
done
|
||||
printf '\nOverall: %s (pass=%d warn=%d fail=%d skip=%d)\n' \
|
||||
"${overall^^}" "$pass_count" "$warn_count" "$fail_count" "$skip_count"
|
||||
fi
|
||||
|
||||
if ((fail_count > 0 || (STRICT && warn_count > 0))); then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Minimal RK3588 NPU smoke test for RKNN Runtime 2.3.2.
|
||||
*
|
||||
* The program deliberately uses deterministic synthetic inputs. Its purpose is
|
||||
* to verify model loading, core selection, command submission, and finite
|
||||
* output, not the semantic accuracy of MobileNet.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "rknn_api.h"
|
||||
|
||||
#define DEFAULT_MODEL "/opt/rknn/current/share/models/rk3588/mobilenet_v1.rknn"
|
||||
#define MAX_TENSORS 64U
|
||||
|
||||
static void usage(FILE *stream, const char *program)
|
||||
{
|
||||
fprintf(stream,
|
||||
"Usage: %s [--model PATH] [--core auto|0|1|2|all] "
|
||||
"[--iterations N]\n",
|
||||
program);
|
||||
}
|
||||
|
||||
static int parse_core(const char *value, rknn_core_mask *mask)
|
||||
{
|
||||
if (strcmp(value, "auto") == 0) {
|
||||
*mask = RKNN_NPU_CORE_AUTO;
|
||||
} else if (strcmp(value, "0") == 0) {
|
||||
*mask = RKNN_NPU_CORE_0;
|
||||
} else if (strcmp(value, "1") == 0) {
|
||||
*mask = RKNN_NPU_CORE_1;
|
||||
} else if (strcmp(value, "2") == 0) {
|
||||
*mask = RKNN_NPU_CORE_2;
|
||||
} else if (strcmp(value, "all") == 0) {
|
||||
*mask = RKNN_NPU_CORE_0_1_2;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_file(const char *path, void **buffer, uint32_t *size)
|
||||
{
|
||||
struct stat info;
|
||||
FILE *file = NULL;
|
||||
void *data = NULL;
|
||||
|
||||
if (stat(path, &info) != 0) {
|
||||
fprintf(stderr, "cannot stat model %s: %s\n", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (info.st_size <= 0 || (uint64_t)info.st_size > UINT32_MAX) {
|
||||
fprintf(stderr, "invalid model size: %jd\n", (intmax_t)info.st_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "cannot open model %s: %s\n", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
data = malloc((size_t)info.st_size);
|
||||
if (data == NULL) {
|
||||
fprintf(stderr, "cannot allocate %jd bytes for model\n",
|
||||
(intmax_t)info.st_size);
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
if (fread(data, 1, (size_t)info.st_size, file) != (size_t)info.st_size) {
|
||||
fprintf(stderr, "short read from model %s\n", path);
|
||||
free(data);
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
fclose(file);
|
||||
*buffer = data;
|
||||
*size = (uint32_t)info.st_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double elapsed_ms(const struct timespec *start, const struct timespec *end)
|
||||
{
|
||||
double seconds = (double)(end->tv_sec - start->tv_sec) * 1000.0;
|
||||
double nanos = (double)(end->tv_nsec - start->tv_nsec) / 1000000.0;
|
||||
return seconds + nanos;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *model_path = DEFAULT_MODEL;
|
||||
const char *core_name = "auto";
|
||||
rknn_core_mask core_mask = RKNN_NPU_CORE_AUTO;
|
||||
unsigned long iterations = 1;
|
||||
void *model = NULL;
|
||||
uint32_t model_size = 0;
|
||||
rknn_context context = 0;
|
||||
rknn_sdk_version sdk_version;
|
||||
rknn_input_output_num io_count;
|
||||
rknn_tensor_attr *input_attrs = NULL;
|
||||
rknn_input *inputs = NULL;
|
||||
rknn_output *outputs = NULL;
|
||||
int outputs_acquired = 0;
|
||||
int context_created = 0;
|
||||
int result = EXIT_FAILURE;
|
||||
double total_ms = 0.0;
|
||||
uint32_t last_top_index = 0;
|
||||
float last_top_value = -INFINITY;
|
||||
int ret;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 1; i < (uint32_t)argc; ++i) {
|
||||
if (strcmp(argv[i], "--model") == 0 && i + 1U < (uint32_t)argc) {
|
||||
model_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "--core") == 0 && i + 1U < (uint32_t)argc) {
|
||||
core_name = argv[++i];
|
||||
if (parse_core(core_name, &core_mask) != 0) {
|
||||
fprintf(stderr, "invalid core selector: %s\n", core_name);
|
||||
usage(stderr, argv[0]);
|
||||
return 2;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--iterations") == 0 &&
|
||||
i + 1U < (uint32_t)argc) {
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
iterations = strtoul(argv[++i], &end, 10);
|
||||
if (errno != 0 || end == argv[i] || *end != '\0' ||
|
||||
iterations == 0 || iterations > 1000) {
|
||||
fprintf(stderr, "iterations must be between 1 and 1000\n");
|
||||
return 2;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--help") == 0 ||
|
||||
strcmp(argv[i], "-h") == 0) {
|
||||
usage(stdout, argv[0]);
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "unknown or incomplete option: %s\n", argv[i]);
|
||||
usage(stderr, argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (load_file(model_path, &model, &model_size) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = rknn_init(&context, model, model_size, 0, NULL);
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "rknn_init failed: %d\n", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
context_created = 1;
|
||||
|
||||
ret = rknn_set_core_mask(context, core_mask);
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "rknn_set_core_mask(%s) failed: %d\n", core_name, ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memset(&sdk_version, 0, sizeof(sdk_version));
|
||||
ret = rknn_query(context, RKNN_QUERY_SDK_VERSION, &sdk_version,
|
||||
sizeof(sdk_version));
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "RKNN_QUERY_SDK_VERSION failed: %d\n", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memset(&io_count, 0, sizeof(io_count));
|
||||
ret = rknn_query(context, RKNN_QUERY_IN_OUT_NUM, &io_count,
|
||||
sizeof(io_count));
|
||||
if (ret != RKNN_SUCC || io_count.n_input == 0 || io_count.n_output == 0 ||
|
||||
io_count.n_input > MAX_TENSORS || io_count.n_output > MAX_TENSORS) {
|
||||
fprintf(stderr, "invalid RKNN input/output count (%u/%u), ret=%d\n",
|
||||
io_count.n_input, io_count.n_output, ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
input_attrs = calloc(io_count.n_input, sizeof(*input_attrs));
|
||||
inputs = calloc(io_count.n_input, sizeof(*inputs));
|
||||
outputs = calloc(io_count.n_output, sizeof(*outputs));
|
||||
if (input_attrs == NULL || inputs == NULL || outputs == NULL) {
|
||||
fprintf(stderr, "cannot allocate tensor metadata\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (i = 0; i < io_count.n_input; ++i) {
|
||||
uint32_t byte;
|
||||
input_attrs[i].index = i;
|
||||
ret = rknn_query(context, RKNN_QUERY_INPUT_ATTR, &input_attrs[i],
|
||||
sizeof(input_attrs[i]));
|
||||
if (ret != RKNN_SUCC || input_attrs[i].n_elems == 0) {
|
||||
fprintf(stderr, "query for input %u failed: %d\n", i, ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
inputs[i].index = i;
|
||||
inputs[i].size = input_attrs[i].n_elems;
|
||||
inputs[i].type = RKNN_TENSOR_UINT8;
|
||||
inputs[i].fmt = input_attrs[i].fmt == RKNN_TENSOR_UNDEFINED
|
||||
? RKNN_TENSOR_NHWC
|
||||
: input_attrs[i].fmt;
|
||||
inputs[i].pass_through = 0;
|
||||
inputs[i].buf = malloc(inputs[i].size);
|
||||
if (inputs[i].buf == NULL) {
|
||||
fprintf(stderr, "cannot allocate input %u (%u bytes)\n", i,
|
||||
inputs[i].size);
|
||||
goto cleanup;
|
||||
}
|
||||
for (byte = 0; byte < inputs[i].size; ++byte) {
|
||||
((uint8_t *)inputs[i].buf)[byte] =
|
||||
(uint8_t)((byte * 17U + i * 23U) & 0xffU);
|
||||
}
|
||||
}
|
||||
|
||||
ret = rknn_inputs_set(context, io_count.n_input, inputs);
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "rknn_inputs_set failed: %d\n", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (i = 0; i < io_count.n_output; ++i) {
|
||||
outputs[i].index = i;
|
||||
outputs[i].want_float = 1;
|
||||
outputs[i].is_prealloc = 0;
|
||||
}
|
||||
|
||||
for (unsigned long iteration = 0; iteration < iterations; ++iteration) {
|
||||
struct timespec start;
|
||||
struct timespec end;
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
|
||||
fprintf(stderr, "clock_gettime failed: %s\n", strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
ret = rknn_run(context, NULL);
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "rknn_run failed at iteration %lu: %d\n",
|
||||
iteration, ret);
|
||||
goto cleanup;
|
||||
}
|
||||
ret = rknn_outputs_get(context, io_count.n_output, outputs, NULL);
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "rknn_outputs_get failed at iteration %lu: %d\n",
|
||||
iteration, ret);
|
||||
goto cleanup;
|
||||
}
|
||||
outputs_acquired = 1;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
|
||||
fprintf(stderr, "clock_gettime failed: %s\n", strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
total_ms += elapsed_ms(&start, &end);
|
||||
|
||||
last_top_value = -INFINITY;
|
||||
last_top_index = 0;
|
||||
for (i = 0; i < io_count.n_output; ++i) {
|
||||
const float *values = outputs[i].buf;
|
||||
uint32_t count = outputs[i].size / (uint32_t)sizeof(float);
|
||||
uint32_t value_index;
|
||||
if (values == NULL || count == 0) {
|
||||
fprintf(stderr, "output %u is empty\n", i);
|
||||
goto cleanup;
|
||||
}
|
||||
for (value_index = 0; value_index < count; ++value_index) {
|
||||
if (!isfinite(values[value_index])) {
|
||||
fprintf(stderr, "output %u contains a non-finite value\n", i);
|
||||
goto cleanup;
|
||||
}
|
||||
if (i == 0 && values[value_index] > last_top_value) {
|
||||
last_top_value = values[value_index];
|
||||
last_top_index = value_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = rknn_outputs_release(context, io_count.n_output, outputs);
|
||||
outputs_acquired = 0;
|
||||
if (ret != RKNN_SUCC) {
|
||||
fprintf(stderr, "rknn_outputs_release failed: %d\n", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
printf("RKNN_RESULT status=pass core=%s iterations=%lu avg_ms=%.3f "
|
||||
"top_index=%u top_value=%.7g api=%s driver=%s\n",
|
||||
core_name, iterations, total_ms / (double)iterations,
|
||||
last_top_index, last_top_value, sdk_version.api_version,
|
||||
sdk_version.drv_version);
|
||||
result = EXIT_SUCCESS;
|
||||
|
||||
cleanup:
|
||||
if (outputs_acquired) {
|
||||
(void)rknn_outputs_release(context, io_count.n_output, outputs);
|
||||
}
|
||||
if (inputs != NULL) {
|
||||
for (i = 0; i < io_count.n_input; ++i) {
|
||||
free(inputs[i].buf);
|
||||
}
|
||||
}
|
||||
free(outputs);
|
||||
free(inputs);
|
||||
free(input_attrs);
|
||||
if (context_created) {
|
||||
(void)rknn_destroy(context);
|
||||
}
|
||||
free(model);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Pinned local-inference runtime for the RK3588 NPU.
|
||||
# This file is sourced by install-rknn-runtime.sh.
|
||||
RKNN_VERSION="2.3.2"
|
||||
RKNN_TAG="v2.3.2"
|
||||
RKNN_COMMIT="42aa1d426c0a9e0869b6374edba009f7208a1926"
|
||||
RKNN_REPOSITORY="https://github.com/airockchip/rknn-toolkit2.git"
|
||||
|
||||
RKNN_HEADER_SHA256="c48e11a6f41b451a5fd1e4ad774ea60252d3d94f78bee9b21ea3d21b21deba9a"
|
||||
RKNN_RUNTIME_AARCH64_SHA256="d31fc19c85b85f6091b2bd0f6af9d962d5264a4e410bfb536402ec92bac738e8"
|
||||
RKNN_MOBILENET_RK3588_SHA256="381dae3b7038a98b10f6ec9dcdbb094a49247341856fb294e692ef518218fcfb"
|
||||
RKNN_DEMO_IMAGE_SHA256="c350299c6283d5f62fecf1f845b6b3be9aafec8dff528ca09a129990f0a584b0"
|
||||
RKNN_LICENSE_SHA256="d846f57d942c7dfdca7b8b54f9e8bb39e1e226790dc4f5ee205d6fd678961720"
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
TEST_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
RUNTIME_DIR=$(cd -- "$TEST_DIR/.." && pwd -P)
|
||||
TEST_TMP=$(mktemp -d "${TMPDIR:-/tmp}/rk1-runtime-tests.XXXXXXXX")
|
||||
|
||||
cleanup() {
|
||||
rm -rf -- "$TEST_TMP"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
pass() {
|
||||
printf 'PASS: %s\n' "$*"
|
||||
}
|
||||
|
||||
bash -n \
|
||||
"$RUNTIME_DIR/install-rknn-runtime.sh" \
|
||||
"$RUNTIME_DIR/rk1-media-selftest" \
|
||||
"$RUNTIME_DIR/rk1-media-diagnostics"
|
||||
pass "shell syntax"
|
||||
|
||||
"$RUNTIME_DIR/install-rknn-runtime.sh" --help >/dev/null
|
||||
"$RUNTIME_DIR/rk1-media-selftest" --help >/dev/null
|
||||
"$RUNTIME_DIR/rk1-media-diagnostics" --help >/dev/null
|
||||
pass "command help"
|
||||
|
||||
mkdir -p \
|
||||
"$TEST_TMP/empty/sys" \
|
||||
"$TEST_TMP/empty/proc" \
|
||||
"$TEST_TMP/empty/dev" \
|
||||
"$TEST_TMP/empty/rknn"
|
||||
RK1_SYSFS_ROOT="$TEST_TMP/empty/sys" \
|
||||
RK1_PROCFS_ROOT="$TEST_TMP/empty/proc" \
|
||||
RK1_DEV_ROOT="$TEST_TMP/empty/dev" \
|
||||
RKNN_HOME="$TEST_TMP/empty/rknn" \
|
||||
"$RUNTIME_DIR/rk1-media-selftest" --quick --json \
|
||||
>"$TEST_TMP/empty.json"
|
||||
grep -q '"overall":"warn"' "$TEST_TMP/empty.json" ||
|
||||
fail "empty-hardware JSON does not report a warning"
|
||||
grep -q '"checks":\[' "$TEST_TMP/empty.json" ||
|
||||
fail "empty-hardware JSON has no checks"
|
||||
pass "absent hardware degrades safely"
|
||||
|
||||
FAKE="$TEST_TMP/fake"
|
||||
mkdir -p \
|
||||
"$FAKE/sys/class/drm/card0-HDMI-A-1" \
|
||||
"$FAKE/sys/class/devfreq/fb000000.gpu" \
|
||||
"$FAKE/sys/class/devfreq/fdab0000.npu" \
|
||||
"$FAKE/sys/class/net/eth0" \
|
||||
"$FAKE/sys/block/mmcblk0" \
|
||||
"$FAKE/sys/block/nvme0n1" \
|
||||
"$FAKE/sys/bus/platform/drivers/dwhdmi-rockchip/fde80000.hdmi" \
|
||||
"$FAKE/sys/bus/platform/drivers/rockchip-hdptx-phy-hdmi/fed60000.hdmiphy" \
|
||||
"$FAKE/sys/bus/platform/drivers/panthor/fb000000.gpu" \
|
||||
"$FAKE/sys/bus/platform/drivers/mpp_rkvdec2/fdc38100.rkvdec-core" \
|
||||
"$FAKE/sys/bus/platform/drivers/mpp_rkvenc2/fdbd0000.rkvenc-core" \
|
||||
"$FAKE/sys/bus/platform/drivers/mpp_av1dec/av1-decoder" \
|
||||
"$FAKE/sys/bus/platform/drivers/rga2/fdb80000.rga" \
|
||||
"$FAKE/sys/bus/platform/drivers/rga3/fdb60000.rga" \
|
||||
"$FAKE/sys/bus/platform/drivers/RKNPU/fdab0000.npu" \
|
||||
"$FAKE/proc/device-tree" \
|
||||
"$FAKE/proc/asound" \
|
||||
"$FAKE/dev/dri" \
|
||||
"$FAKE/rknn"
|
||||
printf 'connected\n' >"$FAKE/sys/class/drm/card0-HDMI-A-1/status"
|
||||
printf '3840x2160\n1920x1080\n' >"$FAKE/sys/class/drm/card0-HDMI-A-1/modes"
|
||||
printf 'fake-edid' >"$FAKE/sys/class/drm/card0-HDMI-A-1/edid"
|
||||
printf 'Turing Machines RK1\0' >"$FAKE/proc/device-tree/model"
|
||||
printf ' 0 [rockchiphdmi]: rockchip-hdmi - rockchip-hdmi\n' \
|
||||
>"$FAKE/proc/asound/cards"
|
||||
printf 'up\n' >"$FAKE/sys/class/net/eth0/operstate"
|
||||
printf '1\n' >"$FAKE/sys/class/net/eth0/carrier"
|
||||
touch "$FAKE/dev/dri/renderD128"
|
||||
|
||||
RK1_SYSFS_ROOT="$FAKE/sys" \
|
||||
RK1_PROCFS_ROOT="$FAKE/proc" \
|
||||
RK1_DEV_ROOT="$FAKE/dev" \
|
||||
RKNN_HOME="$FAKE/rknn" \
|
||||
"$RUNTIME_DIR/rk1-media-selftest" --quick --json \
|
||||
>"$TEST_TMP/fake.json"
|
||||
grep -q '"name":"card0-HDMI-A-1","status":"pass"' \
|
||||
"$TEST_TMP/fake.json" || fail "fake connected HDMI was not detected"
|
||||
grep -q '"name":"kernel-driver","status":"pass","message":"Panthor' \
|
||||
"$TEST_TMP/fake.json" || fail "fake Panthor binding was not detected"
|
||||
pass "synthetic hardware inventory"
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 -m json.tool "$TEST_TMP/empty.json" >/dev/null
|
||||
python3 -m json.tool "$TEST_TMP/fake.json" >/dev/null
|
||||
pass "JSON validity"
|
||||
fi
|
||||
|
||||
RK1_SYSFS_ROOT="$FAKE/sys" \
|
||||
RK1_PROCFS_ROOT="$FAKE/proc" \
|
||||
RK1_DEV_ROOT="$FAKE/dev" \
|
||||
RKNN_HOME="$FAKE/rknn" \
|
||||
"$RUNTIME_DIR/rk1-media-diagnostics" --directory \
|
||||
--output "$TEST_TMP/diagnostics" >/dev/null
|
||||
[[ -s "$TEST_TMP/diagnostics/selftest.json" ]] ||
|
||||
fail "diagnostic bundle is missing selftest.json"
|
||||
[[ -s "$TEST_TMP/diagnostics/drm-connectors.txt" ]] ||
|
||||
fail "diagnostic bundle is missing connector data"
|
||||
[[ -s "$TEST_TMP/diagnostics/kernel-media.log" ]] ||
|
||||
fail "diagnostic bundle is missing filtered kernel data"
|
||||
pass "diagnostic collection"
|
||||
|
||||
if [[ -n ${RKNN_SOURCE_DIR:-} ]]; then
|
||||
[[ -d "$RKNN_SOURCE_DIR" ]] || fail "RKNN_SOURCE_DIR is not a directory"
|
||||
bundled_source="$TEST_TMP/bundled-rknn-source"
|
||||
mkdir -p \
|
||||
"$bundled_source/rknpu2/runtime/Linux/librknn_api/include" \
|
||||
"$bundled_source/rknpu2/runtime/Linux/librknn_api/aarch64" \
|
||||
"$bundled_source/rknpu2/examples/rknn_api_demo/model/RK3588" \
|
||||
"$bundled_source/rknpu2/examples/rknn_api_demo/model"
|
||||
cp "$RKNN_SOURCE_DIR/LICENSE" "$bundled_source/LICENSE"
|
||||
cp "$RKNN_SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/include/rknn_api.h" \
|
||||
"$bundled_source/rknpu2/runtime/Linux/librknn_api/include/rknn_api.h"
|
||||
cp "$RKNN_SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" \
|
||||
"$bundled_source/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so"
|
||||
cp "$RKNN_SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn" \
|
||||
"$bundled_source/rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn"
|
||||
cp "$RKNN_SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg" \
|
||||
"$bundled_source/rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg"
|
||||
mkdir -p "$TEST_TMP/rootfs"
|
||||
"$RUNTIME_DIR/install-rknn-runtime.sh" \
|
||||
--rootfs "$TEST_TMP/rootfs" --source-dir "$bundled_source"
|
||||
installed_runtime="$TEST_TMP/rootfs/opt/rknn/2.3.2/lib/librknnrt.so"
|
||||
installed_model="$TEST_TMP/rootfs/opt/rknn/2.3.2/share/models/rk3588/mobilenet_v1.rknn"
|
||||
[[ -x "$TEST_TMP/rootfs/opt/rknn/2.3.2/bin/rknn-inference-test" ]] ||
|
||||
fail "compiled inference test is missing"
|
||||
[[ $(sha256sum "$installed_runtime" | awk '{print $1}') == \
|
||||
d31fc19c85b85f6091b2bd0f6af9d962d5264a4e410bfb536402ec92bac738e8 ]] ||
|
||||
fail "installed runtime checksum differs"
|
||||
[[ $(sha256sum "$installed_model" | awk '{print $1}') == \
|
||||
381dae3b7038a98b10f6ec9dcdbb094a49247341856fb294e692ef518218fcfb ]] ||
|
||||
fail "installed model checksum differs"
|
||||
pass "pinned RKNN installation"
|
||||
else
|
||||
printf 'SKIP: installer integration (set RKNN_SOURCE_DIR to a v2.3.2 checkout)\n'
|
||||
fi
|
||||
|
||||
printf 'All runtime tests passed.\n'
|
||||
Reference in New Issue
Block a user