Files
rk1/runtime/rk1-media-diagnostics
T

438 lines
14 KiB
Bash
Raw Permalink Normal View History

2026-08-17 18:42:29 +00:00
#!/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"