Skip to content

F0Tg write verification false positive: SMC quantizes target RPM, exact read-back comparison fails every policy cycle #11

@GGGODLIN

Description

@GGGODLIN

Summary

When the daemon applies a fan curve target, the post-write read-back verification of F0Tg does an exact byte comparison. The SMC quantizes the written float (low mantissa bits are zeroed), so whenever the interpolated target has precision beyond what the SMC stores, verification fails — and Fan policy evaluation failed is logged on every policy cycle (roughly once per second under my curve).

The write actually lands: fan actual RPM tracks the intended target fine. This is a false positive that floods the unified log with Error-level entries.

Environment

  • smctl 0.1.8 (Homebrew, built from source)
  • MacBook Pro (M5 Pro), macOS 26.4.1 (25E253)
  • Custom curve profile applied via smctl fan profile <name> (daemon mode manual)

Log sample

smctld: [one.leaper.smctl:daemon] Fan policy evaluation failed: SMC write verification failed for F0Tg: expected 00566f45, read back 00506f45
smctld: [one.leaper.smctl:daemon] Fan policy evaluation failed: SMC write verification failed for F0Tg: expected 00147545, read back 00107545
smctld: [one.leaper.smctl:daemon] Fan policy evaluation failed: SMC write verification failed for F0Tg: expected c0f58545, read back 00f08545

Decoding the bytes (little-endian IEEE-754 float)

written value (RPM) read back value (RPM) delta
00 56 6f 45 → 0x456F5600 3829.5 00 50 6f 45 → 0x456F5000 3829.125 0.375
00 14 75 45 → 0x45751400 3921.25 00 10 75 45 → 0x45751000 3921.0 0.25
c0 f5 85 45 → 0x4585F5C0 4286.72 00 f0 85 45 → 0x4585F000 4286.0 0.72

In every case the read-back is the written value with the low ~12 mantissa bits zeroed — the SMC stores the target at reduced precision. The delta is always < 1 RPM, far below anything a fan can physically resolve.

Observed impact

  • Unified log gets one Error entry per policy cycle while a curve is active (~17 entries in 2 minutes on my machine).
  • Harder to spot real failures: during a genuine incident (safety-ceiling latch), these false positives were interleaved with the real error and initially looked like the cause.
  • Fan control itself works — smctl fan status shows actual converging to target.

Suggested fix

Compare with a tolerance instead of exact bytes, e.g. accept when abs(readBack - written) < 1.0 RPM — or quantize the written value the same way before comparing (mask the low mantissa bits). Alternatively, downgrade this specific mismatch to debug-level once the read-back is within tolerance.

Happy to test a fix on this machine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions