Skip to content

feat: Display and GPU detection improvements - #8

Open
kernel-dev wants to merge 22 commits into
mainfrom
feat/display-gpu-improvements
Open

feat: Display and GPU detection improvements#8
kernel-dev wants to merge 22 commits into
mainfrom
feat/display-gpu-improvements

Conversation

@kernel-dev

Copy link
Copy Markdown
Collaborator
  1. Implemented GPU helper interops for Linux
    1.1. Uses DRM IOCTLs according to vendor ID to extract VRAM information
    1.2. Vulkan is used to extract the name if DRM IOCTL succeeds for VRAM;
    1.3. Vulkan is also used to extract VRAM if DRM IOCTL fails for VRAM
  2. Display detection now resolves its parent GPU name; the biggest change was to pass the list of GPU modules down to fetch_display_info(...) to avoid having to call get_gpu_info(bdf, vendor) interop or PCI IDs look-up down the line
  3. Extended EDID parsing feature set, detailed timing descriptors are now supported
  4. Implemented proper ACPI resolving/detection for Linux platform, either by direct or inferred fetch

@kernel-dev
kernel-dev requested a review from Mahasvan August 12, 2026 13:53
@kernel-dev kernel-dev added the enhancement New feature or request label Aug 12, 2026
@kernel-dev

kernel-dev commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

When testing on Windows, my GPU's ACPI path was reported as \_SB_.PCI0.GPP8, which was weird since my assumption was that the GPP8 was a PCI Bridge.

According to UEFI spec/HSI layer, the ACPI namespace may only describe the parent bus/bridge which then delegates a designated port to the PCI device; the PCI device may not be described through ACPI, since identification through PCI enumeration is sufficient.

So, in fact, the GPP8 namespace is a PCI bridge, but the way it works is that the GPU device is discovered later via PCI enumeration.

As such, the ACPI path of a PCI device may not necessarily be described explicitly; it may only provide the parent device which supports the child. Thus, to interact with the device, it must go through the ACPI parent.

This is why the reported PCI path is PciRoot(0x0)/Pci(0x3,0x1)/Pci(0x0,0x0), because the chain is pci0000:00 -> 0000:00:03.1 -> 0000:09:00.0.

Updated source code to reflect this accordingly.

References:

  • "An _ADR object can be used to provide capabilities to the specified address even if a device is not present. This allows the system to provide capabilities to a slot on the parent bus. [...] High word-Device #, Low word-Function #. (for example, device 3, function 2 is 0x00030002). To refer to all the functions on a device #, use a function number of FFFF)."1
  • "For devices that connect to a hardware-enumerable parent bus (for example, SDIO, USB HSIC) [...]. Instead, the device identifier is created by the parent bus driver (as discussed previously). In this case, though, the Address Object (_ADR) is required to be in the ACPI namespace for the device. This object enables the operating system to associate the bus-enumerated device with its ACPI-described features or controls."2

Footnotes

  1. ACPI 6.6, §6.1.1, _ADR (Address)

  2. Microsoft: Device Management Namespace Objects

@Mahasvan Mahasvan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional notes:

  • Pls update documentation wherever applicable.
  • Also, make sure to update tests for your changes, in every commit. Helps to keep track. Make sure the coverage is high

Comment thread src/hwprobe/core/linux/common.py Outdated
"""
:param device_slot: format: <domain>:<bus>:<slot>.<function>
:return: PCI path, e.g. PciRoot(0x0)/Pci(0x2,0x0)
def _resolve_acpi_path(device_bdf: str) -> tuple[Optional[str], ACPIResult]:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont need an ACPIResult enum - its a failure if the Optional[str] is None, and you only need to return a boolean for whether it was inferred or not

Comment thread src/hwprobe/core/linux/common.py Outdated

bdfs = [p for p in sysfs_path.split(posixpath.sep) if _PCI_BDF_PATTERN.match(p)]
if not bdfs:
path = os.path.realpath(raw_path)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use posixpath.realpath instead of os.path operations wherever, in Mac and linux

Comment thread src/hwprobe/core/linux/common.py Outdated
pci_segments = []

for part in path.split(os.sep):
if part.startswith("pci"):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you improve this logic? Surely there must a more elegant way to filter this?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If theres no better way to get this value, add a comment that describes the structure of this value, and an example value

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is, this is the best way to describe/resolve the path without interacting with lower-level APIs. I'll add a comment.

Comment thread src/hwprobe/core/linux/common.py Outdated
for part in path.split(os.sep):
if part.startswith("pci"):
try:
root_bus = part.split(":")[0].split("pci")[-1]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will using regex with capture groups and a lookbehind not be a more elegant way to get the B, D, F values?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially, but it’s drastically slower to use regex here, with little added benefit

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Resolved privately]: RegEx has been opt-ed for since the performance is negligible and makes the code more readable.

Comment thread src/hwprobe/core/linux/common.py Outdated
root_bus = part.split(":")[0].split("pci")[-1]
pci_root = f"PciRoot(0x{int(root_bus, 16):x})"
except (ValueError, IndexError) as e:
print(f"Error parsing PCI root bus from {part}: {e}")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we printing stuff?
And Why are we returning default values? Should we not be throwing an error or returning None?

same for the other blocks in this function

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Printing stuff is left-overs from when I was debugging things, but for default values - you're right, I'll fix that

Comment thread src/hwprobe/core/linux/display.py Outdated
Comment on lines +66 to +67
# For some reason, it's not guaranteed to only have a single "device" directory in the tree/chain
# So, we look at how many is necessary until "device" stops being a directory.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this been verified across machines? Was a pattern established?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Resolved privately]: a display device’s path is a symlink to its parent GPU DRM entry, can resolve it, and immediately fetch the /device which points to the GPU device.

Comment thread src/hwprobe/core/linux/manager.py Outdated
return self.info.graphics

def fetch_display_info(self) -> DisplayInfo:
self.info.display = fetch_display_info(self.info.graphics.modules)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to query graphics info if it is not present already, otherwise it may be None.

Comment thread src/hwprobe/core/linux/graphics.py Outdated
Comment on lines +132 to +136
if (
vendor_id is not None and
NATIVE_AVAILABLE is True and
(native := native_gpu.get_gpu_info(device, int(gpu.vendor_id, 16))) is not None
):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add status messages and make its status Partial. I see this lacking across more areas. Pls fill in wherever needed.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the other comment, you can use make_partial to both make the status partial, and add a message at the same time

Comment thread src/hwprobe/core/linux/graphics.py Outdated
pci_path = pci_path_linux(device)
gpu.pci_path = pci_path
except Exception as e:
graphics_info.status.messages.append(f"Could not read device ID for {device}")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a function in the StatusInfo class, make_partial, that automatically make the status Partial, and appends a status info. Do not do this manually, itll affect maintainability. Pls change in other usages as well.

Comment thread src/hwprobe/models/info_models.py Outdated
Comment on lines +22 to +23
audio: Optional[AudioInfo] = None
baseboard: Optional[BaseboardInfo] = None

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

audio and baseboard are not supported or stable features yet. Kindly remove this

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same goes for display. It hasn't been tested widely, so it is not supported officially. The functions exist in each platform's HardwareManager, but we don't want this in the base class yet. This will be done in another PR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds richer Linux GPU/display detection through native DRM/Vulkan interop, ACPI resolution, GPU-display association, and extended EDID parsing.

Changes:

  • Adds native Linux GPU name and VRAM detection.
  • Expands PCIe, ACPI, display-parent, and hardware models.
  • Adds CTA/DisplayID timing parsing and updates Linux tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
tests/core/linux/test_graphics.py Updates Linux GPU tests.
tests/core/linux/test_display.py Updates display detection tests.
tests/core/linux/test_common.py Adjusts PCI path tests.
src/hwprobe/util/nvidia.py Removes nvidia-smi helper.
src/hwprobe/models/info_models.py Adds component fields and interfaces.
src/hwprobe/models/gpu_models.py Splits current/max PCIe properties.
src/hwprobe/models/display_models.py Replaces PCI path with parent GPU name.
src/hwprobe/interops/linux/src/gpu_info.cpp Implements DRM/Vulkan GPU probing.
src/hwprobe/interops/linux/README.md Documents native Linux interop.
src/hwprobe/interops/linux/main.c Adds native CLI entry point.
src/hwprobe/interops/linux/include/gpu_info.h Defines native GPU API.
src/hwprobe/interops/linux/CMakeLists.txt Configures native library build.
src/hwprobe/interops/linux/bindings/gpu_info.py Adds Python ctypes binding.
src/hwprobe/core/linux/manager.py Integrates display collection.
src/hwprobe/core/linux/graphics.py Uses sysfs and native GPU probing.
src/hwprobe/core/linux/display.py Resolves displays to parent GPUs.
src/hwprobe/core/linux/common.py Adds ACPI and PCI resolution helpers.
src/hwprobe/core/common/edid.py Parses detailed extension timings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/hwprobe/core/linux/graphics.py
Comment thread tests/core/linux/test_graphics.py
Comment thread src/hwprobe/models/gpu_models.py Outdated
Comment thread src/hwprobe/core/linux/common.py Outdated
Comment thread src/hwprobe/core/linux/display.py Outdated
Comment thread src/hwprobe/core/common/edid.py
Comment thread tests/core/linux/test_display.py
Comment thread src/hwprobe/core/linux/display.py
Comment thread src/hwprobe/interops/linux/README.md Outdated
Comment thread src/hwprobe/interops/linux/README.md Outdated
@kernel-dev
kernel-dev force-pushed the feat/display-gpu-improvements branch from 7eeb195 to 8eca5da Compare August 15, 2026 08:10
- GPU information interops for Linux (DRM IOCTL / Vulkan fallback) for fetching VRAM and GPU name; removed nvidia-smi/amd/lspci
- Display detection fetches parent GPU, then finds it through the internal (also remove pci_path from display device, not sure why I added it there in the first place)
- Extend EDID parsing feature set: now handles `Detailed Timing Descriptor`, `Type 1 Timing` and `CTA-861` ext. block
- Implement ACPI detection / parsing on Linux the proper way
@kernel-dev
kernel-dev force-pushed the feat/display-gpu-improvements branch from 8eca5da to f888a30 Compare August 15, 2026 08:40
@kernel-dev
kernel-dev requested a review from Mahasvan August 15, 2026 08:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Suppressed comments (12)

src/hwprobe/core/linux/manager.py:53

  • This condition is reversed: a newly constructed manager has an empty GPU module list, so it skips GPU detection and passes [] to display detection, preventing the new parent-GPU-name resolution. Conversely, it unnecessarily re-detects GPUs when they are already populated.
        if self.info.graphics.modules and len(self.info.graphics.modules):
            self.fetch_graphics_info()

src/hwprobe/core/linux/display.py:37

  • If pci_path_linux(pci_bdf) cannot resolve a path, this compares None with each unresolved GPU's pci_path. The first GPU whose path is also None is then incorrectly assigned as the display's parent. Resolve once and stop when resolution fails before comparing GPU paths.
    for gpu in gpu_devices:
        if gpu.pci_path == pci_path_linux(pci_bdf):
            return gpu.name

src/hwprobe/core/linux/graphics.py:117

  • The binding explicitly raises RuntimeError when the native call fails, but this call is not caught. One failing GPU therefore aborts fetch_graphics_info() entirely instead of producing the intended partial result and continuing with other devices. Catch the binding error and record it in the status.
        elif (native := native_gpu.get_gpu_info(device, int(vendor_id, 16))) is None:

src/hwprobe/interops/linux/src/gpu_info.cpp:342

  • For integrated GPUs, Vulkan commonly exposes shared system RAM as a DEVICE_LOCAL heap. Intel integrated devices also have no i915 DEVICE memory region, so they reach this fallback and the code reports host RAM as dedicated VRAM. Use the physical-device type/heap model to distinguish shared memory, or leave dedicated VRAM unavailable for integrated devices.
            if (m.memProps.heaps[h].flags & VK_HEAP_DEVICE_LOCAL)
            {
                total += m.memProps.heaps[h].size;

src/hwprobe/interops/linux/src/gpu_info.cpp:345

  • VK_EXT_memory_budget already provides current allocation in heapUsage; heap.size - heapBudget is not usage because the budget is a driver-defined allocation allowance and can be below the physical heap size even when idle. This produces inaccurate vram_used_mb.
                // total - budget ≈ system-wide VRAM usage
                if (bgt.budget[h] > 0 && bgt.budget[h] < m.memProps.heaps[h].size)
                    used += m.memProps.heaps[h].size - bgt.budget[h];

src/hwprobe/interops/linux/CMakeLists.txt:65

  • Writing the library into the source bindings directory does not include it in an installed package: pyproject.toml:33-35 and MANIFEST.in:3-4 only package the Windows and macOS native binaries. Consequently installed Linux distributions always make is_available() false and cannot use this PR's GPU detection. Add Linux native build/package handling for libdevice_info.so.
set_target_properties(
    device_info
    PROPERTIES
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings
        OUTPUT_NAME device_info

src/hwprobe/interops/linux/README.md:59

  • The documented command cannot perform this query: main.c ignores all arguments, prints nothing, and always exits 0. Either implement the described CLI around get_gpu_info or document this executable as an ABI validation harness instead of providing nonfunctional usage and output.
## CLI Usage

```sh
./build/LinuxDeviceInfo <bdf> <vendor_id>

src/hwprobe/interops/linux/README.md:96

  • The high-level implementation no longer falls back to lspci, nvidia-smi, or rocm-smi; those paths were removed from graphics.py, which now marks the result partial when the native library is unavailable. This documented fallback is therefore misleading.
Or use the high-level API (automatic fallback to sysfs + `lspci`/`nvidia-smi`/`rocm-smi` when the native library
isn't available):

tests/core/linux/test_display.py:122

  • The patched helper is now called with (path, gpu_devices), but this test's mock still accepts only path. The resulting TypeError is swallowed by fetch_display_info, so the test passes with zero modules for the wrong reason and no longer verifies the intended None path.
        info = fetch_display_info([])

src/hwprobe/core/linux/display.py:94

  • Making gpu_devices mandatory breaks existing callers of the previously argument-free fetch_display_info() API. The manager can pass the GPU list for parent resolution while preserving compatibility by defaulting omitted input to an empty list.
def fetch_display_info(
    gpu_devices: list[GPUInfo]
):

src/hwprobe/core/linux/display.py:82

  • The new parent-resolution path is not exercised by the updated tests: every call supplies [], and no assertion checks gpu_name. Add a case with a resolved connector BDF and a matching GPUInfo (plus an unresolved-path case) so this behavior and the None-matching edge case are covered.
        # Resolve parent GPU based on the PCI BDF
        if (parent_gpu := _resolve_parent_gpu_by_bdf(pci_bdf, gpu_devices)) is not None:
            monitor_data.gpu_name = parent_gpu

src/hwprobe/interops/linux/src/gpu_info.cpp:244

  • This vendor-filter helper is never called, and vulkan_query does not even receive the vendor ID. Thus the optimization described above is absent: every GPU query initializes every installed ICD, and this is repeated once per GPU. Either wire the filter into loader setup safely or remove the claim/dead helper and reuse a single Vulkan enumeration across GPUs.
static bool icd_matches_vendor(const char *filename, uint32_t vendor_id)
{
    switch (vendor_id)
    {
    case 0x10DE:
        return strstr(filename, "nvidia") || strstr(filename, "nouveau");
    case 0x1002:
        return strstr(filename, "radeon") != NULL;
    case 0x8086:
        return strstr(filename, "intel") != NULL;
    default:
        return false;
    }
}

Comment thread src/hwprobe/models/info_models.py
Comment thread src/hwprobe/interops/linux/main.c Outdated
Comment thread src/hwprobe/interops/linux/src/gpu_info.cpp
@Mahasvan
Mahasvan requested a review from theomacx86 August 18, 2026 16:21

struct VkPhysDevProps
{
uint32_t api, driver, vendorID, deviceID, devType;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad practice, explicitly enumerate each element and its type.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interops were refactored to link against Vulkan directly, built with vulkan headers so no need to define the structures ourselves.

{
uint32_t sType;
void *pNext;
uint32_t dom, bus, dev, func;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark as line 61

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above


enum
{
MAX_GPU_CARDS = 16,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this an enum and not a define ? Those values seem unrelated.
A define in the scope of a code unit wont leak to other source files.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

enum
{
MAX_GPU_CARDS = 16,
BYTES_PER_MB = 1024 * 1024,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When doing math in a define/enum, keep the operations in parenthesis

Suggested change
BYTES_PER_MB = 1024 * 1024,
BYTES_PER_MB = (1024 * 1024),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

if (!bdf)
return pciAddr;

sscanf(bdf, "%hx:%hhx:%hhx.%hhd", &pciAddr.domain, &pciAddr.bus, &pciAddr.device, &pciAddr.function);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing check on sscanf return
Also sscanf is not safe, prefer sscanf_s or strtoll

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opt-ed for strtoll, should be fixed.

req.return_size = sizeof(vram);
req.query = AMDGPU_INFO_VRAM_GTT;

if (ioctl(fd, DRM_IOCTL_AMDGPU_INFO, &req) == 0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use brackets even if the if contains one instruction

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you meant by this, do you mind clarifying?

Is it just this:

if ((ioctl(...) == 0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kernel-dev Forgot to quote the incriminated line

if()
{
 stuff();
}

even if the if contain one single instruction

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, ok, I see now. Thanks!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

ureq.query = AMDGPU_INFO_VRAM_USAGE;

if (ioctl(fd, DRM_IOCTL_AMDGPU_INFO, &ureq) == 0)
g->vram_used_mb = to_mb(usage.vram);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g* wasn't checked for NULL

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

{
if (r->regions[i].region.memory_class == I915_MEMORY_CLASS_DEVICE)
{
g->vram_total_mb = to_mb(r->regions[i].probed_size);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, g not NULL checked

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

p.param = NOUVEAU_GETPARAM_FB_SIZE;

if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &p) == 0 && p.value > 0)
g->vram_total_mb = to_mb(p.value);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

return -1;

// Load the Vulkan entry points we need.
// If any are missing, the ICD is too old to support the features we need.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, maybe there is still some informations that could be gathered, not all pointer are NULl are they ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See reply to first review comment; though to answer your question it'd be insufficient to not have all the function pointers resolve to the desired symbol, since all symbols are necessary to obtain the information we want (besides maybe VRAM, but that was kind of the whole point of these interops).

@kernel-dev
kernel-dev requested review from theomacx86 and a balanced review from Copilot August 19, 2026 08:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.

Suppressed comments (18)

src/hwprobe/interops/linux/src/gpu_info.cpp:158

  • DRM_IOCTL_XE_DEVICE_QUERY expects a drm_xe_device_query, not the variable-length response struct. Passing drm_xe_query_mem_regions gives the ioctl the wrong buffer size/layout and can overwrite adjacent stack data; it also leaves no storage for mem_regions. Perform the required size query, allocate the response, issue the data query, and count only VRAM regions (not SYSMEM).
  drm_xe_query_mem_regions regions    = {0};

  if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &regions) == 0) {
    for (uint32_t i = 0; i < regions.num_mem_regions; i++) {
      total_vram += regions.mem_regions[i].total_size;

src/hwprobe/interops/linux/src/gpu_info.cpp:139

  • The query may return multiple device-memory regions (for example, multi-tile Intel GPUs), but this break reports only the first region. Sum every I915_MEMORY_CLASS_DEVICE region so total VRAM is not underreported.
    for (uint32_t i = 0; i < r->num_regions; i++) {
      if (r->regions[i].region.memory_class == I915_MEMORY_CLASS_DEVICE) {
        g->vram_total_mb = to_mb(r->regions[i].probed_size);
        break;
      }

src/hwprobe/core/linux/graphics.py:113

  • A successful native call can still return an empty name—for example, DRM supplies VRAM but Vulkan cannot initialize. This path assigns "" and may leave the overall status successful despite missing the GPU name. Validate native.name and mark the result partial when it is empty.
            gpu.name = native.name

src/hwprobe/interops/linux/README.md:30

  • This states Python 3.7+, but the package metadata requires Python 3.9 or newer. Keep the native binding requirements consistent with the installable package.
- Python 3.7+ (for the `gpu_info.py` binding) - Assuming you want to compile this to use with HWProbe.

src/hwprobe/core/linux/graphics.py:111

  • The binding contract is return GPUProperties or raise RuntimeError; it never returns None. A native failure therefore escapes this function and aborts all GPU enumeration instead of producing the intended partial status. Catch RuntimeError around the call and mark this device partial; update the failure test to raise rather than return None.
        elif (native := native_gpu.get_gpu_info(device, int(vendor_id, 16))) is None:
            graphics_info.status.make_partial(f"Native GPU info library could not fetch GPU name or VRAM for {device} with vendor ID {vendor_id}")

src/hwprobe/interops/linux/src/gpu_info.cpp:105

  • RADEON_INFO_VRAM_USAGE writes a byte count into this address, but the result is returned as vram_used_mb without conversion. Radeon callers therefore receive a value roughly 1,048,576 times too large.

This issue also appears in the following locations of the same file:

  • line 135
  • line 154
  info.value           = (uint64_t)(uintptr_t)(&g->vram_used_mb);

src/hwprobe/interops/linux/src/gpu_info.cpp:368

  • drmGetVersion returns an allocated object that must be released with drmFreeVersion. Repeated probes currently leak one version object per opened GPU.
      drmVersionPtr drm_version = drmGetVersion(fd);

tests/core/common/test_edid.py:259

  • DisplayID byte 2 is the payload length after the five-byte extension header. This fixture's payload consists of the three-byte data-block header plus payload, so 5 + len(payload) creates a malformed extension and gives the parser two extra bytes of apparent section data. Use 3 + len(payload) to exercise real DisplayID boundaries.
    ext[2] = section_size if section_size is not None else 5 + len(payload)

src/hwprobe/core/linux/display.py:83

  • The new parent-GPU resolution path is not exercised by the updated tests: every call passes an empty GPU list, so a regression in BDF/PCI-path matching or gpu_name assignment would pass. Add a monitor test with a matching GPUInfo and assert the resolved name (plus a non-matching case).
        # Resolve parent GPU based on the PCI BDF
        if pci_bdf is not None and (parent_gpu := _resolve_parent_gpu_by_bdf(pci_bdf, gpu_devices)) is not None:
            monitor_data.gpu_name = parent_gpu

src/hwprobe/core/linux/common.py:9

  • This new ACPI resolver has no direct tests, despite introducing parent traversal and a direct-vs-inferred boolean consumed by graphics detection. Add coverage for a direct firmware node, parent inference, a missing device, and traversal reaching the filesystem root.
def _resolve_acpi_path(device_bdf: str) -> tuple[Optional[str], bool]:

src/hwprobe/core/linux/graphics.py:110

  • Replacing the lspci path with this native result removes the only Linux population path for manufacturer, subsystem_manufacturer, and subsystem_model; those public GPUInfo fields will now always be None. Retain an lspci fallback for fields the native API does not provide until the referenced PCI-IDs parser exists.

This issue also appears in the following locations of the same file:

  • line 110
  • line 113
        if not NATIVE_AVAILABLE:
            graphics_info.status.make_partial(f"Native GPU info library not available, cannot fetch GPU name or VRAM for {device}")
        elif vendor_id is None:
            graphics_info.status.make_partial(f"Vendor ID not available, cannot fetch GPU name or VRAM for {device}")
        elif (native := native_gpu.get_gpu_info(device, int(vendor_id, 16))) is None:

src/hwprobe/interops/linux/src/gpu_info.cpp:401

  • The native function returns success even when no DRM node was opened and Vulkan failed or found no matching BDF. Consequently the binding's failure path and CLI exit code are never reached for valid pointers, and callers receive an empty object as a successful query. Return a failure when neither a name nor VRAM was obtained, and handle that failure in the Python caller.
  *out = g;
  return 0;

src/hwprobe/interops/linux/src/gpu_info.cpp:234

  • After capping the requested count, Vulkan is allowed to return VK_INCOMPLETE when more than 16 devices exist. Treating that as fatal discards all enumerated devices, so fallback fails on such systems; accept VK_INCOMPLETE and process the returned prefix.
  r = vkEnumeratePhysicalDevices(inst, &numberOfDevices, devs);
  if (r != VK_SUCCESS) {
    vkDestroyInstance(inst, NULL);
    return -1;

src/hwprobe/models/display_models.py:34

  • Removing pci_path from this Pydantic model is a breaking serialized-schema/API change unrelated to adding gpu_name; existing consumers can no longer read or provide the field, and Linux previously populated it. Preserve pci_path while adding the new parent name, or provide an explicit compatibility/deprecation path.
    #: Parent GPU driving this display
    gpu_name: Optional[str] = None

src/hwprobe/interops/linux/README.md:19

  • The documented AMD ioctl does not match the implementation: the code issues AMDGPU_INFO_MEMORY, not AMDGPU_INFO_VRAM_GTT. Update the table so maintainers and users know which kernel interface is actually required.

This issue also appears on line 30 of the same file.

| **AMD**    | `AMDGPU_INFO_VRAM_GTT` ioctl              | Direct kernel interface                 |

src/hwprobe/interops/linux/bindings/gpu_info.py:69

  • The binding returns the GPU model/device name (normally from Vulkan), not a DRM driver name. The return documentation is misleading for callers.
    Returns:
        A GPUProperties object with VRAM figures and DRM driver name

src/hwprobe/core/linux/common.py:93

  • Remove the stray P from this documented example tuple.
        ('', '', '', '0000:09:00.0', '0000', '09', '00', '0')P

src/hwprobe/interops/linux/src/gpu_info.cpp:280

  • heapBudget is an allocation budget, not free memory, so heap size - heapBudget is not VRAM usage and can be nonzero even when idle. heapUsage provides process-local usage, while the public field claims all-process usage; either use heapUsage and correct that contract or omit Vulkan usage when system-wide data is unavailable.
        // total - budget ≈ system-wide VRAM usage
        if (budget.heapBudget[h] > 0 &&
            budget.heapBudget[h] <
                memProps2.memoryProperties.memoryHeaps[h].size)
          used += memProps2.memoryProperties.memoryHeaps[h].size -
                  budget.heapBudget[h];

Comment thread src/hwprobe/core/linux/manager.py
Comment thread src/hwprobe/interops/linux/bindings/gpu_info.py
lambda _: "/sys/devices/platform/non-pci-device",
)
assert pci_path_linux("0000:03:00.0") == "PciRoot(0x0)/Pci(0x0,0x0)"
assert pci_path_linux("0000:03:00.0") == None
memory regions size first, then allocate a buffer capable of holding the
information
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants