Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/hwprobe/core/common/pcie_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Optional

from hwprobe.models.gpu_models import PCIeLinkInfo, PCIeLinkValue


def build_pcie_link(
*,
max_gen: int,
current_gen: int,
max_width: int,
current_width: int
) -> Optional[PCIeLinkInfo]:
gen = None
width = None

if max_gen > 0 and current_gen > 0:
gen = PCIeLinkValue(max=max_gen, current=current_gen)

if max_width > 0 and current_width > 0:
width = PCIeLinkValue(max=max_width, current=current_width)

if gen is None and width is None:
return None

return PCIeLinkInfo(gen=gen, width=width)
17 changes: 13 additions & 4 deletions src/hwprobe/core/mac/graphics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from hwprobe.core.common.pcie_link import build_pcie_link
from hwprobe.models.gpu_models import AppleExtendedGPUInfo, GPUInfo, GraphicsInfo
from hwprobe.models.size_models import Megabyte
from hwprobe.models.status_models import StatusType
Expand Down Expand Up @@ -65,13 +66,21 @@ def fetch_graphics_info() -> GraphicsInfo:
if gpu.pci_path:
module.pci_path = gpu.pci_path

# VRAM for non-Apple-Silicon GPUs
# VRAM & PCIe Link for non-Apple-Silicon GPUs
# Apple Silicon GPUs can use the entire system memory as VRAM
if not gpu.is_apple_silicon and gpu.vram_mb:
module.vram = Megabyte(capacity=gpu.vram_mb)
if not gpu.is_apple_silicon:
module.pcie_link = build_pcie_link(
max_gen=gpu.capable_pcie_gen,
max_width=gpu.capable_pcie_width,
current_gen=gpu.negotiated_pcie_gen,
current_width=gpu.negotiated_pcie_width
)

if gpu.vram_mb:
module.vram = Megabyte(capacity=gpu.vram_mb)
elif not gpu.is_apple_silicon:
# Non-Apple Silicon GPU, and yet VRAM is reported as 0 MB.
graphics_info.status.make_partial(f"Could not get VRAM for non-Apple-Silicon GPU: {module.name}")
graphics_info.status.make_partial(f"Could not get VRAM/PCIe info for non-Apple-Silicon GPU: {module.name}")

# Apple Silicon extended info
if gpu.is_apple_silicon:
Expand Down
26 changes: 19 additions & 7 deletions src/hwprobe/interops/mac/bindings/gpu_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ class _GPUProperties(ctypes.Structure):
("name", ctypes.c_char * 256),
("vendor_id", ctypes.c_uint32),
("device_id", ctypes.c_uint32),
("is_apple_silicon", ctypes.c_int),
("apple_gpu", _AppleGPUProperties),
("acpi_path", ctypes.c_char * 512),
("pci_path", ctypes.c_char * 512),
("vram_mb", ctypes.c_uint64),
("capable_pcie_gen", ctypes.c_uint32),
("capable_pcie_width", ctypes.c_uint32),
("negotiated_pcie_gen", ctypes.c_uint32),
("negotiated_pcie_width", ctypes.c_uint32),
("is_apple_silicon", ctypes.c_int),
("apple_gpu", _AppleGPUProperties),
]


Expand Down Expand Up @@ -82,11 +86,15 @@ class GPUProperties:
name: str
vendor_id: int
device_id: int
is_apple_silicon: bool
apple_gpu: Optional[AppleGPUProperties] # None for non-Apple GPUs
acpi_path: Optional[str]
pci_path: Optional[str]
vram_mb: int
capable_pcie_gen: int
capable_pcie_width: int
negotiated_pcie_gen: int
negotiated_pcie_width: int
is_apple_silicon: bool
apple_gpu: Optional[AppleGPUProperties] # None for non-Apple GPUs

