Skip to content

fan: multi-fan RPM with sysfs hwmon fallback for Battlemage/xe (Arc P… - #148

Open
thevisad wants to merge 1 commit into
intel:mainfrom
thevisad:xpum-fan-rpm
Open

fan: multi-fan RPM with sysfs hwmon fallback for Battlemage/xe (Arc P…#148
thevisad wants to merge 1 commit into
intel:mainfrom
thevisad:xpum-fan-rpm

Conversation

@thevisad

@thevisad thevisad commented Aug 2, 2026

Copy link
Copy Markdown

Description

xpu-smi stats reports Fan Speed (RPM): N/A on the Intel Arc Pro B70 (Battlemage, xe driver) even
though the fan is spinning and its RPM is published on the PCI device's hwmon node (fanN_input): Level
Zero sysman enumerates no fan handle on this device. This PR adds per-fan RPM reporting with a sysfs-hwmon
fallback for that case, a correct Level Zero error contract, genuine per-fan identity, and validated
discovery, while leaving the existing percent path byte-identical where Level Zero exposes fan handles.

What it does

  • RPM source is chosen by a pure, unit-tested helper decideFanRpmSource(enumResult, fanCount):
    enumeration failure → propagate; fanCount == 0 → sysfs; fanCount >= 1 → Level Zero. sysfs is
    never consulted when a fan handle exists.
  • Genuine multi-fan: getSpeedRpmById reads a specific L0 handle (propagates its error; accepts
    rpm >= 0); getAllSpeedsRpm iterates all L0 handles keyed by real fan id, and in the zero-handle
    case enumerates all fanN_input nodes (fan1_input→0, fan2_input→1, …). The stats collector
    keys samples by real fan id — no fan is dropped.
  • Zero RPM (a stopped fan) is a valid recorded reading, distinct from a failed read.
  • resolveSysfsHwmon sets zes_pci_properties_t.stype (required by the Level Zero API contract), scans
    all hwmon* nodes, and accepts a candidate only when a real RPM value parses (not merely that the
    file opens), so a malformed node cannot shadow a valid one.
  • readFanInputFromDir returns ZE_RESULT_ERROR_INVALID_NULL_POINTER for a null out-pointer;
    parseFanInputIndex uses std::from_chars and rejects overflow.
  • Pure helpers live in an internal xpum::hwmon utility (hwmon_fan_utils.{h,cpp}), not on the exported
    fan class. The HAL getters propagate Level Zero errors and never substitute a sysfs value for a real
    driver failure; the stats collector treats an unavailable RPM sample as absent (like other optional
    xpu-smi stats metrics), so it does not make xpu-smi exit with the Level Zero failure.

Testing

  • doctest: 18 cases / 83 assertions (decideFanRpmSource matrix incl. inconsistent
    fanCount>0 && handles==nullptr→internal error; RPM parse incl. 0, malformed, overflow; index parse
    • overflow boundary; readFanInputFromDir against a temp hwmon dir incl. null-ptr classification;
      percent-rendering unchanged vs " RPM" rendering). Full meson test: 11/11 suites.
  • Hardware (Arc Pro B70, xe, kernel 7.0.0-28-generic, Level Zero 1.27.0): xpu-smi stats reports
    Fan Speed (RPM) Fan 0: 1177 RPM, tracking raw fan1_input; Fan Speed (%) stays N/A; discovery
    selected the correct hwmon node.

Scope / limitations

  • Multi-fan is genuinely implemented (all L0 handles by id; all fanN_input in the fallback) but
    hardware-validated only on the single-fan B70; the multi-fan paths are exercised by unit tests, not on
    multi-fan silicon.
  • The Level-Zero-handle state-read-error branch (zesFanGetState failing on a present handle) is
    covered by the selection logic but not unit-mocked, because injecting that failure would require
    fabricating a device handle; documented in the code.
  • Driver note: the B70 fan tachometer returns EAGAIN on a back-to-back read, so hwmon resolution is
    validated once and cached rather than re-read every sample.
  • No unguarded POSIX-only header remains (std::filesystem). Windows was not build-tested. The doctest
    test targets require a doctest package in CI.

Fixes:
Relates-To:

Type of change

  • Bug fix
  • New feature
  • API / ABI change
  • Refactor / cleanup
  • Documentation
  • Tests
  • Dependency update
  • CI / build
  • Other

Affected components

  • hal — Hardware Abstraction Layer
  • ial — Interface / Application Layer
  • oal — OS Abstraction Layer
  • xpumd — XPU Manager Daemon
  • cli / smi
  • ci / build system

Checklist

  • Commit messages follow the project guidelines
  • Each commit includes Signed-off-by (DCO)
  • New public APIs or CLI options are documented — the new Fan Speed (RPM) row / fan.speed_rpm
    metric is described here; no new CLI option
  • No unrelated changes included
  • Squash "fixup" commits after reviews — n/a (single commit)

@savery42

savery42 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Related to #146

@savery42

savery42 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Hi @thevisad thanks for the contributions!

No issues with Windows build or runtime for this PR as well as #149 on Windows Server {2022,2025} and w/ Visual Studio {17(2022),18(2026)}. I really need to set up build actions... :).

I'll take a proper look today and should be able to grab a multi-tile system for verification there as well.

On the Arc Pro B70 (Battlemage, xe) Level Zero sysman enumerates no fan handle,
so xpu-smi reports fan speed as N/A even though RPM is published on the
world-readable PCI hwmon node (fanN_input). Add per-fan RPM reporting with a
validated sysfs fallback.

- RPM source is chosen by a pure decideFanRpmSource helper: use Level Zero when
  fan handles are present; fall back to the hwmon node when Level Zero enumerates
  no fan or denies access to an unprivileged process; propagate any other
  enumeration failure so genuine errors are not masked.
- Genuine per-fan identity: getSpeedRpmById and getAllSpeedsRpm iterate every
  Level Zero handle by real fan id, and the sysfs fallback enumerates every
  fanN_input node; the collector keys samples by real fan id.
- Zero RPM (stopped fan) is a valid reading; discovery accepts a hwmon node only
  when a real RPM value parses; null-pointer and index-overflow are handled.
- Pure logic lives in an internal xpum::hwmon utility, off the exported class.
  The HAL getters propagate Level Zero errors; the stats collector treats an
  unavailable sample as absent, like other optional metrics.
- doctest coverage for the selection matrix, parsing, identity and rendering;
  portable std::filesystem, no POSIX-only headers. The percent path is unchanged.

Validated on Arc Pro B70 (xe, kernel 7.0.0-28-generic, Level Zero 1.27.0):
xpu-smi stats reports Fan Speed (RPM) tracking the raw hwmon fan1_input.

Signed-off-by: thevisad <373694+thevisad@users.noreply.github.com>
@thevisad

thevisad commented Aug 3, 2026

Copy link
Copy Markdown
Author

@savery42 thanks! One small heads-up from a self-check on my end, since it slipped past both the Linux build and your Windows build:

I compile-checked the two internal util TUs standalone under MSVC with /WX, and it surfaced a C4273 (inconsistent dll linkage) on this PR — the internal xpum::hwmon fan helpers in hwmon_fan_utils.h were annotated LIBXPUM_API, i.e. exported from the DLL. That's inconsistent with the temperature helpers (which are plain) and with the goal of keeping them off the exported surface. The full DLL build resolves the macro consistently so it stays green, which is why your Windows build was clean, and it's benign on Linux, so it hid on both sides; only a standalone /WX compile flagged it.

Fixed: dropped LIBXPUM_API from the five internal decls. meson test is still 11/11, and both util TUs now compile clean under MSVC /std:c++20 /WX (VS 2022). Pushed to the branch, head 4c089ee. #149 (temperature) was already clean — its helpers never carried the macro.

Appreciate you grabbing a multi-tile box for verification — thanks for taking the time on these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants