-
Notifications
You must be signed in to change notification settings - Fork 0
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__)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
-
zenmaster.sensors: Telemetry decoders and sensor dataclasses. Aliased aszenmaster.table = zenmaster.sensorsand registered insys.modules["zenmaster.table"]. -
zenmaster.iokit: macOS IOKit framework bindings. Aliased aszenmaster.iokitcore = zenmaster.iokitand registered insys.modules["zenmaster.iokitcore"]. -
zenmaster.iopci: Kext-free Apple IOPCIBridge diagnostics client.
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,
)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
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}")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) # 2Platform detection paths:
-
Linux: Parses
model name,cpu family,model, andsteppingfrom/proc/cpuinfo. -
Windows: Reads
PROCESSOR_IDENTIFIERand queriesHKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\ProcessorNameString. -
macOS: Queries
machdep.cpu.brand_string,machdep.cpu.family,machdep.cpu.model, andmachdep.cpu.steppingviasysctlbyname.
Results from detect() are cached for process lifetime.
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.
@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 = 0The 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.
Interacting with SMU mailboxes, SMN registers, and PM tables requires root or administrative privileges.
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()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 | NoneThe 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).rejectedisTrueif any parameter failed parsing, was unsupported on the target family, or returned an SMU error.
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-
Power limits: Converted to milliwatts (e.g.
35000= 35 W). -
Temperatures: Converted to degrees Celsius. Values
>= 1000are divided by 1000. -
PBO Scalar: On HSMP platforms, values
>= 100are divided by 10. - APU Skin Temperature: Gated to verified APU families and multiplied by 256 (fixed-point 8.8).
-
Curve Optimizer (
--set-coper): Supportscore:val,ccd:core:val, andccd: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.
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.
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)"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"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.
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)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()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")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 | NoneZenMaster 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 = Nonefrom 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}")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.")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:
passimport 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.")PyPI | Report an Issue | Releases | Under GPL-3.0 by HorizonUnix
Getting started
Monitoring
Developers
Help