def __str__(self) -> str:
lines = [
Expand Down Expand Up @@ -118,7 +126,7 @@ def get_gpu_info() -> list[GPUProperties]:
if count < 0:
raise RuntimeError("get_gpu_info() failed (C library returned -1)")

result = []
result: list[GPUProperties] = []
for i in range(count):
raw = buf[i]
apple = None
Expand All @@ -137,11 +145,15 @@ def get_gpu_info() -> list[GPUProperties]:
name=raw.name.decode("utf-8", errors="replace"),
vendor_id=raw.vendor_id,
device_id=raw.device_id,
is_apple_silicon=bool(raw.is_apple_silicon),
apple_gpu=apple,
acpi_path=acpi,
pci_path=pci,
vram_mb=raw.vram_mb,
capable_pcie_gen=raw.capable_pcie_gen,
capable_pcie_width=raw.capable_pcie_width,
negotiated_pcie_gen=raw.negotiated_pcie_gen,
negotiated_pcie_width=raw.negotiated_pcie_width,
is_apple_silicon=bool(raw.is_apple_silicon),
apple_gpu=apple,
)
)
return result
Expand Down
Binary file modified src/hwprobe/interops/mac/bindings/libdevice_info.dylib
Binary file not shown.
12 changes: 10 additions & 2 deletions src/hwprobe/interops/mac/include/gpu_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ typedef struct {
char name[256];
uint32_t vendor_id;
uint32_t device_id;
int is_apple_silicon;
AppleGPUProperties apple_gpu;

// non-apple-silicon properties
char acpi_path[512];
char pci_path[512];
uint64_t vram_mb;
uint32_t capable_pcie_gen;
uint32_t capable_pcie_width;
uint32_t negotiated_pcie_gen;
uint32_t negotiated_pcie_width;

// exclusively apple-silicon properties
int is_apple_silicon;
AppleGPUProperties apple_gpu;
} GPUProperties;

// Fills `out` with GPU entries. Returns number of GPUs found, or -1 on error.
Expand Down
5 changes: 5 additions & 0 deletions src/hwprobe/interops/mac/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ int main() {
std::cout << " PCI Path: " << g.pci_path << "\n";
if (g.vram_mb > 0)
std::cout << " VRAM: " << g.vram_mb << " MB\n";

std::cout << " PCIe Gen M: " << g.capable_pcie_gen << "\n";
std::cout << " PCIe Width M: " << g.capable_pcie_width << "\n";
std::cout << " PCIe Gen S: " << g.negotiated_pcie_gen << "\n";
std::cout << " PCIe Width S: " << g.negotiated_pcie_width << "\n";
std::cout << "\n";
}
}
Expand Down
45 changes: 45 additions & 0 deletions src/hwprobe/interops/mac/src/gpu_info.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "gpu_info.h"
#include "iokit_helpers.h"

#include <iostream>
#include <cstdint>
#include <string>
#include <cstring>
Expand All @@ -10,6 +11,9 @@
#include <IOKit/IOKitLib.h>
#include <sys/sysctl.h>

#define RESOLVE_PCIE_SPEED_PROPERTY(capability) (capability & 0xF)
#define RESOLVE_PCIE_WIDTH_PROPERTY(capability) ((capability >> 4) & 0x3F)

// ---- Internal helpers ----

