Skip to content

Library API

Le Khanh Binh edited this page Sep 12, 2026 · 3 revisions

Library API

The zenmaster Python package provides programmatic interfaces for AMD SMU mailbox communication, power management telemetry decoding, System Management Network (SMN) access, and CPU identification. The package ships type definitions (py.typed), requires no third-party dependencies, and exposes identical APIs across Linux, Windows, and macOS. Non-Python processes can interface with the runtime via zenmaster --json (see CLI Usage).

import zenmaster
print(zenmaster.__version__)

Module Layout and Compatibility

ZenMaster organizes functionality across modular subpackages while maintaining backward-compatible aliases:

zenmaster/
├── __init__.py        # Root namespace exports, backward compatibility aliases
├── apply.py           # Command parsing, Curve Optimizer bitfields, SMU dispatch
├── cli.py             # CLI parser, --info, --sensors, --table formatters
├── errors.py          # Typed exception class hierarchy
├── hardware.py        # CPU detection, 44-family CPUID decoding, resolve()
├── mailbox.py         # MP1, RSMU, HSMP register definitions, polling loops
├── pmtable.py         # PM_TABLE_CMDS, 86 TABLE_SIZES definitions
├── runner.py          # 10 socket mappings, opcode tables, query flags
├── sensors.py         # PM table parser, PmSensors, CoreSensors (replaces table.py)
├── smu.py             # Public SMU interface, backend dispatch, get_ccd_count
├── update.py          # PyPI version verification
├── linux.py           # Linux backend (PCI root complex, ryzen_smu, /run/lock mutex)
├── windows.py         # Windows backend (PawnIO driver, Global\Access_PCI mutex)
├── macos.py           # macOS backend (DirectHW / IOPCI, /tmp/access_pci.lock mutex)
├── iokit.py           # IOKit ctypes interface (replaces iokitcore.py)
├── iopci.py           # IOPCIBridge kext-free diagnostics client
└── directhw.py        # DirectHW.kext user client interface

Compatibility Aliases

  • zenmaster.sensors: Telemetry decoders and sensor dataclasses. Aliased as zenmaster.table = zenmaster.sensors and registered in sys.modules["zenmaster.table"].
  • zenmaster.iokit: macOS IOKit framework bindings. Aliased as zenmaster.iokitcore = zenmaster.iokit and registered in sys.modules["zenmaster.iokitcore"].
  • zenmaster.iopci: Kext-free Apple IOPCIBridge diagnostics client.

Top-Level Exports

The root zenmaster module exports 53 symbols via __all__:

from zenmaster import (
    # Hardware & Detection
    CpuInfo, detect, resolve,
    # Tuning Execution
    apply, ApplyResult,
    # Core Submodules
    runner, smu, sensors, table, iokit, iokitcore,
    # Sensors & Telemetry
    SmuStatus, PmSensors, read_sensors, read_pm_sensors,
    CoreSensors, read_core_sensors, read_pm_core_sensors,
    # Driver & Module Status
    ModuleStatus, module_status, module_version, module_version_ok,
    secure_boot_enabled, is_available, init, close, ensure_backend,
    send_arg, unavailable_reason, driver_name, active_backend,
    # SMU Mailbox Primitives
    pm_table_supported, send_mp1, send_rsmu, send_hsmp,
    query_mp1, query_rsmu, query_hsmp,
    read_pm_table, read_pm_table_version, read_pm_table_full,
    get_bios_if_ver, get_smu_version, format_smu_version,
    # Direct SMN & Fuse Detection
    read_smn, write_smn, get_ccd_count,
    # Maintenance & Versioning
    check_update, __version__,
    # Typed Exceptions
    ZenMasterError, BackendUnavailable, SMUNotInitialized, UnsupportedCPU,
)

Exception Hierarchy

All custom exceptions inherit from ZenMasterError, which subclasses RuntimeError:

