Last active
April 25, 2026 16:20
-
-
Save ajmasia/ac629b2d62a7536fe6c69bf387c67c1d to your computer and use it in GitHub Desktop.
MPU Setup for Slimbook EVO AMD Ryzen AI 9 365
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # install-npu-amd.sh | |
| # Installer for AMD Ryzen AI NPU driver (XRT + amdxdna) on Debian/Ubuntu. | |
| # Tested on: Debian 13 trixie + kernel 6.19 backports + Ryzen AI 9 365 (Strix Point). | |
| set -euo pipefail | |
| IFS=$'\n\t' | |
| # ============================================================================= | |
| # Constants | |
| # ============================================================================= | |
| readonly SCRIPT_VERSION="0.2.1" | |
| readonly SCRIPT_NAME="$(basename "$0")" | |
| readonly REPO_URL="https://github.com/amd/xdna-driver.git" | |
| readonly XRT_PREFIX="/opt/xilinx/xrt" | |
| readonly LIMITS_FILE="/etc/security/limits.d/99-xrt.conf" | |
| readonly UDEV_RULES_FILE="/etc/udev/rules.d/99-amdxdna.rules" | |
| readonly DKMS_MODULE="xrt-amdxdna" | |
| readonly RC_MARKER="# AMD XRT (Ryzen AI NPU) — load XRT environment on demand: run \`npu-env\`" | |
| # Ensure /usr/sbin is in PATH (modinfo, dkms, prlimit live there on Debian for non-root) | |
| case ":$PATH:" in *:/usr/sbin:*) ;; *) export PATH="$PATH:/usr/sbin" ;; esac | |
| # ============================================================================= | |
| # Mutable options (overridden by flags) | |
| # ============================================================================= | |
| LANG_CODE="" # auto-detected from $LANG / $LC_* if empty after parse_args | |
| BUILD_DIR="${HOME}/npu-build" | |
| DRY_RUN=0 | |
| SKIP_TESTS=0 | |
| NO_CLEANUP=0 | |
| ASSUME_YES=0 | |
| ACTION="install" # install | uninstall | check | |
| CURRENT_STEP="" | |
| LOG_FILE="" # set by init_log_file | |
| NO_LOG=0 | |
| SUDO_KEEPALIVE_PID="" # tracks the background sudo refresh process for cleanup | |
| # Capture args as a space-joined string. We can't use "$*" here because IFS=$'\n\t' joins with newlines. | |
| ORIG_ARGS="$(printf '%s ' "$@")" | |
| ORIG_ARGS="${ORIG_ARGS% }" | |
| # Allowed characters for user-supplied paths (--build-dir, --log-file). | |
| # Restrictive on purpose: rules out shell metacharacters and substitution. | |
| readonly PATH_REGEX='^[a-zA-Z0-9._/~+:-]+$' | |
| # ============================================================================= | |
| # Catppuccin Mocha palette (24-bit ANSI). Only used for tags; body text white. | |
| # ============================================================================= | |
| if [[ -t 1 ]]; then | |
| C_BLUE=$'\033[38;2;137;180;250m' # blue — info | |
| C_GREEN=$'\033[38;2;166;227;161m' # green — ok | |
| C_RED=$'\033[38;2;243;139;168m' # red — err | |
| C_YELLOW=$'\033[38;2;249;226;175m' # yellow — warn | |
| C_LAVENDER=$'\033[38;2;180;190;254m' # lav — step | |
| C_PINK=$'\033[38;2;245;194;231m' # pink — ask | |
| C_MAUVE=$'\033[38;2;203;166;247m' # mauve — title | |
| C_TEAL=$'\033[38;2;148;226;213m' # teal — run | |
| C_SUBTEXT=$'\033[38;2;166;173;200m' # subtxt — skip / dim | |
| C_TEXT=$'\033[38;2;205;214;244m' # text body | |
| C_BOLD=$'\033[1m' | |
| C_DIM=$'\033[2m' | |
| C_RESET=$'\033[0m' | |
| else | |
| C_BLUE="" C_GREEN="" C_RED="" C_YELLOW="" C_LAVENDER="" | |
| C_PINK="" C_MAUVE="" C_TEAL="" C_SUBTEXT="" C_TEXT="" | |
| C_BOLD="" C_DIM="" C_RESET="" | |
| fi | |
| # ============================================================================= | |
| # Logging — tags colored on terminal, plain (uncolored) text mirrored to LOG_FILE | |
| # ============================================================================= | |
| log_to_file() { | |
| # Append a timestamped, color-free line to the log file if logging is enabled. | |
| [[ -z "${LOG_FILE:-}" ]] && return 0 | |
| printf "[%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE" | |
| } | |
| log_info() { printf "%s[info]%s %s%s%s\n" "$C_BLUE" "$C_RESET" "$C_TEXT" "$*" "$C_RESET"; log_to_file "[info] $*"; } | |
| log_ok() { printf "%s[ok]%s %s%s%s\n" "$C_GREEN" "$C_RESET" "$C_TEXT" "$*" "$C_RESET"; log_to_file "[ok] $*"; } | |
| log_warn() { printf "%s[warn]%s %s%s%s\n" "$C_YELLOW" "$C_RESET" "$C_TEXT" "$*" "$C_RESET"; log_to_file "[warn] $*"; } | |
| log_err() { printf "%s[err]%s %s%s%s\n" "$C_RED" "$C_RESET" "$C_TEXT" "$*" "$C_RESET" >&2; log_to_file "[err] $*"; } | |
| log_step() { printf "\n%s[step]%s %s%s%s%s\n" "$C_LAVENDER" "$C_RESET" "$C_TEXT$C_BOLD" "$*" "$C_RESET" ""; log_to_file "[step] $*"; } | |
| log_skip() { printf "%s[skip]%s %s%s%s\n" "$C_SUBTEXT" "$C_RESET" "$C_TEXT" "$*" "$C_RESET"; log_to_file "[skip] $*"; } | |
| log_run() { printf "%s[run]%s %s%s%s\n" "$C_TEAL" "$C_RESET" "$C_DIM$C_TEXT" "$*" "$C_RESET"; log_to_file "[run] $*"; } | |
| log_title() { printf "\n%s%s%s%s\n" "$C_MAUVE$C_BOLD" "$*" "$C_RESET" ""; log_to_file "**** $* ****"; } | |
| # ============================================================================= | |
| # i18n — keep keys short, fallback to EN if missing in ES | |
| # ============================================================================= | |
| declare -A T_EN T_ES | |
| # --- English strings --- | |
| T_EN[help_summary]="Installer for the AMD Ryzen AI NPU driver (XRT + amdxdna)." | |
| T_EN[help_options]="Options:" | |
| T_EN[help_lang]=" --es, --spanish Use Spanish output" | |
| T_EN[help_dry]=" --dry-run Print actions without executing them" | |
| T_EN[help_skip_tests]=" --skip-tests Do not run xrt-smi validate at the end" | |
| T_EN[help_no_cleanup]=" --no-cleanup Keep build directory after install" | |
| T_EN[help_yes]=" -y, --yes Answer yes to all prompts" | |
| T_EN[help_check]=" --check Only show current install state" | |
| T_EN[help_uninstall]=" --uninstall Remove the NPU stack" | |
| T_EN[help_build_dir]=" --build-dir <path> Use custom build directory (default: ~/npu-build)" | |
| T_EN[help_log_file]=" --log-file <path> Write a timestamped log to <path> (default: /tmp/install-npu-amd-<ts>.log)" | |
| T_EN[help_no_log]=" --no-log Disable file logging" | |
| T_EN[help_help]=" -h, --help Show this help" | |
| T_EN[help_version]=" --version Show script version" | |
| T_EN[intro_what]="What is this driver?" | |
| T_EN[intro_what_body]="The amdxdna driver exposes the NPU (XDNA / AI Engine) embedded in AMD Ryzen AI CPUs as a Linux DRM accel device. XRT (AMD/Xilinx RunTime) is the userspace runtime that talks to it. Together they enable hardware acceleration for AI inference workloads (~50 TOPS INT8 in Strix Point)." | |
| T_EN[intro_what_will]="What this script will do" | |
| T_EN[intro_steps]=" - Pre-flight checks: hardware, kernel, network, disk, current state.\n - Install DKMS via apt (if missing).\n - Clone the AMD xdna-driver repository.\n - Run AMD's xrtdeps.sh to install repo-based build deps.\n - Build XRT (~5–10 min) and install the base .deb.\n - Build the amdxdna plugin (~1–3 min) and install it (DKMS).\n - Configure memlock limit (udev rule comes from the plugin's postinst).\n - Add an 'npu-env' helper to your shell rc.\n - Verify module path, device permissions, udev rule.\n - Activate XRT and run validation tests." | |
| T_EN[intro_disk]="Disk: ~4 GB during build, ~70 MB after cleanup. Network: required (clone + apt)." | |
| T_EN[intro_sudo]="The script will request sudo only when needed and cache it for the rest of the run." | |
| T_EN[ask_proceed]="Proceed with installation?" | |
| T_EN[ask_continue]="Continue?" | |
| T_EN[abort_user]="Aborted by user." | |
| T_EN[prompt_sudo]="Requesting sudo credentials (cached for the rest of the script)..." | |
| T_EN[check_title]="Pre-flight checks" | |
| T_EN[check_arch_ok]="Architecture is x86_64." | |
| T_EN[check_arch_fail]="Unsupported architecture: %s. Only x86_64 is supported." | |
| T_EN[check_distro_ok]="Distribution: %s %s (apt-based)." | |
| T_EN[check_distro_fail]="Unsupported distribution: %s. Only Debian/Ubuntu are supported." | |
| T_EN[check_kernel]="Kernel: %s" | |
| T_EN[check_kmod_ok]="amdxdna module is available in kernel (CONFIG_DRM_ACCEL_AMDXDNA=m)." | |
| T_EN[check_kmod_fail]="amdxdna kernel support not found. Need kernel >= 6.14 with CONFIG_DRM_ACCEL_AMDXDNA enabled." | |
| T_EN[check_headers_ok]="linux-headers-%s is installed (DKMS can build modules)." | |
| T_EN[check_headers_fail]="linux-headers-%s is not installed. DKMS would fail to build the amdxdna module after a long XRT compile. Install it first with: sudo apt install linux-headers-%s" | |
| T_EN[check_cpu_ok]="CPU detected: %s" | |
| T_EN[check_cpu_warn]="CPU does not look like a Ryzen AI / Strix / Phoenix / Hawk Point. Continuing anyway." | |
| T_EN[check_dev_ok]="NPU device present at /dev/accel/accel0." | |
| T_EN[check_dev_warn]="No /dev/accel/accel0 found yet. Module may need to be loaded after install." | |
| T_EN[check_disk_ok]="Free disk space: %s GB (need at least 4 GB)." | |
| T_EN[check_disk_fail]="Insufficient disk space at %s: %s GB free, need at least 4 GB." | |
| T_EN[check_net_ok]="Network reachable (github.com)." | |
| T_EN[check_net_fail]="Cannot reach github.com. Check your network." | |
| T_EN[check_dkms_ok]="dkms is installed (%s)." | |
| T_EN[check_dkms_missing]="dkms is not installed. Will install it." | |
| T_EN[check_secureboot_on]="Secure Boot is ENABLED. DKMS will sign the module with the system MOK; if no MOK is enrolled the module won't load. Consider 'mokutil --list-enrolled'." | |
| T_EN[check_secureboot_off]="Secure Boot is disabled." | |
| T_EN[state_title]="Current installation state" | |
| T_EN[state_xrt_installed]="xrt-base is installed: %s" | |
| T_EN[state_xrt_missing]="xrt-base is not installed." | |
| T_EN[state_plugin_installed]="xrt_plugin-amdxdna is installed: %s" | |
| T_EN[state_plugin_missing]="xrt_plugin-amdxdna is not installed." | |
| T_EN[state_dkms_active]="DKMS module active: %s" | |
| T_EN[state_dkms_inactive]="DKMS module not registered." | |
| T_EN[state_limits_ok]="memlock limits configured at %s." | |
| T_EN[state_limits_missing]="memlock limits file %s missing." | |
| T_EN[state_udev_ok]="udev rule present at %s." | |
| T_EN[state_udev_missing]="udev rule %s missing." | |
| T_EN[state_rc_ok]="npu-env helper present in %s." | |
| T_EN[state_rc_missing]="npu-env helper not in any shell rc." | |
| T_EN[state_dev_perms_ok]="/dev/accel/accel0 mode is %s (udev rule applied)." | |
| T_EN[state_dev_perms_warn]="/dev/accel/accel0 mode is %s (expected 666 — try: sudo udevadm control --reload-rules && sudo udevadm trigger)." | |
| T_EN[state_all_ok]="Everything is already installed and configured." | |
| T_EN[state_partial]="Partial installation detected — re-running will fill the gaps." | |
| T_EN[step_deps]="Install DKMS (if missing)" | |
| T_EN[step_clone]="Clone xdna-driver repository (with submodules)" | |
| T_EN[step_amd_deps]="Install AMD repo-based build dependencies (xrtdeps.sh)" | |
| T_EN[step_build_xrt]="Build XRT base" | |
| T_EN[step_install_xrt]="Install XRT base .deb" | |
| T_EN[step_build_plugin]="Build amdxdna plugin" | |
| T_EN[step_install_plugin]="Install amdxdna plugin .deb (registers DKMS)" | |
| T_EN[step_memlock]="Configure memlock limits and apply to current session" | |
| T_EN[step_shell]="Configure shell helper (npu-env)" | |
| T_EN[step_verify]="Verify post-install (module path, device permissions, udev rule)" | |
| T_EN[step_validate]="Activate XRT and run validation tests" | |
| T_EN[skip_xrt]="xrt-base already installed — skipping build and install." | |
| T_EN[skip_plugin]="xrt_plugin-amdxdna already installed — skipping build and install." | |
| T_EN[skip_limits]="memlock limits already configured — skipping." | |
| T_EN[skip_rc]="npu-env helper already present — skipping." | |
| T_EN[final_title]="Final verification" | |
| T_EN[final_ok]="Installation complete. The NPU is operational." | |
| T_EN[final_partial]="Installation finished with warnings — review the messages above." | |
| T_EN[useful_title]="Useful commands" | |
| T_EN[useful_activate]="Activate XRT in a new terminal:" | |
| T_EN[useful_examine]="Show NPU info and detected devices:" | |
| T_EN[useful_validate]="Run validation tests (gemm, latency, throughput):" | |
| T_EN[useful_turbo]="Switch NPU to performance (turbo) mode:" | |
| T_EN[useful_status]="Check DKMS module after a kernel update:" | |
| T_EN[useful_uninstall]="Uninstall everything:" | |
| T_EN[uninstall_confirm]="This will remove XRT, the plugin, DKMS module, limits, udev rule and the npu-env helper. Continue?" | |
| T_EN[uninstall_done]="Uninstall complete. Reboot recommended." | |
| # --- Spanish strings --- | |
| T_ES[help_summary]="Instalador del driver NPU AMD Ryzen AI (XRT + amdxdna)." | |
| T_ES[help_options]="Opciones:" | |
| T_ES[help_lang]=" --es, --spanish Mostrar mensajes en español" | |
| T_ES[help_dry]=" --dry-run Imprimir acciones sin ejecutarlas" | |
| T_ES[help_skip_tests]=" --skip-tests No ejecutar xrt-smi validate al final" | |
| T_ES[help_no_cleanup]=" --no-cleanup Conservar el directorio de build tras instalar" | |
| T_ES[help_yes]=" -y, --yes Responder sí a todas las preguntas" | |
| T_ES[help_check]=" --check Solo mostrar el estado actual de la instalación" | |
| T_ES[help_uninstall]=" --uninstall Desinstalar el stack NPU" | |
| T_ES[help_build_dir]=" --build-dir <ruta> Usar un directorio de build distinto (por defecto: ~/npu-build)" | |
| T_ES[help_log_file]=" --log-file <ruta> Escribir log con timestamps en <ruta> (por defecto: /tmp/install-npu-amd-<ts>.log)" | |
| T_ES[help_no_log]=" --no-log Desactivar el log a fichero" | |
| T_ES[help_help]=" -h, --help Mostrar esta ayuda" | |
| T_ES[help_version]=" --version Mostrar versión del script" | |
| T_ES[intro_what]="¿Qué es este driver?" | |
| T_ES[intro_what_body]="El driver amdxdna expone la NPU (XDNA / AI Engine) integrada en los procesadores AMD Ryzen AI como un dispositivo Linux DRM accel. XRT (AMD/Xilinx RunTime) es el runtime userspace que se comunica con él. Juntos permiten acelerar inferencia de IA por hardware (~50 TOPS INT8 en Strix Point)." | |
| T_ES[intro_what_will]="Qué hará este script" | |
| T_ES[intro_steps]=" - Comprobaciones previas: hardware, kernel, red, disco, estado actual.\n - Instalar DKMS via apt (si falta).\n - Clonar el repositorio xdna-driver de AMD.\n - Ejecutar xrtdeps.sh de AMD para instalar deps de build del repo.\n - Compilar XRT (~5–10 min) e instalar el .deb base.\n - Compilar el plugin amdxdna (~1–3 min) e instalarlo (DKMS).\n - Configurar el límite memlock (la regla udev la añade el postinst del plugin).\n - Añadir la función 'npu-env' a tu shell rc.\n - Verificar ruta del módulo, permisos del device y regla udev.\n - Activar XRT y ejecutar tests de validación." | |
| T_ES[intro_disk]="Disco: ~4 GB durante el build, ~70 MB tras limpieza. Red: necesaria (clone + apt)." | |
| T_ES[intro_sudo]="El script solicitará sudo solo cuando haga falta y lo cachea durante toda la ejecución." | |
| T_ES[ask_proceed]="¿Proceder con la instalación?" | |
| T_ES[ask_continue]="¿Continuar?" | |
| T_ES[abort_user]="Cancelado por el usuario." | |
| T_ES[prompt_sudo]="Solicitando credenciales sudo (se cachean durante el resto del script)..." | |
| T_ES[check_title]="Comprobaciones previas" | |
| T_ES[check_arch_ok]="Arquitectura: x86_64." | |
| T_ES[check_arch_fail]="Arquitectura no soportada: %s. Solo se soporta x86_64." | |
| T_ES[check_distro_ok]="Distribución: %s %s (basada en apt)." | |
| T_ES[check_distro_fail]="Distribución no soportada: %s. Solo Debian/Ubuntu." | |
| T_ES[check_kernel]="Kernel: %s" | |
| T_ES[check_kmod_ok]="Módulo amdxdna disponible en el kernel (CONFIG_DRM_ACCEL_AMDXDNA=m)." | |
| T_ES[check_kmod_fail]="No se encuentra soporte de amdxdna en el kernel. Se necesita kernel >= 6.14 con CONFIG_DRM_ACCEL_AMDXDNA habilitado." | |
| T_ES[check_headers_ok]="linux-headers-%s instalado (DKMS puede compilar módulos)." | |
| T_ES[check_headers_fail]="linux-headers-%s no instalado. DKMS fallaría al compilar el módulo amdxdna tras una compilación larga de XRT. Instálalo antes con: sudo apt install linux-headers-%s" | |
| T_ES[check_cpu_ok]="CPU detectada: %s" | |
| T_ES[check_cpu_warn]="La CPU no parece Ryzen AI / Strix / Phoenix / Hawk Point. Se continúa de todas formas." | |
| T_ES[check_dev_ok]="Dispositivo NPU presente en /dev/accel/accel0." | |
| T_ES[check_dev_warn]="No se encuentra /dev/accel/accel0 todavía. El módulo puede necesitar cargarse tras instalar." | |
| T_ES[check_disk_ok]="Espacio libre: %s GB (mínimo 4 GB)." | |
| T_ES[check_disk_fail]="Espacio insuficiente en %s: %s GB libres, se necesitan al menos 4 GB." | |
| T_ES[check_net_ok]="Red accesible (github.com)." | |
| T_ES[check_net_fail]="No se puede acceder a github.com. Revisa tu conexión." | |
| T_ES[check_dkms_ok]="dkms instalado (%s)." | |
| T_ES[check_dkms_missing]="dkms no está instalado. Se instalará." | |
| T_ES[check_secureboot_on]="Secure Boot está ACTIVADO. DKMS firmará el módulo con la MOK del sistema; si no hay MOK enrolada, el módulo no cargará. Considera 'mokutil --list-enrolled'." | |
| T_ES[check_secureboot_off]="Secure Boot está desactivado." | |
| T_ES[state_title]="Estado actual de la instalación" | |
| T_ES[state_xrt_installed]="xrt-base instalado: %s" | |
| T_ES[state_xrt_missing]="xrt-base no instalado." | |
| T_ES[state_plugin_installed]="xrt_plugin-amdxdna instalado: %s" | |
| T_ES[state_plugin_missing]="xrt_plugin-amdxdna no instalado." | |
| T_ES[state_dkms_active]="Módulo DKMS activo: %s" | |
| T_ES[state_dkms_inactive]="Módulo DKMS no registrado." | |
| T_ES[state_limits_ok]="Límites memlock configurados en %s." | |
| T_ES[state_limits_missing]="Falta el archivo de límites %s." | |
| T_ES[state_udev_ok]="Regla udev presente en %s." | |
| T_ES[state_udev_missing]="Falta la regla udev %s." | |
| T_ES[state_rc_ok]="Función npu-env presente en %s." | |
| T_ES[state_rc_missing]="Función npu-env no encontrada en ningún shell rc." | |
| T_ES[state_dev_perms_ok]="/dev/accel/accel0 con permisos %s (regla udev aplicada)." | |
| T_ES[state_dev_perms_warn]="/dev/accel/accel0 con permisos %s (esperado 666 — prueba: sudo udevadm control --reload-rules && sudo udevadm trigger)." | |
| T_ES[state_all_ok]="Todo está ya instalado y configurado." | |
| T_ES[state_partial]="Instalación parcial detectada — reejecutar completará lo que falte." | |
| T_ES[step_deps]="Instalar DKMS (si falta)" | |
| T_ES[step_clone]="Clonar el repositorio xdna-driver (con submódulos)" | |
| T_ES[step_amd_deps]="Instalar dependencias de build del repo de AMD (xrtdeps.sh)" | |
| T_ES[step_build_xrt]="Compilar XRT base" | |
| T_ES[step_install_xrt]="Instalar el .deb base de XRT" | |
| T_ES[step_build_plugin]="Compilar el plugin amdxdna" | |
| T_ES[step_install_plugin]="Instalar el .deb del plugin amdxdna (registra DKMS)" | |
| T_ES[step_memlock]="Configurar el límite memlock y aplicarlo a la sesión actual" | |
| T_ES[step_shell]="Configurar el helper de shell (npu-env)" | |
| T_ES[step_verify]="Verificar post-instalación (ruta del módulo, permisos del device, regla udev)" | |
| T_ES[step_validate]="Activar XRT y ejecutar tests de validación" | |
| T_ES[skip_xrt]="xrt-base ya está instalado — se omite build e instalación." | |
| T_ES[skip_plugin]="xrt_plugin-amdxdna ya está instalado — se omite build e instalación." | |
| T_ES[skip_limits]="Límites memlock ya configurados — se omite." | |
| T_ES[skip_rc]="Helper npu-env ya presente — se omite." | |
| T_ES[final_title]="Verificación final" | |
| T_ES[final_ok]="Instalación completada. La NPU está operativa." | |
| T_ES[final_partial]="Instalación finalizada con avisos — revisa los mensajes anteriores." | |
| T_ES[useful_title]="Comandos útiles" | |
| T_ES[useful_activate]="Activar XRT en una terminal nueva:" | |
| T_ES[useful_examine]="Mostrar info de la NPU y dispositivos detectados:" | |
| T_ES[useful_validate]="Ejecutar tests de validación (gemm, latency, throughput):" | |
| T_ES[useful_turbo]="Pasar la NPU a modo rendimiento (turbo):" | |
| T_ES[useful_status]="Comprobar el módulo DKMS tras una actualización de kernel:" | |
| T_ES[useful_uninstall]="Desinstalar todo:" | |
| T_ES[uninstall_confirm]="Esto eliminará XRT, el plugin, el módulo DKMS, los límites, la regla udev y el helper npu-env. ¿Continuar?" | |
| T_ES[uninstall_done]="Desinstalación completa. Se recomienda reiniciar." | |
| detect_default_lang() { | |
| # Pick Spanish if any locale env var starts with "es" (es_ES, es_MX, etc.). | |
| local v="${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" | |
| if [[ "$v" =~ ^es([_.@-]|$) ]]; then | |
| echo "es" | |
| else | |
| echo "en" | |
| fi | |
| } | |
| t() { | |
| local key="$1" | |
| if [[ "$LANG_CODE" == "es" ]]; then | |
| printf "%s" "${T_ES[$key]:-${T_EN[$key]:-???$key???}}" | |
| else | |
| printf "%s" "${T_EN[$key]:-???$key???}" | |
| fi | |
| } | |
| # ============================================================================= | |
| # Helpers | |
| # ============================================================================= | |
| ask_yes_no() { | |
| local prompt="$1" | |
| local default="${2:-Y}" | |
| local yn | |
| if (( ASSUME_YES )); then | |
| return 0 | |
| fi | |
| if [[ "$default" == "Y" ]]; then | |
| printf "%s[ask]%s %s%s [Y/n]:%s " "$C_PINK" "$C_RESET" "$C_TEXT" "$prompt" "$C_RESET" | |
| else | |
| printf "%s[ask]%s %s%s [y/N]:%s " "$C_PINK" "$C_RESET" "$C_TEXT" "$prompt" "$C_RESET" | |
| fi | |
| read -r yn | |
| yn="${yn:-$default}" | |
| [[ "$yn" =~ ^[YySs]$ ]] | |
| } | |
| fmt_cmd() { | |
| # Join args with spaces for human-readable display (independent of IFS). | |
| local out="" | |
| local arg | |
| for arg in "$@"; do | |
| out+="$arg " | |
| done | |
| printf "%s" "${out% }" | |
| } | |
| run_cmd() { | |
| # Execute a command as an argv array — NO eval, no shell interpretation. | |
| # Use run_in_dir() if you need to cd before executing. | |
| log_run "$(fmt_cmd "$@")" | |
| if (( DRY_RUN )); then | |
| return 0 | |
| fi | |
| if [[ -n "${LOG_FILE:-}" ]]; then | |
| "$@" 2>&1 | tee -a "$LOG_FILE" | |
| return "${PIPESTATUS[0]}" | |
| fi | |
| "$@" | |
| } | |
| run_in_dir() { | |
| # Run a command inside a subshell after cd-ing to a directory. Avoids `cd && cmd` via eval. | |
| local dir="$1"; shift | |
| log_run "(cd $dir && $(fmt_cmd "$@"))" | |
| if (( DRY_RUN )); then | |
| return 0 | |
| fi | |
| if [[ -n "${LOG_FILE:-}" ]]; then | |
| ( cd "$dir" && "$@" ) 2>&1 | tee -a "$LOG_FILE" | |
| return "${PIPESTATUS[0]}" | |
| fi | |
| ( cd "$dir" && "$@" ) | |
| } | |
| validate_path() { | |
| # Reject user-supplied paths containing shell metacharacters. | |
| local label="$1" value="$2" | |
| if [[ -z "$value" ]]; then | |
| log_err "Empty value for $label." | |
| exit 2 | |
| fi | |
| if [[ ! "$value" =~ $PATH_REGEX ]]; then | |
| log_err "Invalid characters in $label: $value (allowed: letters, digits, . _ / ~ + : -)" | |
| exit 2 | |
| fi | |
| } | |
| init_log_file() { | |
| if (( NO_LOG )); then | |
| LOG_FILE="" | |
| return 0 | |
| fi | |
| if [[ -z "${LOG_FILE:-}" ]]; then | |
| LOG_FILE="${TMPDIR:-/tmp}/install-npu-amd-$(date +%Y%m%d-%H%M%S).log" | |
| fi | |
| # Ensure directory exists, then create/truncate the file | |
| local log_dir | |
| log_dir="$(dirname "$LOG_FILE")" | |
| mkdir -p "$log_dir" | |
| if ! : > "$LOG_FILE" 2>/dev/null; then | |
| printf "%s[warn]%s %sCould not write to %s — disabling file logging.%s\n" \ | |
| "$C_YELLOW" "$C_RESET" "$C_TEXT" "$LOG_FILE" "$C_RESET" >&2 | |
| LOG_FILE="" | |
| return 0 | |
| fi | |
| { | |
| echo "***** install-npu-amd.sh log *****" | |
| echo "Started: $(date -Iseconds)" | |
| echo "Version: $SCRIPT_VERSION" | |
| echo "Args: $ORIG_ARGS" | |
| echo "Host: $(hostname)" | |
| echo "User: $USER" | |
| echo "Kernel: $(uname -r)" | |
| echo "Machine: $(uname -m)" | |
| echo "**********************************" | |
| echo | |
| } >> "$LOG_FILE" | |
| } | |
| require_sudo() { | |
| if (( DRY_RUN )); then | |
| return 0 | |
| fi | |
| if ! sudo -n true 2>/dev/null; then | |
| log_info "$(t prompt_sudo)" | |
| sudo -v | |
| fi | |
| # Start keep-alive only once per script run, track its PID so the trap can clean it up | |
| if [[ -z "$SUDO_KEEPALIVE_PID" ]]; then | |
| ( while true; do sudo -n true; sleep 60; kill -0 "$$" 2>/dev/null || exit; done ) 2>/dev/null & | |
| SUDO_KEEPALIVE_PID=$! | |
| disown 2>/dev/null || true | |
| fi | |
| } | |
| cleanup_on_exit() { | |
| local rc=$? | |
| # Stop the sudo keep-alive subshell if still running | |
| if [[ -n "$SUDO_KEEPALIVE_PID" ]] && kill -0 "$SUDO_KEEPALIVE_PID" 2>/dev/null; then | |
| kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true | |
| wait "$SUDO_KEEPALIVE_PID" 2>/dev/null || true | |
| fi | |
| if (( rc != 0 )); then | |
| log_err "Failed at: ${CURRENT_STEP:-unknown step}" | |
| if [[ -n "${LOG_FILE:-}" ]]; then | |
| log_err "Full log saved to: $LOG_FILE" | |
| log_err "Inspect it with: tail -n 100 \"$LOG_FILE\"" | |
| fi | |
| fi | |
| return $rc | |
| } | |
| trap cleanup_on_exit EXIT | |
| trap 'log_err "Interrupted."; exit 130' INT TERM | |
| # ============================================================================= | |
| # Help / version | |
| # ============================================================================= | |
| show_help() { | |
| # Show the figlet banner if available; otherwise fall back to a plain title. | |
| # Never prompts to install figlet from --help. | |
| if command -v figlet >/dev/null 2>&1; then | |
| printf "%s" "$C_MAUVE" | |
| figlet -w 100 "NPU Setup" | |
| printf "%s\n" "$C_RESET" | |
| else | |
| log_title "***** NPU Setup *****" | |
| fi | |
| cat <<EOF | |
| $(t help_summary) | |
| Usage: $SCRIPT_NAME [OPTIONS] | |
| $(t help_options) | |
| $(t help_lang) | |
| $(t help_dry) | |
| $(t help_skip_tests) | |
| $(t help_no_cleanup) | |
| $(t help_yes) | |
| $(t help_check) | |
| $(t help_uninstall) | |
| $(t help_build_dir) | |
| $(t help_log_file) | |
| $(t help_no_log) | |
| $(t help_help) | |
| $(t help_version) | |
| EOF | |
| } | |
| show_version() { | |
| printf "%s %s\n" "$SCRIPT_NAME" "$SCRIPT_VERSION" | |
| } | |
| # ============================================================================= | |
| # Welcome banner (figlet) | |
| # ============================================================================= | |
| ensure_figlet() { | |
| if command -v figlet >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| # In read-only modes (check) skip the install prompt entirely | |
| if [[ "$ACTION" == "check" ]]; then | |
| return 1 | |
| fi | |
| log_warn "figlet is not installed." | |
| if ask_yes_no "Install figlet now (sudo apt install figlet)?" "Y"; then | |
| require_sudo | |
| run_cmd sudo apt-get install -y figlet | |
| else | |
| log_info "Continuing without figlet banner." | |
| return 1 | |
| fi | |
| } | |
| show_welcome() { | |
| if ensure_figlet; then | |
| printf "%s" "$C_MAUVE" | |
| figlet -w 100 "NPU Setup" | |
| printf "%s\n" "$C_RESET" | |
| else | |
| log_title "***** NPU Setup *****" | |
| fi | |
| log_info "Version: $SCRIPT_VERSION | Language: $LANG_CODE | Action: $ACTION" | |
| if (( DRY_RUN )); then | |
| log_warn "Dry-run mode — no changes will be made." | |
| fi | |
| } | |
| # ============================================================================= | |
| # Introduction shown before user confirms | |
| # ============================================================================= | |
| show_introduction() { | |
| log_title "$(t intro_what)" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t intro_what_body)" "$C_RESET" | |
| log_title "$(t intro_what_will)" | |
| # printf '%b' lets the embedded \n in the i18n string render correctly | |
| printf "%s%b%s\n" "$C_TEXT" "$(t intro_steps)" "$C_RESET" | |
| printf "\n" | |
| log_info "$(t intro_disk)" | |
| log_info "$(t intro_sudo)" | |
| printf "\n" | |
| } | |
| # ============================================================================= | |
| # Pre-flight checks | |
| # ============================================================================= | |
| preflight_checks() { | |
| log_step "$(t check_title)" | |
| local arch distro_id distro_ver kernel cpu free_gb | |
| arch="$(uname -m)" | |
| if [[ "$arch" != "x86_64" ]]; then | |
| log_err "$(printf "$(t check_arch_fail)" "$arch")" | |
| exit 1 | |
| fi | |
| log_ok "$(t check_arch_ok)" | |
| if [[ ! -r /etc/os-release ]]; then | |
| log_err "Cannot read /etc/os-release." | |
| exit 1 | |
| fi | |
| # Parse without sourcing to avoid executing arbitrary code from a system file. | |
| distro_id="$(grep -E '^ID=' /etc/os-release | head -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" | |
| distro_ver="$(grep -E '^VERSION_ID=' /etc/os-release | head -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" | |
| distro_ver="${distro_ver:-unknown}" | |
| case "$distro_id" in | |
| debian|ubuntu|linuxmint) | |
| log_ok "$(printf "$(t check_distro_ok)" "$distro_id" "$distro_ver")" ;; | |
| *) | |
| log_err "$(printf "$(t check_distro_fail)" "$distro_id")" | |
| exit 1 ;; | |
| esac | |
| kernel="$(uname -r)" | |
| log_info "$(printf "$(t check_kernel)" "$kernel")" | |
| local kconfig="/boot/config-$kernel" | |
| if [[ -r "$kconfig" ]] && grep -q "^CONFIG_DRM_ACCEL_AMDXDNA=" "$kconfig"; then | |
| log_ok "$(t check_kmod_ok)" | |
| elif [[ -r "/proc/config.gz" ]] && zcat /proc/config.gz 2>/dev/null | grep -q "^CONFIG_DRM_ACCEL_AMDXDNA="; then | |
| log_ok "$(t check_kmod_ok)" | |
| else | |
| log_err "$(t check_kmod_fail)" | |
| exit 1 | |
| fi | |
| # Kernel headers check — DKMS needs them to build the amdxdna module during the plugin postinst. | |
| # Without this gate, the user discovers the failure after a 5–10 min XRT compile. | |
| if dpkg-query -W -f='${Status}' "linux-headers-$kernel" 2>/dev/null | grep -q "install ok installed"; then | |
| log_ok "$(printf "$(t check_headers_ok)" "$kernel")" | |
| else | |
| log_err "$(printf "$(t check_headers_fail)" "$kernel" "$kernel")" | |
| exit 1 | |
| fi | |
| cpu="$(awk -F: '/^model name/ {print $2; exit}' /proc/cpuinfo | sed 's/^[ \t]*//')" | |
| log_ok "$(printf "$(t check_cpu_ok)" "$cpu")" | |
| if ! echo "$cpu" | grep -qiE "Ryzen AI|Strix|Phoenix|Hawk|Krackan"; then | |
| log_warn "$(t check_cpu_warn)" | |
| fi | |
| if [[ -e /dev/accel/accel0 ]]; then | |
| log_ok "$(t check_dev_ok)" | |
| else | |
| log_warn "$(t check_dev_warn)" | |
| fi | |
| free_gb="$(df -BG --output=avail "$HOME" | tail -n1 | tr -dc '0-9')" | |
| if (( free_gb < 4 )); then | |
| log_err "$(printf "$(t check_disk_fail)" "$HOME" "$free_gb")" | |
| exit 1 | |
| fi | |
| log_ok "$(printf "$(t check_disk_ok)" "$free_gb")" | |
| if curl -fsSL --connect-timeout 5 https://github.com >/dev/null 2>&1; then | |
| log_ok "$(t check_net_ok)" | |
| else | |
| log_err "$(t check_net_fail)" | |
| exit 1 | |
| fi | |
| if dpkg-query -W -f='${Version}' dkms >/dev/null 2>&1; then | |
| local dkms_ver | |
| dkms_ver="$(dpkg-query -W -f='${Version}' dkms)" | |
| log_ok "$(printf "$(t check_dkms_ok)" "$dkms_ver")" | |
| else | |
| log_warn "$(t check_dkms_missing)" | |
| fi | |
| if [[ -d /sys/firmware/efi ]] && command -v mokutil >/dev/null 2>&1 \ | |
| && mokutil --sb-state 2>/dev/null | grep -q "SecureBoot enabled"; then | |
| log_warn "$(t check_secureboot_on)" | |
| else | |
| log_info "$(t check_secureboot_off)" | |
| fi | |
| } | |
| # ============================================================================= | |
| # Detect current install state | |
| # ============================================================================= | |
| detect_state() { | |
| log_step "$(t state_title)" | |
| local missing=0 | |
| local xrt_ver plugin_ver dkms_state target_rc | |
| if xrt_ver="$(dpkg-query -W -f='${Version}' xrt-base 2>/dev/null)"; then | |
| log_ok "$(printf "$(t state_xrt_installed)" "$xrt_ver")" | |
| XRT_INSTALLED=1 | |
| else | |
| log_info "$(t state_xrt_missing)" | |
| XRT_INSTALLED=0 | |
| missing=$((missing+1)) | |
| fi | |
| if plugin_ver="$(dpkg-query -W -f='${Version}' xrt_plugin-amdxdna 2>/dev/null)"; then | |
| log_ok "$(printf "$(t state_plugin_installed)" "$plugin_ver")" | |
| PLUGIN_INSTALLED=1 | |
| else | |
| log_info "$(t state_plugin_missing)" | |
| PLUGIN_INSTALLED=0 | |
| missing=$((missing+1)) | |
| fi | |
| if command -v dkms >/dev/null 2>&1 \ | |
| && dkms_state="$(dkms status "$DKMS_MODULE" 2>/dev/null | head -n1)" \ | |
| && [[ -n "$dkms_state" ]]; then | |
| log_ok "$(printf "$(t state_dkms_active)" "$dkms_state")" | |
| else | |
| log_info "$(t state_dkms_inactive)" | |
| fi | |
| if [[ -f "$LIMITS_FILE" ]]; then | |
| log_ok "$(printf "$(t state_limits_ok)" "$LIMITS_FILE")" | |
| LIMITS_OK=1 | |
| else | |
| log_info "$(printf "$(t state_limits_missing)" "$LIMITS_FILE")" | |
| LIMITS_OK=0 | |
| missing=$((missing+1)) | |
| fi | |
| if [[ -f "$UDEV_RULES_FILE" ]]; then | |
| log_ok "$(printf "$(t state_udev_ok)" "$UDEV_RULES_FILE")" | |
| else | |
| log_info "$(printf "$(t state_udev_missing)" "$UDEV_RULES_FILE")" | |
| fi | |
| # Device permission check: confirms the udev rule is actually applied to /dev/accel/accel0. | |
| if [[ -e /dev/accel/accel0 ]]; then | |
| local dev_mode | |
| dev_mode="$(stat -c '%a' /dev/accel/accel0 2>/dev/null || echo "?")" | |
| if [[ "$dev_mode" == "666" ]]; then | |
| log_ok "$(printf "$(t state_dev_perms_ok)" "$dev_mode")" | |
| else | |
| log_warn "$(printf "$(t state_dev_perms_warn)" "$dev_mode")" | |
| fi | |
| fi | |
| target_rc="$(detect_shell_rc)" | |
| if [[ -n "$target_rc" ]] && grep -Fq "npu-env()" "$target_rc" 2>/dev/null; then | |
| log_ok "$(printf "$(t state_rc_ok)" "$target_rc")" | |
| RC_OK=1 | |
| else | |
| log_info "$(t state_rc_missing)" | |
| RC_OK=0 | |
| missing=$((missing+1)) | |
| fi | |
| if (( missing == 0 )); then | |
| log_ok "$(t state_all_ok)" | |
| else | |
| log_info "$(t state_partial)" | |
| fi | |
| } | |
| detect_shell_rc() { | |
| # Prefer the rc matching the user's $SHELL; fall back by existence. | |
| local shell_basename | |
| shell_basename="$(basename "${SHELL:-}")" | |
| case "$shell_basename" in | |
| zsh) echo "$HOME/.zshrc" ;; | |
| bash) echo "$HOME/.bashrc" ;; | |
| *) | |
| if [[ -f "$HOME/.bashrc" ]]; then echo "$HOME/.bashrc" | |
| elif [[ -f "$HOME/.zshrc" ]]; then echo "$HOME/.zshrc" | |
| else echo "$HOME/.bashrc"; fi | |
| ;; | |
| esac | |
| } | |
| # ============================================================================= | |
| # Installation steps | |
| # ============================================================================= | |
| step_install_deps() { | |
| CURRENT_STEP="install-deps" | |
| log_step "$(t step_deps)" | |
| require_sudo | |
| if ! command -v dkms >/dev/null 2>&1; then | |
| run_cmd sudo apt-get update | |
| run_cmd sudo apt-get install -y dkms | |
| fi | |
| # Repo-based deps (libpybind11-dev, libeigen3-dev, etc.) are installed by step_run_amd_deps. | |
| } | |
| step_clone_repo() { | |
| CURRENT_STEP="clone" | |
| log_step "$(t step_clone)" | |
| if [[ -d "$BUILD_DIR/xdna-driver/.git" ]]; then | |
| log_skip "Repository already cloned at $BUILD_DIR/xdna-driver" | |
| return 0 | |
| fi | |
| run_cmd mkdir -p "$BUILD_DIR" | |
| run_cmd git clone --recursive "$REPO_URL" "$BUILD_DIR/xdna-driver" | |
| } | |
| step_run_amd_deps() { | |
| CURRENT_STEP="amd-deps" | |
| log_step "$(t step_amd_deps)" | |
| require_sudo | |
| if [[ ! -f "$BUILD_DIR/xdna-driver/tools/amdxdna_deps.sh" ]]; then | |
| log_err "amdxdna_deps.sh not found — run clone first." | |
| return 1 | |
| fi | |
| run_in_dir "$BUILD_DIR/xdna-driver" sudo ./tools/amdxdna_deps.sh | |
| } | |
| step_build_xrt() { | |
| CURRENT_STEP="build-xrt" | |
| log_step "$(t step_build_xrt)" | |
| if (( XRT_INSTALLED )); then | |
| log_skip "$(t skip_xrt)" | |
| return 0 | |
| fi | |
| run_in_dir "$BUILD_DIR/xdna-driver/xrt/build" ./build.sh -npu -opt | |
| } | |
| step_install_xrt_base() { | |
| CURRENT_STEP="install-xrt-base" | |
| log_step "$(t step_install_xrt)" | |
| if (( XRT_INSTALLED )); then | |
| log_skip "$(t skip_xrt)" | |
| return 0 | |
| fi | |
| require_sudo | |
| # Glob expansion happens here in the caller, not inside run_cmd, so no eval is needed. | |
| local debs=( "$BUILD_DIR/xdna-driver/xrt/build/Release/"xrt_*-amd64-base.deb ) | |
| if (( ! DRY_RUN )) && [[ ! -e "${debs[0]}" ]]; then | |
| log_err "Expected XRT base .deb not found in $BUILD_DIR/xdna-driver/xrt/build/Release/" | |
| return 1 | |
| fi | |
| run_cmd sudo apt-get install -y "${debs[@]}" | |
| XRT_INSTALLED=1 | |
| } | |
| step_build_plugin() { | |
| CURRENT_STEP="build-plugin" | |
| log_step "$(t step_build_plugin)" | |
| if (( PLUGIN_INSTALLED )); then | |
| log_skip "$(t skip_plugin)" | |
| return 0 | |
| fi | |
| run_in_dir "$BUILD_DIR/xdna-driver/build" ./build.sh -release | |
| } | |
| step_install_plugin() { | |
| CURRENT_STEP="install-plugin" | |
| log_step "$(t step_install_plugin)" | |
| if (( PLUGIN_INSTALLED )); then | |
| log_skip "$(t skip_plugin)" | |
| return 0 | |
| fi | |
| require_sudo | |
| local debs=( "$BUILD_DIR/xdna-driver/build/Release/"xrt_plugin.*-x86_64-amdxdna.deb ) | |
| if (( ! DRY_RUN )) && [[ ! -e "${debs[0]}" ]]; then | |
| log_err "Expected amdxdna plugin .deb not found in $BUILD_DIR/xdna-driver/build/Release/" | |
| return 1 | |
| fi | |
| run_cmd sudo apt-get install -y "${debs[@]}" | |
| PLUGIN_INSTALLED=1 | |
| } | |
| step_configure_memlock() { | |
| CURRENT_STEP="memlock" | |
| log_step "$(t step_memlock)" | |
| if [[ -f "$LIMITS_FILE" ]]; then | |
| log_skip "$(t skip_limits)" | |
| else | |
| require_sudo | |
| log_run "write memlock limits to $LIMITS_FILE" | |
| if (( ! DRY_RUN )); then | |
| printf '* soft memlock unlimited\n* hard memlock unlimited\n' \ | |
| | sudo tee "$LIMITS_FILE" >/dev/null | |
| fi | |
| LIMITS_OK=1 | |
| fi | |
| # Apply to the current shell so xrt-smi can run right after | |
| if (( ! DRY_RUN )); then | |
| sudo prlimit --pid $$ --memlock=unlimited:unlimited 2>/dev/null || \ | |
| log_warn "Could not raise memlock for current shell — open a new login session if xrt-smi reports mmap errors." | |
| fi | |
| } | |
| step_configure_shell() { | |
| CURRENT_STEP="shell-rc" | |
| log_step "$(t step_shell)" | |
| local rc | |
| rc="$(detect_shell_rc)" | |
| if grep -Fq "npu-env()" "$rc" 2>/dev/null; then | |
| log_skip "$(t skip_rc)" | |
| return 0 | |
| fi | |
| if (( ! DRY_RUN )) && [[ -f "$rc" ]]; then | |
| cp -a "$rc" "${rc}.bak.$(date +%Y%m%d-%H%M%S)" | |
| log_info "Backed up $rc" | |
| fi | |
| log_run "append npu-env() helper to $rc" | |
| if (( ! DRY_RUN )); then | |
| { | |
| printf '\n%s\n' "$RC_MARKER" | |
| printf 'npu-env() { source %s/setup.sh; }\n' "$XRT_PREFIX" | |
| } >> "$rc" | |
| fi | |
| log_ok "Updated $rc" | |
| RC_OK=1 | |
| } | |
| step_post_install_verify() { | |
| # Explicit verification of: DKMS module path, /dev/accel/accel0 permissions, udev rule. | |
| CURRENT_STEP="post-verify" | |
| log_step "$(t step_verify)" | |
| local modfile mode | |
| if command -v modinfo >/dev/null 2>&1; then | |
| modfile="$(modinfo amdxdna 2>/dev/null | awk '/^filename:/ {print $2; exit}')" | |
| if [[ -z "$modfile" ]]; then | |
| log_warn "modinfo amdxdna returned no filename — module may not be loadable." | |
| elif [[ "$modfile" == *"/updates/dkms/"* ]]; then | |
| log_ok "amdxdna module loaded from DKMS: $modfile" | |
| else | |
| log_warn "amdxdna found at $modfile (expected DKMS path /updates/dkms/)." | |
| fi | |
| fi | |
| if [[ -e /dev/accel/accel0 ]]; then | |
| mode="$(stat -c '%a' /dev/accel/accel0 2>/dev/null || echo "?")" | |
| if [[ "$mode" == "666" ]]; then | |
| log_ok "/dev/accel/accel0 present, mode 0666 (udev rule applied)." | |
| else | |
| log_warn "/dev/accel/accel0 present but mode is $mode (expected 666 from udev rule). You may need: sudo udevadm control --reload-rules && sudo udevadm trigger" | |
| fi | |
| else | |
| log_warn "/dev/accel/accel0 missing — the kernel module may not be loaded." | |
| fi | |
| if [[ -f "$UDEV_RULES_FILE" ]]; then | |
| log_ok "udev rule present: $UDEV_RULES_FILE" | |
| else | |
| log_warn "udev rule missing: $UDEV_RULES_FILE" | |
| fi | |
| } | |
| step_run_validation() { | |
| CURRENT_STEP="validate" | |
| log_step "$(t step_validate)" | |
| if (( SKIP_TESTS )); then | |
| log_skip "Tests skipped (--skip-tests)." | |
| return 0 | |
| fi | |
| # shellcheck disable=SC1091 | |
| if (( ! DRY_RUN )); then | |
| # Activate XRT in this shell (variables don't persist after the script ends, that's fine). | |
| source "$XRT_PREFIX/setup.sh" >/dev/null 2>&1 || true | |
| fi | |
| run_cmd "$XRT_PREFIX/bin/xrt-smi" --version | |
| run_cmd "$XRT_PREFIX/bin/xrt-smi" examine | |
| run_cmd "$XRT_PREFIX/bin/xrt-smi" validate | |
| } | |
| # ============================================================================= | |
| # Final summary | |
| # ============================================================================= | |
| show_useful_commands() { | |
| log_title "$(t useful_title)" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t useful_activate)" "$C_RESET" | |
| printf " npu-env\n\n" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t useful_examine)" "$C_RESET" | |
| printf " xrt-smi examine\n\n" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t useful_validate)" "$C_RESET" | |
| printf " xrt-smi validate\n\n" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t useful_turbo)" "$C_RESET" | |
| printf " sudo xrt-smi configure --pmode turbo\n\n" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t useful_status)" "$C_RESET" | |
| printf " sudo dkms status %s\n sudo modinfo amdxdna | grep filename\n\n" "$DKMS_MODULE" | |
| printf "%s%s%s\n" "$C_TEXT" "$(t useful_uninstall)" "$C_RESET" | |
| printf " %s --uninstall\n" "$SCRIPT_NAME" | |
| } | |
| show_final_summary() { | |
| log_step "$(t final_title)" | |
| if (( XRT_INSTALLED && PLUGIN_INSTALLED && LIMITS_OK && RC_OK )); then | |
| log_ok "$(t final_ok)" | |
| else | |
| log_warn "$(t final_partial)" | |
| fi | |
| show_useful_commands | |
| if (( ! NO_CLEANUP )) && [[ -d "$BUILD_DIR" ]]; then | |
| printf "\n" | |
| if ask_yes_no "Remove build directory ($BUILD_DIR, ~3 GB)? DKMS keeps the sources separately, so this is safe." "Y"; then | |
| run_cmd rm -rf "$BUILD_DIR" | |
| log_ok "Build directory cleaned." | |
| fi | |
| fi | |
| if [[ -n "${LOG_FILE:-}" ]]; then | |
| printf "\n" | |
| log_info "Full log written to: $LOG_FILE" | |
| fi | |
| } | |
| # ============================================================================= | |
| # Uninstall | |
| # ============================================================================= | |
| do_uninstall() { | |
| log_step "Uninstall" | |
| if ! ask_yes_no "$(t uninstall_confirm)" "N"; then | |
| log_info "$(t abort_user)" | |
| exit 0 | |
| fi | |
| require_sudo | |
| # Only call apt-get purge for packages that are actually installed. | |
| if dpkg-query -W -f='${Version}' xrt_plugin-amdxdna >/dev/null 2>&1; then | |
| run_cmd sudo apt-get purge -y xrt_plugin-amdxdna | |
| fi | |
| if dpkg-query -W -f='${Version}' xrt-base >/dev/null 2>&1; then | |
| run_cmd sudo apt-get purge -y xrt-base | |
| fi | |
| run_cmd sudo rm -f "$LIMITS_FILE" "$UDEV_RULES_FILE" | |
| run_cmd sudo rm -rf "$XRT_PREFIX" | |
| local rc marker_escaped | |
| rc="$(detect_shell_rc)" | |
| if [[ -f "$rc" ]] && grep -Fq "npu-env()" "$rc"; then | |
| # Escape characters that are special to sed BRE in the marker. | |
| marker_escaped="$(printf '%s' "$RC_MARKER" | sed 's/[\/&.*[^$]/\\&/g')" | |
| run_cmd sed -i.bak "/$marker_escaped/,/^npu-env/d" "$rc" | |
| log_ok "Removed npu-env helper from $rc (backup at ${rc}.bak)" | |
| fi | |
| log_ok "$(t uninstall_done)" | |
| } | |
| # ============================================================================= | |
| # Argument parsing | |
| # ============================================================================= | |
| parse_args() { | |
| # Auto-detect locale first so --help/--version respect $LANG when no explicit flag is given. | |
| if [[ -z "$LANG_CODE" ]]; then | |
| LANG_CODE="$(detect_default_lang)" | |
| fi | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -h|--help) show_help; exit 0 ;; | |
| --version) show_version; exit 0 ;; | |
| --es|--spanish) LANG_CODE="es" ;; | |
| --en|--english) LANG_CODE="en" ;; | |
| --dry-run) DRY_RUN=1 ;; | |
| --skip-tests) SKIP_TESTS=1 ;; | |
| --no-cleanup) NO_CLEANUP=1 ;; | |
| -y|--yes) ASSUME_YES=1 ;; | |
| --check) ACTION="check" ;; | |
| --uninstall) ACTION="uninstall" ;; | |
| --build-dir) shift; validate_path "--build-dir" "${1:-}"; BUILD_DIR="$1" ;; | |
| --log-file) shift; validate_path "--log-file" "${1:-}"; LOG_FILE="$1" ;; | |
| --no-log) NO_LOG=1 ;; | |
| *) | |
| log_err "Unknown option: $1" | |
| show_help | |
| exit 2 ;; | |
| esac | |
| shift | |
| done | |
| } | |
| # ============================================================================= | |
| # Main | |
| # ============================================================================= | |
| main() { | |
| parse_args "$@" | |
| # Resolve language: explicit flag wins, else auto-detect from locale, else English. | |
| if [[ -z "$LANG_CODE" ]]; then | |
| LANG_CODE="$(detect_default_lang)" | |
| fi | |
| init_log_file | |
| # Globals tracking install state (set by detect_state, used by skip logic) | |
| XRT_INSTALLED=0 | |
| PLUGIN_INSTALLED=0 | |
| LIMITS_OK=0 | |
| RC_OK=0 | |
| show_welcome | |
| if [[ -n "${LOG_FILE:-}" ]]; then | |
| log_info "Logging to: $LOG_FILE" | |
| fi | |
| case "$ACTION" in | |
| check) | |
| preflight_checks | |
| detect_state | |
| exit 0 | |
| ;; | |
| uninstall) | |
| do_uninstall | |
| exit 0 | |
| ;; | |
| install) | |
| show_introduction | |
| if ! ask_yes_no "$(t ask_proceed)" "Y"; then | |
| log_info "$(t abort_user)" | |
| exit 0 | |
| fi | |
| preflight_checks | |
| detect_state | |
| if (( XRT_INSTALLED && PLUGIN_INSTALLED && LIMITS_OK && RC_OK )); then | |
| log_ok "$(t state_all_ok)" | |
| if ! ask_yes_no "Run validation tests anyway?" "Y"; then | |
| exit 0 | |
| fi | |
| step_run_validation | |
| show_useful_commands | |
| exit 0 | |
| fi | |
| require_sudo | |
| step_install_deps | |
| step_clone_repo | |
| step_run_amd_deps | |
| step_build_xrt | |
| step_install_xrt_base | |
| step_build_plugin | |
| step_install_plugin | |
| step_configure_memlock | |
| step_configure_shell | |
| step_post_install_verify | |
| step_run_validation | |
| show_final_summary | |
| ;; | |
| esac | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment