feat: Display and GPU detection improvements - #8
Conversation
|
When testing on Windows, my GPU's ACPI path was reported as 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 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 Updated source code to reflect this accordingly. References:
Footnotes |
Mahasvan
left a comment
There was a problem hiding this comment.
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
| """ | ||
| :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]: |
There was a problem hiding this comment.
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
|
|
||
| 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) |
There was a problem hiding this comment.
use posixpath.realpath instead of os.path operations wherever, in Mac and linux
| pci_segments = [] | ||
|
|
||
| for part in path.split(os.sep): | ||
| if part.startswith("pci"): |
There was a problem hiding this comment.
Can you improve this logic? Surely there must a more elegant way to filter this?
There was a problem hiding this comment.
If theres no better way to get this value, add a comment that describes the structure of this value, and an example value
There was a problem hiding this comment.
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.
| for part in path.split(os.sep): | ||
| if part.startswith("pci"): | ||
| try: | ||
| root_bus = part.split(":")[0].split("pci")[-1] |
There was a problem hiding this comment.
will using regex with capture groups and a lookbehind not be a more elegant way to get the B, D, F values?
There was a problem hiding this comment.
Potentially, but it’s drastically slower to use regex here, with little added benefit
There was a problem hiding this comment.
[Resolved privately]: RegEx has been opt-ed for since the performance is negligible and makes the code more readable.
| 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}") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Printing stuff is left-overs from when I was debugging things, but for default values - you're right, I'll fix that
| # 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. |
There was a problem hiding this comment.
Has this been verified across machines? Was a pattern established?
There was a problem hiding this comment.
[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.
| return self.info.graphics | ||
|
|
||
| def fetch_display_info(self) -> DisplayInfo: | ||
| self.info.display = fetch_display_info(self.info.graphics.modules) |
There was a problem hiding this comment.
You need to query graphics info if it is not present already, otherwise it may be None.
| 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 | ||
| ): |
There was a problem hiding this comment.
Add status messages and make its status Partial. I see this lacking across more areas. Pls fill in wherever needed.
There was a problem hiding this comment.
Like the other comment, you can use make_partial to both make the status partial, and add a message at the same time
| 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}") |
There was a problem hiding this comment.
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.
| audio: Optional[AudioInfo] = None | ||
| baseboard: Optional[BaseboardInfo] = None |
There was a problem hiding this comment.
audio and baseboard are not supported or stable features yet. Kindly remove this
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
7eeb195 to
8eca5da
Compare
- 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
…olated rather than read directly
8eca5da to
f888a30
Compare
`Optional[str]` since `_read_from_sysfs` can return None
root/function segments, added comment to elaborate
There was a problem hiding this comment.
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 comparesNonewith each unresolved GPU'spci_path. The first GPU whose path is alsoNoneis 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
RuntimeErrorwhen the native call fails, but this call is not caught. One failing GPU therefore abortsfetch_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_LOCALheap. Intel integrated devices also have no i915DEVICEmemory 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_budgetalready provides current allocation inheapUsage;heap.size - heapBudgetis not usage because the budget is a driver-defined allocation allowance and can be below the physical heap size even when idle. This produces inaccuratevram_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
bindingsdirectory does not include it in an installed package:pyproject.toml:33-35andMANIFEST.in:3-4only package the Windows and macOS native binaries. Consequently installed Linux distributions always makeis_available()false and cannot use this PR's GPU detection. Add Linux native build/package handling forlibdevice_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.cignores all arguments, prints nothing, and always exits 0. Either implement the described CLI aroundget_gpu_infoor 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, orrocm-smi; those paths were removed fromgraphics.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 onlypath. The resultingTypeErroris swallowed byfetch_display_info, so the test passes with zero modules for the wrong reason and no longer verifies the intendedNonepath.
info = fetch_display_info([])
src/hwprobe/core/linux/display.py:94
- Making
gpu_devicesmandatory breaks existing callers of the previously argument-freefetch_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 checksgpu_name. Add a case with a resolved connector BDF and a matchingGPUInfo(plus an unresolved-path case) so this behavior and theNone-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_querydoes 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;
}
}
|
|
||
| struct VkPhysDevProps | ||
| { | ||
| uint32_t api, driver, vendorID, deviceID, devType; |
There was a problem hiding this comment.
Bad practice, explicitly enumerate each element and its type.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Same remark as line 61
There was a problem hiding this comment.
See comment above
|
|
||
| enum | ||
| { | ||
| MAX_GPU_CARDS = 16, |
There was a problem hiding this comment.
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.
| enum | ||
| { | ||
| MAX_GPU_CARDS = 16, | ||
| BYTES_PER_MB = 1024 * 1024, |
There was a problem hiding this comment.
When doing math in a define/enum, keep the operations in parenthesis
| BYTES_PER_MB = 1024 * 1024, | |
| BYTES_PER_MB = (1024 * 1024), |
| if (!bdf) | ||
| return pciAddr; | ||
|
|
||
| sscanf(bdf, "%hx:%hhx:%hhx.%hhd", &pciAddr.domain, &pciAddr.bus, &pciAddr.device, &pciAddr.function); |
There was a problem hiding this comment.
Missing check on sscanf return
Also sscanf is not safe, prefer sscanf_s or strtoll
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Use brackets even if the if contains one instruction
There was a problem hiding this comment.
I'm not sure what you meant by this, do you mind clarifying?
Is it just this:
if ((ioctl(...) == 0))There was a problem hiding this comment.
@kernel-dev Forgot to quote the incriminated line
if()
{
stuff();
}
even if the if contain one single instruction
There was a problem hiding this comment.
Ahhh, ok, I see now. Thanks!
| ureq.query = AMDGPU_INFO_VRAM_USAGE; | ||
|
|
||
| if (ioctl(fd, DRM_IOCTL_AMDGPU_INFO, &ureq) == 0) | ||
| g->vram_used_mb = to_mb(usage.vram); |
There was a problem hiding this comment.
g* wasn't checked for NULL
| { | ||
| if (r->regions[i].region.memory_class == I915_MEMORY_CLASS_DEVICE) | ||
| { | ||
| g->vram_total_mb = to_mb(r->regions[i].probed_size); |
There was a problem hiding this comment.
Same, g not NULL checked
| 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); |
| return -1; | ||
|
|
||
| // Load the Vulkan entry points we need. | ||
| // If any are missing, the ICD is too old to support the features we need. |
There was a problem hiding this comment.
Weird, maybe there is still some informations that could be gathered, not all pointer are NULl are they ?
There was a problem hiding this comment.
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).
build with vulkan headers fix(linux): PR fixes #3
There was a problem hiding this comment.
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_QUERYexpects adrm_xe_device_query, not the variable-length response struct. Passingdrm_xe_query_mem_regionsgives the ioctl the wrong buffer size/layout and can overwrite adjacent stack data; it also leaves no storage formem_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, ®ions) == 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
breakreports only the first region. Sum everyI915_MEMORY_CLASS_DEVICEregion 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. Validatenative.nameand 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
GPUPropertiesor raiseRuntimeError; it never returnsNone. A native failure therefore escapes this function and aborts all GPU enumeration instead of producing the intended partial status. CatchRuntimeErroraround the call and mark this device partial; update the failure test to raise rather than returnNone.
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_USAGEwrites a byte count into this address, but the result is returned asvram_used_mbwithout 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
drmGetVersionreturns an allocated object that must be released withdrmFreeVersion. 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, so5 + len(payload)creates a malformed extension and gives the parser two extra bytes of apparent section data. Use3 + 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_nameassignment would pass. Add a monitor test with a matchingGPUInfoand 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
lspcipath with this native result removes the only Linux population path formanufacturer,subsystem_manufacturer, andsubsystem_model; those publicGPUInfofields will now always beNone. Retain anlspcifallback 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_INCOMPLETEwhen more than 16 devices exist. Treating that as fatal discards all enumerated devices, so fallback fails on such systems; acceptVK_INCOMPLETEand 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_pathfrom this Pydantic model is a breaking serialized-schema/API change unrelated to addinggpu_name; existing consumers can no longer read or provide the field, and Linux previously populated it. Preservepci_pathwhile 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, notAMDGPU_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
Pfrom this documented example tuple.
('', '', '', '0000:09:00.0', '0000', '09', '00', '0')P
src/hwprobe/interops/linux/src/gpu_info.cpp:280
heapBudgetis an allocation budget, not free memory, soheap size - heapBudgetis not VRAM usage and can be nonzero even when idle.heapUsageprovides process-local usage, while the public field claims all-process usage; either useheapUsageand 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];
| 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
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
fetch_display_info(...)to avoid having to callget_gpu_info(bdf, vendor)interop or PCI IDs look-up down the line