#!/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}//g' \ -e 's/([[:space:]=]|^)([0-9]{1,3}\.){3}[0-9]{1,3}([[:space:]\/:]|$)/\1\3/g' \ -e 's/(root=)[^[:space:]]+/\1/g' \ -e 's/(ip=)[^[:space:]]+/\1/g' \ -e 's/(UUID|PARTUUID)=[A-Za-z0-9-]+/\1=/g' \ -e 's/([Ss]erial([ _-]?[Nn]umber)?[=:][[:space:]]*)[^[:space:],;]+/\1/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#' "$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"