ZenMasterError (RuntimeError)
├── BackendUnavailable     # Hardware access mechanism inaccessible or unprivileged
├── SMUNotInitialized      # Mailbox or SMN primitive invoked before smu.init()
└── UnsupportedCPU         # CPU family lacks opcode tables or is non-AMD

Exception Definitions

class ZenMasterError(RuntimeError):
    """Base exception for all ZenMaster runtime errors."""

class BackendUnavailable(ZenMasterError):
    """Raised when hardware access drivers or PCI config nodes are unavailable.
    Appends the wiki installation documentation URL to the error message."""
    _INSTALL_DOCS = "https://github.com/HorizonUnix/ZenMaster/wiki/Installation"

class SMUNotInitialized(ZenMasterError):
    """Raised when calling mailbox or SMN routines prior to smu.init()."""

class UnsupportedCPU(ZenMasterError):
    """Raised when the processor is not an AMD CPU or lacks command definitions."""

Usage pattern:

from zenmaster import smu, BackendUnavailable, UnsupportedCPU, ZenMasterError

try:
    backend = smu.init()
except BackendUnavailable as exc:
    print(f"Driver unavailable: {exc}")
except UnsupportedCPU as exc:
    print(f"Processor unsupported: {exc}")
except ZenMasterError as exc:
    print(f"Runtime failure: {exc}")

CPU Detection

Hardware detection queries CPUID registers and system files without requiring elevated privileges.

from zenmaster import detect

info = detect()
print(info.name)              # "AMD Ryzen 9 7950X 16-Core Processor"
print(info.family)            # "Raphael"
print(info.arch)              # "Zen 3 - Zen 4"
print(info.type)              # "Amd_Desktop_Cpu"
print(info.cpu_family_int)    # 25
print(info.cpu_model_int)     # 97
print(info.cpu_stepping_int)  # 2

Platform detection paths:

  • Linux: Parses model name, cpu family, model, and stepping from /proc/cpuinfo.
  • Windows: Reads PROCESSOR_IDENTIFIER and queries HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\ProcessorNameString.
  • macOS: Queries machdep.cpu.brand_string, machdep.cpu.family, machdep.cpu.model, and machdep.cpu.stepping via sysctlbyname.

Results from detect() are cached for process lifetime.

Manual Resolution

To evaluate a specific CPUID configuration without probing local hardware, call resolve():

from zenmaster import resolve

info = resolve("AMD Ryzen 7 7840U", 25, 116, 1)
print(info.family)  # "PhoenixPoint"
print(info.type)    # "Amd_Apu"

resolve(name: str, cpu_family_int: int, cpu_model_int: int, cpu_stepping_int: int = 0) -> CpuInfo recalculates architecture and codename parameters on every call without caching.

CpuInfo Structure

@dataclass
class CpuInfo:
    name: str
    arch: str
    family: str
    type: str              # "Amd_Desktop_Cpu" | "Amd_Apu" | "Intel" | "Unknown"
    cpu_family_int: int
    cpu_model_int: int
    cpu_stepping_int: int = 0

Runner Module Inspection

The zenmaster.runner module contains static socket command tables and opcode definitions. It performs no hardware transactions and runs without elevation.

from zenmaster import runner

socket_name = runner.get_socket("Raphael")             # "AM5_V1"
args = runner.get_supported_args("Raphael")            # ['vrm-current', 'vrmmax-current', ...]
is_supported = runner.is_supported("Raphael")          # True
is_flag = runner.is_flag_arg("enable-oc")              # True
is_hsmp = runner.is_hsmp("Medusa1")                    # True

# Retrieve list of (is_mp1, opcode) tuples
commands = runner.lookup("Raphael", "fast-limit")      # [(True, 0x25)]

is_mp1=True targets the MP1 mailbox. is_mp1=False targets the RSMU mailbox. If runner.is_hsmp(family) evaluates to True, operations target the HSMP mailbox.


Backend Lifecycle and Status

Interacting with SMU mailboxes, SMN registers, and PM tables requires root or administrative privileges.

Backend Lifecycle

from zenmaster import smu

