From ba2cac07851a6e9dee6e718de1ec48e2d39718ff Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 21:45:57 +0200 Subject: [PATCH 01/10] Change find_igpu function to take into account when multiple AMD GPUs are found on the same device. --- src/adjustor/fuse/gpu.py | 25 +---------- src/adjustor/fuse/utils.py | 89 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 28 deletions(-) diff --git a/src/adjustor/fuse/gpu.py b/src/adjustor/fuse/gpu.py index 659a060..76af1e8 100644 --- a/src/adjustor/fuse/gpu.py +++ b/src/adjustor/fuse/gpu.py @@ -3,7 +3,7 @@ from typing import Literal, NamedTuple from typing import Sequence -from adjustor.fuse.utils import find_igpu as find_amd_igpu +from adjustor.fuse.utils import find_amd_igpu, find_intel_igpu logger = logging.getLogger(__name__) GPU_FREQUENCY_PATH = "device/pp_od_clk_voltage" @@ -39,29 +39,6 @@ class GPUStatus(NamedTuple): epp: EppStatus | None -def find_intel_igpu(): - for hw in os.listdir("/sys/class/drm"): - if not hw.startswith("card"): - continue - if not os.path.exists(f"/sys/class/drm/{hw}/device/subsystem_vendor"): - continue - with open(f"/sys/class/drm/{hw}/device/subsystem_vendor", "r") as f: - # intel - if "1462" not in f.read(): - continue - - if not os.path.exists(f"/sys/class/drm/{hw}/device/local_cpulist"): - logger.warning( - f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' - ) - continue - - pth = os.path.realpath(os.path.join("/sys/class/drm", hw)) - return pth - - return None - - def get_igpu_status(): hwmon = find_intel_igpu() if not hwmon: diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index 3466599..7d9c05e 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -3,14 +3,70 @@ import sys import time from threading import Event, Thread +import subprocess +from typing import Optional logger = logging.getLogger(__name__) TDP_MOUNT = "/run/hhd-tdp/hwmon" FUSE_MOUNT_SOCKET = "/run/hhd-tdp/socket" +AMD_DGPU_KEYWORDS = ['navi', 'vega', 'polaris'] -def find_igpu(): +import os +import subprocess +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + +AMD_DGPU_KEYWORDS = [ + "navi", "radeon", "rx", "vega", "5700", "6600", "6800", "6900", "7900" +] + +def _is_amd_dgpu(pci_address: str) -> bool: + try: + result = subprocess.run( + ['lspci', '-s', pci_address], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True + ) + output = result.stdout.decode().strip().lower() + logger.debug(f"lspci output for {pci_address}: {output}") + + if any(keyword in output for keyword in AMD_DGPU_KEYWORDS): + logger.info(f"Detected AMD dGPU at {pci_address}") + return True + + except subprocess.CalledProcessError as e: + logger.error( + f"Error decoding PCI address for GPU {pci_address}: " + f"{e.stderr.decode().strip()}" + ) + + return False + + +def _extract_pci_address(hwmon_path: str) -> Optional[str]: + def _is_valid_pci_address(segment: str) -> bool: + return ( + len(segment) == 12 + and segment[4] == ':' + and segment[7] == ':' + and segment[10] == '.' + ) + + try: + segments = reversed(os.path.realpath(hwmon_path).split('/')) + return next((seg for seg in segments if _is_valid_pci_address(seg)), None) + + except Exception as e: + logger.error(f"Error extracting PCI address for {hwmon_path}: {e}") + return None + + +def find_amd_igpu(): for hw in os.listdir("/sys/class/hwmon"): if not hw.startswith("hwmon"): continue @@ -30,8 +86,10 @@ def find_igpu(): ) continue - pth = os.path.realpath(os.path.join("/sys/class/hwmon", hw)) - return pth + if not _is_amd_dgpu(_extract_pci_address(f"/sys/class/hwmon/{hw}")): + pth = os.path.realpath(os.path.join("/sys/class/hwmon", hw)) + logger.info(f"Found AMD iGPU at:\n'{pth}'") + return pth logger.error("No iGPU found. Binding TDP attributes will not be possible.") return None @@ -39,7 +97,7 @@ def find_igpu(): def prepare_tdp_mount(debug: bool = False, passhtrough: bool = False): try: - gpu = find_igpu() + gpu = find_amd_igpu() logger.info(f"Found GPU at:\n'{gpu}'") if not gpu: return False @@ -176,3 +234,26 @@ def start_tdp_client( if __name__ == "__main__": logging.basicConfig(level=logging.INFO) prepare_tdp_mount(True) + + +def find_intel_igpu(): + for hw in os.listdir("/sys/class/drm"): + if not hw.startswith("card"): + continue + if not os.path.exists(f"/sys/class/drm/{hw}/device/subsystem_vendor"): + continue + with open(f"/sys/class/drm/{hw}/device/subsystem_vendor", "r") as f: + # intel + if "1462" not in f.read(): + continue + + if not os.path.exists(f"/sys/class/drm/{hw}/device/local_cpulist"): + logger.warning( + f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' + ) + continue + + pth = os.path.realpath(os.path.join("/sys/class/drm", hw)) + return pth + + return None From be0f6b430caf315dd01a7b1e5e829c321738b511 Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 21:57:35 +0200 Subject: [PATCH 02/10] remove specific test string for AMD gpus --- src/adjustor/fuse/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index 7d9c05e..aa2df88 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) AMD_DGPU_KEYWORDS = [ - "navi", "radeon", "rx", "vega", "5700", "6600", "6800", "6900", "7900" + "navi", "radeon", "rx", "vega" ] def _is_amd_dgpu(pci_address: str) -> bool: From 2d7bcf5d7182fccc1ff8d76565bec31f7cd288d0 Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 23:25:27 +0200 Subject: [PATCH 03/10] Simplify PR --- src/adjustor/fuse/gpu.py | 22 ++++++++++++++++++++++ src/adjustor/fuse/utils.py | 23 ----------------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/adjustor/fuse/gpu.py b/src/adjustor/fuse/gpu.py index 76af1e8..7f0f4d9 100644 --- a/src/adjustor/fuse/gpu.py +++ b/src/adjustor/fuse/gpu.py @@ -38,6 +38,28 @@ class GPUStatus(NamedTuple): epp_avail: Sequence[EppStatus] | None epp: EppStatus | None +def find_intel_igpu(): + for hw in os.listdir("/sys/class/drm"): + if not hw.startswith("card"): + continue + if not os.path.exists(f"/sys/class/drm/{hw}/device/subsystem_vendor"): + continue + with open(f"/sys/class/drm/{hw}/device/subsystem_vendor", "r") as f: + # intel + if "1462" not in f.read(): + continue + + if not os.path.exists(f"/sys/class/drm/{hw}/device/local_cpulist"): + logger.warning( + f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' + ) + continue + + pth = os.path.realpath(os.path.join("/sys/class/drm", hw)) + return pth + + return None + def get_igpu_status(): hwmon = find_intel_igpu() diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index aa2df88..6c11ce7 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -234,26 +234,3 @@ def start_tdp_client( if __name__ == "__main__": logging.basicConfig(level=logging.INFO) prepare_tdp_mount(True) - - -def find_intel_igpu(): - for hw in os.listdir("/sys/class/drm"): - if not hw.startswith("card"): - continue - if not os.path.exists(f"/sys/class/drm/{hw}/device/subsystem_vendor"): - continue - with open(f"/sys/class/drm/{hw}/device/subsystem_vendor", "r") as f: - # intel - if "1462" not in f.read(): - continue - - if not os.path.exists(f"/sys/class/drm/{hw}/device/local_cpulist"): - logger.warning( - f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' - ) - continue - - pth = os.path.realpath(os.path.join("/sys/class/drm", hw)) - return pth - - return None From 1d4710bdf8a8d34f1e9f1e72b873c1fb4b122b52 Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 23:26:34 +0200 Subject: [PATCH 04/10] Fix issue with last PR --- src/adjustor/fuse/gpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adjustor/fuse/gpu.py b/src/adjustor/fuse/gpu.py index 7f0f4d9..40b304c 100644 --- a/src/adjustor/fuse/gpu.py +++ b/src/adjustor/fuse/gpu.py @@ -3,7 +3,7 @@ from typing import Literal, NamedTuple from typing import Sequence -from adjustor.fuse.utils import find_amd_igpu, find_intel_igpu +from adjustor.fuse.utils import find_amd_igpu logger = logging.getLogger(__name__) GPU_FREQUENCY_PATH = "device/pp_od_clk_voltage" From b876baf945ea366e7b62d263acf20898135f5cbe Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 23:27:06 +0200 Subject: [PATCH 05/10] Fix formatting --- src/adjustor/fuse/gpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/adjustor/fuse/gpu.py b/src/adjustor/fuse/gpu.py index 40b304c..bb7fb5d 100644 --- a/src/adjustor/fuse/gpu.py +++ b/src/adjustor/fuse/gpu.py @@ -38,6 +38,7 @@ class GPUStatus(NamedTuple): epp_avail: Sequence[EppStatus] | None epp: EppStatus | None + def find_intel_igpu(): for hw in os.listdir("/sys/class/drm"): if not hw.startswith("card"): From 2e1db00769418da65fde0ee8c9b4c2042bc534b6 Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 23:36:54 +0200 Subject: [PATCH 06/10] Fix stupid mistake and add reformating --- src/adjustor/fuse/utils.py | 47 ++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index 6c11ce7..bf16f5b 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -11,26 +11,16 @@ TDP_MOUNT = "/run/hhd-tdp/hwmon" FUSE_MOUNT_SOCKET = "/run/hhd-tdp/socket" -AMD_DGPU_KEYWORDS = ['navi', 'vega', 'polaris'] +AMD_DGPU_KEYWORDS = ["navi", "vega", "polaris"] -import os -import subprocess -import logging -from typing import Optional - -logger = logging.getLogger(__name__) - -AMD_DGPU_KEYWORDS = [ - "navi", "radeon", "rx", "vega" -] def _is_amd_dgpu(pci_address: str) -> bool: try: result = subprocess.run( - ['lspci', '-s', pci_address], + ["lspci", "-s", pci_address], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - check=True + check=True, ) output = result.stdout.decode().strip().lower() logger.debug(f"lspci output for {pci_address}: {output}") @@ -52,13 +42,13 @@ def _extract_pci_address(hwmon_path: str) -> Optional[str]: def _is_valid_pci_address(segment: str) -> bool: return ( len(segment) == 12 - and segment[4] == ':' - and segment[7] == ':' - and segment[10] == '.' + and segment[4] == ":" + and segment[7] == ":" + and segment[10] == "." ) try: - segments = reversed(os.path.realpath(hwmon_path).split('/')) + segments = reversed(os.path.realpath(hwmon_path).split("/")) return next((seg for seg in segments if _is_valid_pci_address(seg)), None) except Exception as e: @@ -234,3 +224,26 @@ def start_tdp_client( if __name__ == "__main__": logging.basicConfig(level=logging.INFO) prepare_tdp_mount(True) + + +def find_intel_igpu(): + for hw in os.listdir("/sys/class/drm"): + if not hw.startswith("card"): + continue + if not os.path.exists(f"/sys/class/drm/{hw}/device/subsystem_vendor"): + continue + with open(f"/sys/class/drm/{hw}/device/subsystem_vendor", "r") as f: + # intel + if "1462" not in f.read(): + continue + + if not os.path.exists(f"/sys/class/drm/{hw}/device/local_cpulist"): + logger.warning( + f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' + ) + continue + + pth = os.path.realpath(os.path.join("/sys/class/drm", hw)) + return pth + + return None From 758c177be5ad9b36330a870d7aa9913a49bbdfa6 Mon Sep 17 00:00:00 2001 From: SamlRx Date: Sun, 11 May 2025 23:44:05 +0200 Subject: [PATCH 07/10] Remove legacy change. --- src/adjustor/fuse/utils.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index bf16f5b..4a4ec8f 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -224,26 +224,3 @@ def start_tdp_client( if __name__ == "__main__": logging.basicConfig(level=logging.INFO) prepare_tdp_mount(True) - - -def find_intel_igpu(): - for hw in os.listdir("/sys/class/drm"): - if not hw.startswith("card"): - continue - if not os.path.exists(f"/sys/class/drm/{hw}/device/subsystem_vendor"): - continue - with open(f"/sys/class/drm/{hw}/device/subsystem_vendor", "r") as f: - # intel - if "1462" not in f.read(): - continue - - if not os.path.exists(f"/sys/class/drm/{hw}/device/local_cpulist"): - logger.warning( - f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' - ) - continue - - pth = os.path.realpath(os.path.join("/sys/class/drm", hw)) - return pth - - return None From 1be04f88b16d0cfe8268d5f67b16acd2c966ec88 Mon Sep 17 00:00:00 2001 From: SamlRx Date: Mon, 12 May 2025 22:32:24 +0200 Subject: [PATCH 08/10] Change logic to use Vulkaninfo for a more reliable result --- src/adjustor/fuse/utils.py | 70 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index 4a4ec8f..466cdde 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -5,37 +5,44 @@ from threading import Event, Thread import subprocess from typing import Optional +import re logger = logging.getLogger(__name__) TDP_MOUNT = "/run/hhd-tdp/hwmon" FUSE_MOUNT_SOCKET = "/run/hhd-tdp/socket" -AMD_DGPU_KEYWORDS = ["navi", "vega", "polaris"] +def _get_vulkaninfo_output(): + result = subprocess.run(["vulkaninfo", "--summary"], capture_output=True, text=True) + return result.stdout -def _is_amd_dgpu(pci_address: str) -> bool: - try: - result = subprocess.run( - ["lspci", "-s", pci_address], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - check=True, - ) - output = result.stdout.decode().strip().lower() - logger.debug(f"lspci output for {pci_address}: {output}") - if any(keyword in output for keyword in AMD_DGPU_KEYWORDS): - logger.info(f"Detected AMD dGPU at {pci_address}") - return True +def _extract_integrated_gpu_uuid(vulkaninfo_output: str) -> Optional[str]: + devices = vulkaninfo_output.split("Devices:")[1] + device_blocks = re.split(r"GPU\d+:", devices) - except subprocess.CalledProcessError as e: - logger.error( - f"Error decoding PCI address for GPU {pci_address}: " - f"{e.stderr.decode().strip()}" - ) + for block in device_blocks: + if "PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU" in block: + uuid_match = re.search(r"deviceUUID\s*=\s*([a-fA-F0-9\-]+)", block) + if uuid_match: + return uuid_match.group(1).lower() + return None + + +def _uuid_to_pci_address(uuid: str) -> Optional[str]: + parts = uuid.split("-") + if len(parts) != 5: + return None + + bus_hex_le = parts[1] + if len(bus_hex_le) != 4: + return None - return False + bus_hex = bus_hex_le[2:] + bus_hex_le[:2] # '6400' → '0064' + bus = int(bus_hex, 16) + + return f"0000:{bus:02x}:00.0" def _extract_pci_address(hwmon_path: str) -> Optional[str]: @@ -52,31 +59,20 @@ def _is_valid_pci_address(segment: str) -> bool: return next((seg for seg in segments if _is_valid_pci_address(seg)), None) except Exception as e: - logger.error(f"Error extracting PCI address for {hwmon_path}: {e}") + logger.warning(f"Error extracting PCI address for {hwmon_path}: {e}") return None def find_amd_igpu(): + igpu_pci_address = _uuid_to_pci_address( + _extract_integrated_gpu_uuid(_get_vulkaninfo_output()) + ) + for hw in os.listdir("/sys/class/hwmon"): if not hw.startswith("hwmon"): continue - if not os.path.exists(f"/sys/class/hwmon/{hw}/name"): - continue - with open(f"/sys/class/hwmon/{hw}/name", 'r') as f: - if "amdgpu" not in f.read(): - continue - - if not os.path.exists(f"/sys/class/hwmon/{hw}/device"): - logger.error(f'No device symlink found for "{hw}"') - continue - - if not os.path.exists(f"/sys/class/hwmon/{hw}/device/local_cpulist"): - logger.warning( - f'No local_cpulist found for "{hw}". Assuming it is a dedicated unit.' - ) - continue - if not _is_amd_dgpu(_extract_pci_address(f"/sys/class/hwmon/{hw}")): + if igpu_pci_address == _extract_pci_address(f"/sys/class/hwmon/{hw}"): pth = os.path.realpath(os.path.join("/sys/class/hwmon", hw)) logger.info(f"Found AMD iGPU at:\n'{pth}'") return pth From 9049053d9c72bc6e82207f5a7c83fe61e4c6bd8a Mon Sep 17 00:00:00 2001 From: SamlRx Date: Mon, 12 May 2025 22:33:17 +0200 Subject: [PATCH 09/10] add typing --- src/adjustor/fuse/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index 466cdde..16ce62c 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -13,7 +13,7 @@ FUSE_MOUNT_SOCKET = "/run/hhd-tdp/socket" -def _get_vulkaninfo_output(): +def _get_vulkaninfo_output() -> str: result = subprocess.run(["vulkaninfo", "--summary"], capture_output=True, text=True) return result.stdout @@ -39,7 +39,7 @@ def _uuid_to_pci_address(uuid: str) -> Optional[str]: if len(bus_hex_le) != 4: return None - bus_hex = bus_hex_le[2:] + bus_hex_le[:2] # '6400' → '0064' + bus_hex = bus_hex_le[2:] + bus_hex_le[:2] bus = int(bus_hex, 16) return f"0000:{bus:02x}:00.0" From 02152db7efe7382edd76423298fea90f927c9acc Mon Sep 17 00:00:00 2001 From: SamlRx Date: Tue, 13 May 2025 23:34:43 +0200 Subject: [PATCH 10/10] Revert code and use mem_info_vram_vendor to determine if we are checking an iGPU or a dGPU --- src/adjustor/fuse/utils.py | 73 +++++++------------------------------- 1 file changed, 12 insertions(+), 61 deletions(-) diff --git a/src/adjustor/fuse/utils.py b/src/adjustor/fuse/utils.py index 16ce62c..23ce9fa 100644 --- a/src/adjustor/fuse/utils.py +++ b/src/adjustor/fuse/utils.py @@ -3,9 +3,6 @@ import sys import time from threading import Event, Thread -import subprocess -from typing import Optional -import re logger = logging.getLogger(__name__) @@ -13,69 +10,23 @@ FUSE_MOUNT_SOCKET = "/run/hhd-tdp/socket" -def _get_vulkaninfo_output() -> str: - result = subprocess.run(["vulkaninfo", "--summary"], capture_output=True, text=True) - return result.stdout - - -def _extract_integrated_gpu_uuid(vulkaninfo_output: str) -> Optional[str]: - devices = vulkaninfo_output.split("Devices:")[1] - device_blocks = re.split(r"GPU\d+:", devices) - - for block in device_blocks: - if "PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU" in block: - uuid_match = re.search(r"deviceUUID\s*=\s*([a-fA-F0-9\-]+)", block) - if uuid_match: - return uuid_match.group(1).lower() - return None - - -def _uuid_to_pci_address(uuid: str) -> Optional[str]: - parts = uuid.split("-") - if len(parts) != 5: - return None - - bus_hex_le = parts[1] - if len(bus_hex_le) != 4: - return None - - bus_hex = bus_hex_le[2:] + bus_hex_le[:2] - bus = int(bus_hex, 16) - - return f"0000:{bus:02x}:00.0" - - -def _extract_pci_address(hwmon_path: str) -> Optional[str]: - def _is_valid_pci_address(segment: str) -> bool: - return ( - len(segment) == 12 - and segment[4] == ":" - and segment[7] == ":" - and segment[10] == "." - ) - - try: - segments = reversed(os.path.realpath(hwmon_path).split("/")) - return next((seg for seg in segments if _is_valid_pci_address(seg)), None) - - except Exception as e: - logger.warning(f"Error extracting PCI address for {hwmon_path}: {e}") - return None - - def find_amd_igpu(): - igpu_pci_address = _uuid_to_pci_address( - _extract_integrated_gpu_uuid(_get_vulkaninfo_output()) - ) - for hw in os.listdir("/sys/class/hwmon"): if not hw.startswith("hwmon"): continue + if not os.path.exists(f"/sys/class/hwmon/{hw}/name"): + continue + with open(f"/sys/class/hwmon/{hw}/name", "r") as f: + if "amdgpu" not in f.read(): + continue + + if not os.path.exists(f"/sys/class/hwmon/{hw}/device"): + logger.error(f'No device symlink found for "{hw}"') + continue - if igpu_pci_address == _extract_pci_address(f"/sys/class/hwmon/{hw}"): - pth = os.path.realpath(os.path.join("/sys/class/hwmon", hw)) - logger.info(f"Found AMD iGPU at:\n'{pth}'") - return pth + # This assume dGPU will come with VRAM vendor while iGPU will not + if not os.path.exists(f"/sys/class/hwmon/{hw}/device/mem_info_vram_vendor"): + return os.path.realpath(os.path.join("/sys/class/hwmon", hw)) logger.error("No iGPU found. Binding TDP attributes will not be possible.") return None