diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index f014457..de11a10 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -30,6 +30,7 @@ on: - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" - "tests/java-memory.sh" + - "tests/gpu-pci-class.sh" - "tests/nvidia-version-pin.sh" - "tests/external-installer.sh" - "tests/forge-version-key.sh" @@ -88,6 +89,7 @@ on: - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" - "tests/java-memory.sh" + - "tests/gpu-pci-class.sh" - "tests/nvidia-version-pin.sh" - "tests/external-installer.sh" - "tests/forge-version-key.sh" @@ -170,6 +172,9 @@ jobs: - name: Java memory drop-in tests run: bash tests/java-memory.sh + - name: GPU detection by PCI class tests + run: bash tests/gpu-pci-class.sh + - name: NVIDIA userspace version pin tests run: bash tests/nvidia-version-pin.sh diff --git a/api/sysinfo.func b/api/sysinfo.func index 32b7fc2..6f30b9d 100644 --- a/api/sysinfo.func +++ b/api/sysinfo.func @@ -125,7 +125,8 @@ detect_gpu() { GPU_PASSTHROUGH="unknown" local gpu_line - gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1 || true) + # The class field only: the whole line matched "Device 43d0" and "SanDisk Ultra 3D". + gpu_line=$(lspci 2>/dev/null | grep -E '^[^ ]+ (VGA compatible|XGA compatible|3D|Display) controller[ :]' | head -1 || true) if [[ -n "$gpu_line" ]]; then GPU_MODEL=$(echo "$gpu_line" | sed 's/.*: //' | sed 's/ (rev .*)$//' | cut -c1-64) diff --git a/incus/backend.func b/incus/backend.func index c880d27..70a25c9 100644 --- a/incus/backend.func +++ b/incus/backend.func @@ -662,7 +662,7 @@ incus_detect_gpu_devices() { elif grep -q '\[10de:' <<<"$line"; then NVIDIA_GPUS+=("pci=${pci}") fi - done < <(lspci -nn -D 2>/dev/null | grep -E 'VGA|Display|3D' || true) + done < <(lspci -nn -D 2>/dev/null | grep -E '^[^ ]+ (VGA compatible|XGA compatible|3D|Display) controller[ :]' || true) # DRM sysfs fallback (useful on ARM / non-PCI GPUs) if [[ ${#INTEL_GPUS[@]} -eq 0 && ${#AMD_GPUS[@]} -eq 0 && ${#NVIDIA_GPUS[@]} -eq 0 && -d /sys/class/drm ]]; then diff --git a/lib/hwaccel.func b/lib/hwaccel.func index 6a830ec..9f2cf1f 100644 --- a/lib/hwaccel.func +++ b/lib/hwaccel.func @@ -136,7 +136,8 @@ setup_hwaccel() { GPU_NAMES+=("$gpu_name") ((gpu_count++)) || true fi - done < <(lspci 2>/dev/null | grep -Ei 'vga|3d|display') + # The class field only: the whole line matched "Device 43d0" and "SanDisk Ultra 3D". + done < <(lspci 2>/dev/null | grep -E '^[^ ]+ (VGA compatible|XGA compatible|3D|Display) controller[ :]') # Check for AMD APU via CPU vendor if no discrete GPU found local cpu_vendor diff --git a/pve/backend.func b/pve/backend.func index cc53a90..da4bd73 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -788,7 +788,7 @@ $PCT_OPTIONS_STRING" # Store PCI info to avoid multiple calls local pci_vga_info - pci_vga_info=$(lspci -nn 2>/dev/null | grep -E "VGA|Display|3D" || true) + pci_vga_info=$(lspci -nn 2>/dev/null | grep -E '^[^ ]+ (VGA compatible|XGA compatible|3D|Display) controller[ :]' || true) if [[ -z "$pci_vga_info" ]]; then msg_debug "No VGA/Display/3D PCI devices found" diff --git a/tests/gpu-pci-class.sh b/tests/gpu-pci-class.sh new file mode 100644 index 0000000..b584ef1 --- /dev/null +++ b/tests/gpu-pci-class.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +PATTERN='^[^ ]+ (VGA compatible|XGA compatible|3D|Display) controller[ :]' +passed=0 failed=0 + +check() { + if [[ "$3" == "$2" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + failed=$((failed + 1)) + fi +} + +for f in lib/hwaccel.func api/sysinfo.func pve/backend.func incus/backend.func; do + check "$f filters lspci on the class field" 1 "$(grep -F "lspci" "$CORE_DIR/$f" | grep -cF "grep -E '$PATTERN'")" +done + +# From issue #217 and telemetry gpu_model values; the slot says what each is. +lines=( + "02:00.0 USB controller: Advanced Micro Devices, Inc. [AMD] Device 43d0 (rev 01)" + "03:00.0 Non-Volatile memory controller: Sandisk Corp SanDisk Ultra 3D / WD PC SN530, IX SN530, Blue SN550 NVMe SSD (rev 01)" + "04:00.0 Non-Volatile memory controller: Realtek Semiconductor Co., Ltd. RTS5763DL x2 NVMe SSD Controller (rev 01)" + "00:03.0 PCI bridge: Intel Corporation Xeon E5/Core i7 IIO PCI Express Root Port 3d (rev 07)" + "0b:00.1 Audio device: NVIDIA Corporation GA104 High Definition Audio Controller (rev a1)" + "0b:00.0 VGA compatible controller: NVIDIA Corporation GA104 [GeForce RTX 3070] (rev a1)" + "0e:00.0 VGA compatible controller: Intel Corporation Battlemage G21 [Intel Graphics]" + "01:00.0 3D controller: NVIDIA Corporation GP104GL [Tesla P4] (rev a1)" + "00:02.0 Display controller: Intel Corporation HD Graphics 630 (rev 04)" +) +check "plain lspci: only display controllers" "0b:00.0 0e:00.0 01:00.0 00:02.0" \ + "$(printf '%s\n' "${lines[@]}" | grep -E "$PATTERN" | awk '{print $1}' | xargs)" + +nn=( + "02:00.0 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Device [1022:43d0] (rev 01)" + "0b:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3070] [10de:2484] (rev a1)" + "0000:0e:00.0 VGA compatible controller [0300]: Intel Corporation Battlemage G21 [Intel Graphics] [8086:e20b]" +) +check "lspci -nn and -D: only display controllers" "0b:00.0 0000:0e:00.0" \ + "$(printf '%s\n' "${nn[@]}" | grep -E "$PATTERN" | awk '{print $1}' | xargs)" + +printf '%s passed, %s failed\n' "$passed" "$failed" +((failed == 0))