Fan Control Service Plan

Rough draft for review, 2026-08-23.

This plan translates documents/05-mi25-driver-interfaces.md into a concrete design for the fan-control service. It is the deliverable of episode 03 (fan-control-interface-plan).

Goal

A C++ service that runs continuously under systemd and keeps the MI25 cool by writing PWM values through the amdgpu hwmon interface. The service must probe and adapt to the driver, not assume a fixed interface.

Interface classification

From documents/05-mi25-driver-interfaces.md.

Control surface (the service writes these)

Monitoring surface (the service reads these)

Fingerprinting surface (the service reads these at startup)

Optional diagnostics (never required)

Unused

Card discovery and enumeration

The service must not assume card1. The service controls every MI25 found.

Enumeration algorithm:

  1. Iterate /sys/class/drm/card*/device/.
  2. Read vendor and device files.
  3. Keep every card with vendor 0x1002 (AMD).
  4. Verify the driver symlink points to amdgpu.
  5. Find the hwmon directory with pwm1_enable.
  6. Verify temp1_input, temp2_input, temp3_input, and pwm1 exist.
  7. A card is controllable when device is 0x6860 (MI25) and steps 4-6 pass.
  8. Report every AMD card and its controllable state.

The –check option enumerates and reports, then exits. The service itself controls every controllable card.

The SYSFS_ROOT environment variable overrides /sys for tests.

Startup sequence

  1. Record kernel release and driver version in the startup log.
  2. Enumerate AMD cards and identify the controllable MI25 set (retry while nodes appear).
  3. Verify the required attributes exist on each; log what was found.
  4. For each controllable card: write pwm1_enable = 1.
  5. For each controllable card: write power_dpm_force_performance_level = manual.
  6. For each controllable card: write pp_power_profile_mode = 5 (COMPUTE).
  7. For each controllable card: write the minimum PWM value to pwm1.
  8. Enter the control loop over every controllable card.

The hwmon node may appear late at boot. Discovery retries with a short delay until at least one controllable card appears or a timeout elapses. On timeout, log the failure and exit nonzero for systemd to restart.

Control algorithm

Modeled on the reference actor6 service.

Constants:

Loop:

  1. Read temp2 (junction) and temp3 (mem).
  2. Control temperature = max(junction, mem).
  3. If the control temperature changed by at least the delta, recompute the stage and the interpolated PWM target.
  4. Ramp toward the target: up by 10 per second, down by 6 per second.
  5. Write the ramp value to pwm1; remember it as the last written value.
  6. Report edge, junction, mem, and PWM state every PERIOD_REPORT seconds.

The last written PWM value is the source of truth. The pwm1 read-back is a sanity check only.

Failure policy

Defined by the research spike; see documents/07-failure-behavior.md.

The service never drops fan control silently.

Reporting

One line per report, written to standard output (journald):

edge:NN C junction:NN C mem:NN C pwm_have:NN last:NN next:NN want:NN

Units in degrees Celsius and PWM 0-255. Rate: every PERIOD_REPORT seconds when stable, on every change otherwise.

systemd unit

Unit name: amd-mi25-fanctl.service.

Implementation structure

Test plan

Research-spike outcomes

The research spike is complete; see documents/07-failure-behavior.md.