MI25 Driver Interfaces
Rough draft for review, 2026-08-23.
This document maps the externally exposed kernel interfaces of the Linux driver that controls the AMD MI25 GPU on beast.lan.
The driver implementation is the source of truth. Public documentation exists, but this document cites the driver source.
Environment
Verified 2026-08-23.
- Host: beast.lan.
- Kernel: 6.8.0-138-generic (Ubuntu).
- MI25: PCI 43:00.0, AMD Vega 10, device 0x6860, DRM card1.
- Running driver: amdgpu-dkms 6.19.14 (out-of-tree DKMS build).
- Running driver source: /usr/src/amdgpu-6.19.14-2377056.24.04/ on beast.lan.
- Local reference tree: ~/sources/linux, mainline 7.2/7.3.
- Module parameter ppfeaturemask = 0xffffffff.
- Debugfs is mounted on beast.lan.
- amdgpu exposes nothing under /proc/driver; only /proc/irq/160/amdgpu exists.
The local mainline tree is newer than the running DKMS driver. The hwmon and fan-control code is effectively identical between the two. Section “Source Drift” records the details.
hwmon surface
The hwmon directory is /sys/class/drm/card1/device/hwmon/hwmon1.
Temperature sensors
Verified 2026-08-23.
The driver defines exactly three temperature channels with these labels (amdgpu_pm.c, temp_label[], both trees):
- temp1: edge.
- temp2: junction.
- temp3: mem.
The old amd-mi25-control README called temp2 “hotspot” and temp3 “VRM”. The driver’s own name for temp2 is junction. For Vega 10, junction is the hot spot of the die. temp3 is the memory temperature, which tracks the VRM area on this card. The naming is corrected here; the control logic (max of temp2, temp3) is unchanged.
Live values (millidegrees Celsius):
- temp1_input = 48000 (48 C), temp1_crit = 85000.
- temp2_input = 49000 (49 C), temp2_crit = 105000.
- temp3_input = 48000 (48 C), temp3_crit = 95000.
The temperature read path is amdgpu_hwmon_show_temp in amdgpu_pm.c. Junction maps to AMDGPU_PP_SENSOR_HOTSPOT_TEMP, edge to EDGE_TEMP, mem to MEM_TEMP.
Fan control
Verified 2026-08-23.
- pwm1 = 71 (0-255; the value the old service last wrote).
- pwm1_enable = 1 (0 none, 1 manual, 2 automatic).
- pwm1_min = 0, pwm1_max = 255 (hardcoded in the driver).
- fan1_input = 134217728 (2^27; garbage, do not trust).
- fan1_min = 0, fan1_max = 0.
- fan1_target = 134217728 (garbage).
- fan1_enable = 1.
The hwmon attributes are defined in amdgpu_pm.c (hwmon_attributes table). Fan attributes are skipped when adev->pm.no_fan is set (hwmon_attributes_visible). The pwm1 and fan1_* attributes exist on this card, so no_fan is false.
Writing pwm1 requires pwm1_enable = 1 first. The driver rejects a pwm1 write in automatic mode with “manual fan speed control should be enabled first” (amdgpu_hwmon_set_pwm1, amdgpu_pm.c). pwm1_enable = 1 maps to AMD_FAN_CTRL_MANUAL, 2 to AMD_FAN_CTRL_AUTO.
Reading pwm1 returns the current hardware duty, scaled to 0-255 (vega10_fan_ctrl_get_fan_speed_pwm reads CG_THERMAL_STATUS.FDO_PWM_DUTY). The read value can differ from the last written value because the hardware steps the duty. The old service tracked the last written value, and that remains correct.
The driver source warns: do not set pwm1 and fan1_target at the same time; the former gets overridden. fan1_target control is unusable on this card anyway, because the tachometer returns garbage.
Other hwmon channels
Verified 2026-08-23.
- in0_input = 1081 (mV), label vddgfx.
- power1_input = 26000000 (uW, 26 W), power1_cap = 220000000 (220 W cap).
- freq1_input = 1500000000 (Hz, sclk 1.5 GHz), label sclk.
- freq2_input = 945000000 (Hz, mclk 945 MHz), label mclk.
Device sysfs surface
The device directory is /sys/class/drm/card1/device.
The attribute table is amdgpu_device_attrs in amdgpu_pm.c.
Verified 2026-08-23.
- power_dpm_state = performance (legacy interface; battery or performance).
- power_dpm_force_performance_level = high (auto, low, high, manual, profile_standard, profile_min_sclk, profile_min_mclk, profile_peak).
- pp_num_states = 2 (boot, performance).
- pp_cur_state = 1 (performance).
- pp_features = 0x0000000009a1fe4f; DPM features mostly enabled.
- pp_power_profile_mode: BOOTUP_DEFAULT active; 3D_FULL_SCREEN, POWER_SAVING, VIDEO, VR, COMPUTE, CUSTOM available.
- gpu_busy_percent = 0.
- mem_info_vram_total = 17163091968 (16 GiB), mem_info_vram_vendor = samsung.
- vbios_version = 113-D0513600-109.
- pp_dpm_sclk: 852, 991, 1138 MHz (levels visible).
- pp_table, pp_force_state, pp_od_clk_voltage, pp_sclk_od, pp_mclk_od, thermal_throttling_logging, gpu_metrics, pm_metrics also exist.
The power_dpm_force_performance_level values are documented in amdgpu_pm.c (DOC: power_dpm_force_performance_level). The old service writes “high”, which forces the highest DPM level.
debugfs surface
Debugfs is mounted on beast.lan (/sys/kernel/debug, rw). Reading the dri tree requires root; sudo is available.
Verified 2026-08-23.
- /sys/kernel/debug/dri/0 is the GTX 1050 (0000:05:00.0).
- /sys/kernel/debug/dri/1 is the MI25 (0000:43:00.0).
- amdgpu_pm_info (dri/1) shows clocks, voltage, power, and GPU temperature.
- amdgpu_pm_prv_buffer exists when smu_prv_buffer_size > 0.
- amdgpu_debugfs.c registers amdgpu_benchmark, amdgpu_vbios, amdgpu_smu_debug, unique_id, unitid, and others.
amdgpu_pm_info live sample (2026-08-23): MCLK 945 MHz, SCLK 1500 MHz, VDDGFX 1081 mV, SoC power 27 W, GPU temperature 46 C.
debugfs availability is not guaranteed
Verified 2026-08-23 against the kernel sources and the live host.
debugfs is a diagnostics filesystem, not a stable interface. The kernel documentation says so directly (Documentation/filesystems/debugfs.rst):
- debugfs has no rules; developers can put any information they want there.
- debugfs is intended to not serve as a stable ABI to user space.
- There are, in theory, no stability constraints on files exported there.
- Even debugfs interfaces are best designed with the idea that they will need to be maintained forever.
A host may lack debugfs for several independent reasons:
- The kernel may be built without CONFIG_DEBUG_FS.
- The kernel may be built with CONFIG_DEBUG_FS_ALLOW_NONE or CONFIG_DEBUG_FS_DISALLOW_MOUNT.
- The kernel lockdown policy may block debugfs access.
- debugfs may simply not be mounted.
- The debugfs root is accessible only to root by default.
On beast.lan the state is (verified 2026-08-23):
- CONFIG_DEBUG_FS=y, CONFIG_DEBUG_FS_ALLOW_ALL=y.
- Lockdown is none (integrity and confidentiality available but not forced).
- debugfs is mounted by the sys-kernel-debug.mount systemd unit.
- The debugfs root directory is drwx—— root root.
- Reading the dri tree requires root; sudo works.
A fan-control service must treat debugfs as optional diagnostics. The service must not depend on debugfs for control or monitoring. The control path is sysfs hwmon, which is the stable interface. The service should read debugfs only when it is present and readable, and it should degrade cleanly when it is not.
/proc situation
amdgpu registers no /proc/driver/amdgpu directory. Only /proc/irq/160/amdgpu exists, which is the IRQ affinity mask. All driver state is exposed through sysfs and debugfs, not /proc.
Fan-control call path (Vega 10)
Traced in the local mainline tree; the DKMS 6.19.14 tree is identical.
pwm1 write:
- amdgpu_hwmon_set_pwm1 (amdgpu_pm.c).
- Checks current mode is AMD_FAN_CTRL_MANUAL, else returns -EINVAL.
- amdgpu_dpm_set_fan_speed_pwm (amdgpu_dpm.c).
- pp_funcs->set_fan_speed_pwm.
- vega10_fan_ctrl_set_fan_speed_pwm (vega10_thermal.c).
- Returns 0 without writing if fanInfo.bNoFan is set.
- Clamps speed to 255.
- Stops SMC fan control if PHM_PlatformCaps_MicrocodeFanControl.
- Reads FMAX_DUTY100 from CG_FDO_CTRL1; returns -EINVAL if zero.
- Writes duty = speed * duty100 / 255 to CG_FDO_CTRL0.FDO_STATIC_DUTY.
- Sets static mode (FDO_PWM_MODE_STATIC).
pwm1_enable write:
- amdgpu_hwmon_set_pwm1_enable (amdgpu_pm.c).
- Maps 0/1/2 to AMD_FAN_CTRL_NONE/MANUAL/AUTO.
- amdgpu_dpm_set_fan_control_mode (amdgpu_dpm.c).
- vega10_set_fan_control_mode (vega10_hwmgr.c).
- NONE: set fan speed pwm to 255.
- MANUAL: stop SMC fan control (if MicrocodeFanControl).
- AUTO: start SMC fan control (if MicrocodeFanControl).
Fan RPM read:
- amdgpu_hwmon_get_fan1_input (amdgpu_pm.c).
- vega10_fan_ctrl_get_fan_speed_rpm (vega10_thermal.c).
- If GNLD_FAN_CONTROL supported, vega10_get_current_rpm sends PPSMC_MSG_GetCurrentRpm to the SMC.
- The SMC response on this card is 2^27, which is not a valid RPM.
The MicrocodeFanControl capability decides whether the SMC owns fan control. The old README’s observation that the MI25 firmware cannot control the fan is consistent with the no_fan and MicrocodeFanControl paths in this code.
Implications for the fan-control service
- Write pwm1_enable = 1 before writing pwm1; the driver rejects manual pwm1 writes otherwise.
- Use temp2 (junction) and temp3 (mem) for control, max of the two.
- Track the last written pwm1; do not trust pwm1 read-back.
- Do not use fan1_target or fan1_input; the tachometer is garbage on this card.
- Temperature limits from the card: edge crit 85 C, junction crit 105 C, mem crit 95 C.
- The old README’s “keep VRM under 90 C” maps to the mem sensor.
- pwm1_enable = 2 hands control back to the automatic path.
Source Drift
Verified 2026-08-23.
- Running kernel: 6.8.0-138-generic.
- Running driver: amdgpu-dkms 6.19.14 (out-of-tree, DKMS).
- Local reference tree: mainline 7.2/7.3.
- The hwmon attribute table and the Vega 10 fan-control functions are identical between the DKMS source and the local tree.
- Line numbers differ by a small offset (hwmon table ~6 lines).
- No behavioral difference found in the fan-control path.
Driver version detection and adaptation
The service should recognize and adapt to changing driver behavior across kernel and driver versions, as far as the interfaces allow.
What identifies the driver
Verified 2026-08-23.
The driver version is exposed in two places:
- /sys/module/amdgpu/version
- modinfo amdgpu (version field)
The DKMS driver on beast.lan reports version 6.19.14.31400100. The in-tree driver on athena (mainline 7.0) reports no version: /sys/module/amdgpu/version is empty and modinfo shows no version line. The kernel release is available from uname -r in both cases.
The service should record the driver version when present. The service should record the kernel release always. An empty driver version is expected for in-tree drivers and is not an error.
Distinguishing in-tree from out-of-tree drivers
Verified 2026-08-23 against the kernel source and both live hosts.
/proc/modules shows taint flags for each loaded module (kernel/module/main.c, kernel/panic.c). The flags that matter here:
- O = TAINT_OOT_MODULE — the module is out-of-tree.
- E = TAINT_UNSIGNED_MODULE — the module is unsigned.
The DKMS amdgpu on beast.lan shows “Live … (OE)”: out-of-tree and unsigned. The in-tree amdgpu on athena shows “Live …” with no flags.
The service can read /proc/modules to tell the two apart. An out-of-tree driver (OE) is the AMD DKMS build, which reports its version in /sys/module/amdgpu/version. An in-tree driver has no flags and no version file. This is a cheap, reliable adaptation signal.
What the service must probe, not assume
The interface surface is stable enough to rely on, but the service should verify each attribute before using it, so a changed driver degrades gracefully instead of failing hard.
Probe at startup:
- pwm1_enable exists and accepts 0, 1, 2.
- pwm1 exists and accepts writes in manual mode.
- temp1_input, temp2_input, temp3_input exist and report plausible values.
- temp2_label and temp3_label read junction and mem; if the labels change, log the change and continue using the channel indexes.
- fan1_input reports a plausible RPM; on this card it does not, so the service must not rely on it.
Probe behavior empirically:
- Writing pwm1_enable=1 then pwm1 must change the fan.
- The driver rejects pwm1 writes before pwm1_enable=1; the service must write pwm1_enable=1 first, always.
- pwm1 read-back may differ from the last written value; track the written value, not the read value.
Adaptation strategy
- Treat sysfs hwmon as the control interface; debugfs as optional diagnostics.
- Discover the hwmon directory by scanning for pwm1_enable, not by a fixed path; the card number can change across reboots.
- Verify attribute presence and semantics at startup; log what was found.
- On missing or changed attributes, fall back to the closest working set and log the degradation.
- Keep the written PWM value as the source of truth; use read-back only as a sanity check.
- Record the driver version and kernel release with the startup log, so a behavior change can be correlated with a driver change.
- Do not depend on pwm1 read-back, fan1_input, or fan1_target; all are unreliable on this card.
Known stability across versions
Verified 2026-08-23.
- Sensor channels and labels (edge, junction, mem) are identical between the DKMS 6.19.14 source and the local mainline 7.2/7.3 source.
- The pwm1 / pwm1_enable / fan1_* attribute set is identical in both.
- The requirement to write pwm1_enable=1 before pwm1 is identical in both.
- The Vega 10 fan-control call path is identical in both.
- The attribute table line numbers differ by a small offset only.
This is evidence the interface is stable across recent versions, but the service should still probe rather than assume.
Special filesystems for driver and hardware introspection
Four special filesystems are relevant to introspecting kernel drivers and hardware device trees: /sys, /proc, /dev, and /run. Their usefulness differs sharply.
/sys (sysfs) — the primary interface
sysfs is the main interface for driver and hardware introspection. All driver state and device-tree structure is exposed here.
- /sys/bus/pci/devices/ — the PCI device tree (one directory per device).
- /sys/class/drm/ — DRM devices: cardN, renderDN, and per-card directories.
- /sys/class/drm/cardN/device/hwmon/hwmonN/ — the hwmon sensor surface.
- /sys/class/drm/cardN/device/ — device attributes (pp_, power_dpm_, mem_info_*, gpu_busy_percent).
- /sys/module/amdgpu/ — module parameters and version.
- /sys/class/drm/cardN/device/driver — symlink to the bound driver.
The fan-control service reads and writes almost entirely under /sys.
/proc (procfs) — secondary, still useful
procfs is process-centric but carries system-wide kernel state.
- /proc/version — the kernel version string.
- /proc/sys/kernel/osrelease — the clean kernel release, e.g. 6.8.0-138-generic.
- /proc/modules — loaded modules with taint flags.
- /proc/interrupts — IRQ usage, including the amdgpu MSI-X interrupt.
- /proc/iomem — the memory resource map, including PCI BAR regions.
- /proc/devices — registered character and block device numbers.
amdgpu registers no /proc/driver/amdgpu directory (see the /proc section).
/dev (devtmpfs) — access nodes, not introspection
/dev holds device nodes, which are the access path, not an introspection surface.
- /dev/dri/cardN and /dev/dri/renderDN — DRM access nodes.
- /dev/kfd — the AMD kernel fusion driver node (ROCm).
- /dev/shm, /dev/mem, and others — general access nodes.
Presence of a node tells you a device exists. Attribute data lives in /sys.
/run (tmpfs) — runtime state, not kernel introspection
/run holds userspace runtime state.
- /run/udev/ — the udev database of device metadata and symlinks.
- /run/lock, /run/user, and others — userspace state.
/run is not a kernel interface and carries no driver attribute data. udev metadata is derived from the kernel; /sys remains the source of truth.
Summary for the service
- /sys is the control and monitoring surface.
- /proc supplements it for kernel-level facts (version, modules, IRQs, memory map).
- /dev provides access nodes only.
- /run is not needed for introspection.
Kernel version: uname or /proc?
Both work; the choice is about robustness, not information.
- uname is a utility that calls the uname(2) syscall.
- /proc/version and /proc/sys/kernel/osrelease expose the same kernel data.
- /proc/sys/kernel/osrelease contains exactly the release string, e.g. 6.8.0-138-generic, which matches uname -r.
- /proc/version contains the full build string, including the compiler and build date.
For the service, call uname(2) directly in code. The syscall needs no external utility and works even if procfs is not mounted. Reading /proc/sys/kernel/osrelease is an acceptable alternative when procfs is present. Do not parse /proc/version for the release; use osrelease or the syscall.
The driver version is a separate fact from the kernel release. The DKMS driver exposes /sys/module/amdgpu/version; the in-tree driver exposes nothing there. The kernel release is always available from uname(2) or osrelease.
References
- ~/sources/linux/drivers/gpu/drm/amd/pm/amdgpu_pm.c
- ~/sources/linux/drivers/gpu/drm/amd/pm/amdgpu_dpm.c
- ~/sources/linux/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_thermal.c
- ~/sources/linux/drivers/gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c
- ~/sources/linux/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
- ~/sources/linux/Documentation/gpu/amdgpu/ (driver-core, driver-misc, debugfs, thermal)
- /usr/src/amdgpu-6.19.14-2377056.24.04/ (DKMS source on beast.lan)