static int getAppleGpuProperty(CFDictionaryRef gpuConfig, CFStringRef key) {
Expand Down Expand Up @@ -180,6 +184,31 @@ static std::string parseAcpiPath(CFDictionaryRef props) {
return result;
}

static void processPCIeInformationForGpu(GPUProperties *gpu, uint32_t capabilities, uint32_t negotiated) {
gpu->capable_pcie_gen = RESOLVE_PCIE_SPEED_PROPERTY(capabilities);
gpu->capable_pcie_width = RESOLVE_PCIE_WIDTH_PROPERTY(capabilities);

gpu->negotiated_pcie_gen = RESOLVE_PCIE_SPEED_PROPERTY(negotiated);
gpu->negotiated_pcie_width = RESOLVE_PCIE_WIDTH_PROPERTY(negotiated);
}

static bool readRecursiveUInt32Property(io_service_t service, CFStringRef key, uint32_t *value) {
if (!service || !value) {
return false;
}

CFTypeRef ref = IORegistryEntrySearchCFProperty(
service, kIOServicePlane, key,
kCFAllocatorDefault, kIORegistryIterateRecursively);
if (!ref) {
return false;
}

*value = static_cast<uint32_t>(readCFTypeAsUInt64(ref));
CFRelease(ref);
return true;
}

// ---- VRAM helper ----

static uint64_t getDiscreteVramMB(io_service_t service) {
Expand Down Expand Up @@ -245,6 +274,22 @@ int get_gpu_info(GPUProperties *out, int max_count) {
gpu.vendor_id = readUInt32(props, CFSTR("vendor-id"));
gpu.device_id = readUInt32(props, CFSTR("device-id"));

if (!is_arm) {
uint32_t capabilities = 0;
uint32_t negotiated = 0;
bool hasCapabilities = readRecursiveUInt32Property(
service, CFSTR("IOPCIExpressLinkCapabilities"), &capabilities);
bool hasNegotiated = readRecursiveUInt32Property(
service, CFSTR("IOPCIExpressLinkStatus"), &negotiated);

std::cout << "Capable PCIe Link: " << capabilities << "\n";
std::cout << "Negotiated PCIe Link: " << negotiated << "\n";

if (hasCapabilities && hasNegotiated) {
processPCIeInformationForGpu(&gpu, capabilities, negotiated);
}
}

CFTypeRef modelRef = CFDictionaryGetValue(props, CFSTR("model"));
if (modelRef) {
if (CFGetTypeID(modelRef) == CFStringGetTypeID()) {
Expand Down
42 changes: 32 additions & 10 deletions src/hwprobe/interops/mac/src/iokit_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,40 @@ std::string readCFString(CFStringRef cfStr) {
return {};
}

uint32_t readUInt32(const CFDictionaryRef dict, const CFStringRef key) {
const CFTypeRef ref = CFDictionaryGetValue(dict, key);
if (!ref || CFGetTypeID(ref) != CFDataGetTypeID())
uint32_t readUInt32(CFDictionaryRef dict, CFStringRef key) {
CFTypeRef ref = CFDictionaryGetValue(dict, key);
if (!ref) {
return 0;
uint32_t value = 0;
CFIndex len = CFDataGetLength(static_cast<CFDataRef>(ref));
if (len > 0) {
CFDataGetBytes(static_cast<CFDataRef>(ref),
CFRangeMake(0, std::min(static_cast<CFIndex>(sizeof(value)), len)),
reinterpret_cast<UInt8 *>(&value));
}
return value;

CFTypeID typeId = CFGetTypeID(ref);

if (typeId == CFNumberGetTypeID()) {
uint32_t value = 0;
if (CFNumberGetValue(static_cast<CFNumberRef>(ref), kCFNumberSInt32Type, &value)) {
return value;
}
return 0;
}

if (typeId == CFDataGetTypeID()) {
auto data = static_cast<CFDataRef>(ref);
CFIndex len = CFDataGetLength(data);
if (len <= 0) {
return 0;
}

uint32_t value = 0;
CFDataGetBytes(
data,
CFRangeMake(0, std::min<CFIndex>(len, sizeof(value))),
reinterpret_cast<UInt8 *>(&value)
);

return value;
}

return 0;
}

uint64_t readCFTypeAsUInt64(CFTypeRef ref) {
Expand Down
16 changes: 12 additions & 4 deletions src/hwprobe/models/gpu_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ class AppleExtendedGPUInfo(BaseModel):
gpu_gen: Optional[int] = None


class PCIeLinkValue(BaseModel):
"""The max/current value distinction for a PCIe Link"""
max: Optional[int] = None
current: Optional[int] = None

class PCIeLinkInfo(BaseModel):
"""Information about the PCIe Link for this GPU (gen/width)"""
gen: Optional[PCIeLinkValue] = None
width: Optional[PCIeLinkValue] = None


class GPUInfo(BaseModel):
"""Information for one GPU is stored here"""

Expand Down Expand Up @@ -47,10 +58,7 @@ class GPUInfo(BaseModel):
pci_path: Optional[str] = None

#: Number of lanes that the GPU occupies on the PCIe bus.
pcie_width: Optional[int] = None

#: PCIe generation supported by the GPU.
pcie_gen: Optional[int] = None
pcie_link: Optional[PCIeLinkInfo] = None

#: Total VRAM available on the GPU.
vram: Optional[StorageSize] = None
Expand Down
Loading