# Idempotent initialization. Returns active backend string.
# Raises BackendUnavailable on failure.
backend = smu.init()

# Check active backend name without raising exceptions
current = smu.active_backend()    # "pci", "ryzen_smu", "pawnio", "directhw", "iopci", or None

# Non-throwing initialization helper
available_backend = smu.ensure_backend()  # Returns backend string or None

# Query failure reason if initialization fails
reason = smu.unavailable_reason()  # None if ready, or formatted string

# Release device handles and file descriptors
smu.close()

Driver and Module Status

from zenmaster import smu

status = smu.module_status()
print(status.ok)           # True if driver is present and meets version requirements
print(status.version)      # Driver version string (e.g. "0.1.7")
print(status.min_version)  # Minimum version requirement ("0.1.7" on Linux ryzen_smu)
print(status.reason)       # None, or "not_loaded", "not_installed", "too_old", etc.

driver = smu.driver_name()               # "PCI direct access", "ryzen_smu", "PawnIO", etc.
version_ok = smu.module_version_ok()     # True if driver meets version threshold
sb_active = smu.secure_boot_enabled()    # Linux Secure Boot state (False on Windows/macOS)

ModuleStatus definition:

@dataclass
class ModuleStatus:
    ok: bool
    version: str
    min_version: str
    reason: str | None

Applying Tuning Presets

The apply() function accepts CLI-formatted parameter strings, resolves argument scaling and bitfields, and issues SMU commands.

from zenmaster import apply, detect, smu

info = detect()
smu.init()

preset = "--fast-limit=35000 --slow-limit=28000 --tctl-temp=85"
results, rejected = apply(preset, info.family)

for r in results:
    if r["error"]:
        print(f"Error on {r['arg']}: {r['error']}")
    else:
        print(f"{r['arg']} via {r['mailbox']} (0x{r['opcode']:02X}): {smu.status_name(r['status'])}")

if rejected:
    print("One or more arguments failed execution.")

Signature:

def apply(args_str: str, family: str) -> tuple[list[ApplyResult], bool]
  • args_str: Argument string using CLI syntax (e.g. "--set-coall=-20 --fast-limit=45000").
  • family: Detected CPU family codename.
  • Returns (results, rejected). rejected is True if any parameter failed parsing, was unsupported on the target family, or returned an SMU error.

ApplyResult Structure

class ApplyResult(TypedDict):
    arg: str            # Normalized parameter name (e.g. "fast-limit")
    value: int          # Encoded 32-bit payload sent to SMU Arg0
    mailbox: str        # "MP1", "RSMU", "HSMP", or "" on parse error
    opcode: int         # Hardware opcode written to SMU
    status: int         # SmuStatus return code
    error: str | None   # Description of parse/lookup error, or None on success
    returned: int | None # Register value returned by get-* queries, else None

Parameter Encoding Rules in apply()

  • Power limits: Converted to milliwatts (e.g. 35000 = 35 W).
  • Temperatures: Converted to degrees Celsius. Values >= 1000 are divided by 1000.
  • PBO Scalar: On HSMP platforms, values >= 100 are divided by 10.
  • APU Skin Temperature: Gated to verified APU families and multiplied by 256 (fixed-point 8.8).
  • Curve Optimizer (--set-coper): Supports core:val, ccd:core:val, and ccd:ccx:core:val. Scaled using 20-bit two's complement (0x100000 - abs(val)). On HSMP, maps to APIC ID and 16-bit signed PSM margin.

Low-Level SMU Mailbox Access

Callers can bypass preset parsing and invoke SMU mailbox operations directly.

from zenmaster import smu, detect, SmuStatus

info = detect()
smu.init()

# Write operation: send_mp1, send_rsmu, send_hsmp
status = smu.send_mp1(info.family, 0x25, 45000)
if status == SmuStatus.OK:
    print("Command accepted.")

# Read operation: query_mp1, query_rsmu, query_hsmp
status, args_out = smu.query_mp1(info.family, 0x02, 1)
if status == SmuStatus.OK:
    print(f"SMU raw version word: 0x{args_out[0]:08X}")

Signatures:

smu.send_mp1(family: str, op: int, arg0: int = 0) -> int
smu.send_rsmu(family: str, op: int, arg0: int = 0) -> int
smu.send_hsmp(family: str, op: int, arg0: int = 0) -> int

smu.query_mp1(family: str, op: int, arg0: int = 0) -> tuple[int, list[int]]
smu.query_rsmu(family: str, op: int, arg0: int = 0) -> tuple[int, list[int]]
smu.query_hsmp(family: str, op: int, arg0: int = 0) -> tuple[int, list[int]]

Calling these primitives before smu.init() raises SMUNotInitialized.

Hardware Response Codes

from zenmaster import SmuStatus, smu

SmuStatus.OK               # 0x01: Operation completed successfully
SmuStatus.FAILED           # 0xFF: Mailbox timeout or communication failure
SmuStatus.UNKNOWN_CMD      # 0xFE: Opcode not recognized by active SMU firmware
SmuStatus.REJECTED_PREREQ  # 0xFD: Command rejected (transient state; retried automatically)
SmuStatus.REJECTED_BUSY    # 0xFC: SMU coprocessor busy

smu.status_name(0x01)      # "OK"
smu.status_name(0xFD)      # "Rejected (prerequisite)"

Firmware Version Helpers

from zenmaster import smu, detect

info = detect()
smu.init()

bios_if = smu.get_bios_if_ver(info.family)         # Integer BIOS interface version
smu_ver_int = smu.get_smu_version(info.family)     # Integer SMU firmware version word
formatted = smu.format_smu_version(smu_ver_int)    # Formatted string, e.g. "84.79.0"

send_arg() Primitive

from zenmaster import smu

# Issues value across all mailbox entries associated with an argument name
results = smu.send_arg("Raphael", "fast-limit", 35000)
for mailbox_name, opcode, status in results:
    print(f"{mailbox_name} 0x{opcode:02X} -> {smu.status_name(status)}")

send_arg(family: str, name: str, value: int) -> list[tuple[str, int, int]] raises UnsupportedCPU if family has no registered opcode tables.


Direct SMN and Fuse Primitives

ZenMaster provides direct 32-bit System Management Network (SMN) register access:

from zenmaster import smu

smu.init()

# Read 32-bit DWORD from SMN space
val = smu.read_smn(0x0005D218)

# Write 32-bit DWORD to SMN space
smu.write_smn(0x0005D218, 0x12345678)

Dynamic CCD Counting

Physical Core Complex Die (CCD) count is extracted directly from SMN fuse registers rather than inferred from core counts:

from zenmaster import smu

smu.init()
ccd_count = smu.get_ccd_count()  # Returns active physical CCD count (1..16)

get_ccd_count(family_int: int = 0, model_int: int = 0) -> int evaluates SMN registers 0x5D218 and 0x5D21C (with +0x40 offset on Family 23 non-Matisse CPUs) using bitfield mask equations:

disabled = ((down & 0x3F) << 2) | ((present >> 30) & 0x3)
enabled = ((present >> 22) & 0xFF) & ~disabled
count = enabled.bit_count()

Power Management (PM) Table and Telemetry

PM tables contain realtime hardware metrics transferred by the SMU coprocessor into DRAM.

from zenmaster import smu, detect

info = detect()
smu.init()

if smu.pm_table_supported(info.family):
    # Retrieve raw binary buffer and version word
    result = smu.read_pm_table_full(info.family)
    if result:
        table_bytes, table_version = result
        print(f"Version: 0x{table_version:08X}, Size: {len(table_bytes)} bytes")

Package Telemetry (read_pm_sensors, PmSensors)

from zenmaster import smu, detect

info = detect()
sensors = smu.read_pm_sensors(info.family)  # Returns PmSensors or None

if sensors:
    print(f"Tctl Temp: {sensors.tctl_temp} °C")
    print(f"Socket Power: {sensors.socket_power} W")
    print(f"CCLK Busy: {sensors.cclk_busy} MHz")
    print(f"Fast Limit: {sensors.fast_limit} W")

PmSensors dataclass definition:

@dataclass
class PmSensors:
    stapm_limit: float | None;  stapm_value: float | None
    fast_limit: float | None;   fast_value: float | None
    slow_limit: float | None;   slow_value: float | None
    tctl_temp: float | None;    cclk_busy: float | None
    socket_power: float | None; gfx_clk: float | None
    gfx_temp: float | None;     mem_clk: float | None

Per-Core Telemetry (read_core_sensors, read_pm_core_sensors, CoreSensors)

ZenMaster decodes telemetry per physical core across multi-CCD topologies, filtering out down-fused cores:

from zenmaster import smu, detect

info = detect()
cores = smu.read_pm_core_sensors(info.family)  # list[dict[str, float]] | None

if cores:
    for c in cores:
        print(f"Core {int(c['core'])}: {c.get('power', 0.0):.2f} W, "
              f"{c.get('volt', 0.0):.3f} V, {c.get('clk', 0.0):.0f} MHz")

When operating on raw table bytes:

from zenmaster import sensors

core_list = sensors.read_core_sensors(data=table_bytes, ver=table_version, ccd_count=2)
for core in core_list:
    print(core.core, core.power, core.volt, core.temp, core.clk)

CoreSensors definition:

@dataclass
class CoreSensors(dict[str, float]):
    core: int
    power: float | None = None
    volt: float | None = None
    temp: float | None = None
    clk: float | None = None
    freqeff: float | None = None
    c0: float | None = None
    cc1: float | None = None
    cc6: float | None = None

Raw Table Decoding (read_table)

from zenmaster.sensors import read_table

# Returns list of (label, value, flag) tuples
rows = read_table(table_bytes, table_version)
for label, val, flag in rows:
    print(f"{label:<25} : {val:.2f}")

Update Verification

Check whether a newer version is published to PyPI:

import zenmaster

newer_version = zenmaster.check_update()
if newer_version:
    print(f"ZenMaster {newer_version} is available on PyPI.")

Practical Examples

Example 1: Real-Time Multi-Core Telemetry Monitor

import time
import sys
from zenmaster import smu, detect

info = detect()
if smu.ensure_backend() is None:
    sys.exit("Hardware backend unavailable. Elevated privileges required.")

if not smu.pm_table_supported(info.family):
    sys.exit(f"PM table unsupported on {info.family}.")

print(f"Monitoring {info.name} ({info.family})")
try:
    while True:
        cores = smu.read_pm_core_sensors(info.family)
        if cores:
            print("-" * 65)
            print(f"{'Core':<6}{'Power (W)':<12}{'Volt (V)':<12}{'Temp (°C)':<12}{'Clock (MHz)':<12}")
            print("-" * 65)
            for c in cores:
                print(f"{int(c['core']):<6}{c.get('power', 0.0):<12.2f}"
                      f"{c.get('volt', 0.0):<12.3f}{c.get('temp', 0.0):<12.1f}"
                      f"{c.get('clk', 0.0):<12.0f}")
        time.sleep(1.0)
except KeyboardInterrupt:
    pass

Example 2: Dynamic Power Limit Management

import sys
from zenmaster import smu, detect, apply, SmuStatus

info = detect()
if smu.ensure_backend() is None:
    sys.exit("Hardware backend unavailable. Elevated privileges required.")

# Configure PPT limits and Curve Optimizer offset on Core 0
cmd = "--fast-limit=45000 --slow-limit=35000 --set-coper=0:-20"
results, rejected = apply(cmd, info.family)

for r in results:
    status_str = smu.status_name(r["status"]) if r["status"] is not None else "ERROR"
    print(f"Parameter: {r['arg']:<15} Mailbox: {r['mailbox']:<6} Status: {status_str}")

if rejected:
    print("Warning: One or more parameters were rejected by firmware.")

Clone this wiki locally