..

Manual fan speed control for iMacs running FreeBSD

Let me preface this, that I am in no way a developer. I am a home DIY enthusiast with an old and broken iMac that fell on its face when I attempted to change the hard drive to an SSD. The screen broke. Once the shame and self pity went away, I decided I can make a literally headless server. I already have a FreeBSD server in another location and absolutely love it, but a lot of software is linux-first, so I also tried, “linux first”. And it did not even boot.

Multiple fruitless attempts later, I decided to try FreeBSD after all. And you know what, to my surprise, it works fine! No wifi out of the box, and the fans were spinning at max speed, but those are peanuts, or so I thought.

Since the fan was blasting in my ear, I wanted to start with that. And I quickly found that the issue is universal in all operating systems, since it was caused by me replacing the harddrive. Apple drives have internal temperature sensors and changing the drive makes the system unable to read the temperature, so out of precaution, it just blasts at max speed. Interestingly OWC sells some kind of aftermarket temperature sensor for 40US dollars. Well, but there must be another way!

Like I said, I am not a developer and I am only using FreeBSD for a year or so. Internet searches yielded nothing, so I asked Claude. Yes, yes, click away quickly, the heretic used AI. I did ask it for advice on where to look, what to poke.

I started with generic suggestions to look for userland tools, possibly existing utilities, firmware upgrades and such. Nothing worked.

Then my artificial friend suggested to load the asmc kernel driver. This driver communicates with the Apple SMC and exposes sensor info via the sysctl command

Doing that via kldload resulted in a clue … “asmc0: model not recognized”.

Unfortunately in my case, looking in the source of this driver, located at “/sys/dev/asmc/asmc.c”, reveals that it only contains references to MacBooks and Mac Pros, no iMacs mentioned. The LLM suggested to just copy one of the other entries and rename it to my model. This worked and the driver did load, but the fan kept blasting.

Uncommenting the protected debug output and recompiling the driver with a new dumpkeys sysctl that Claude wrote, revealed the other available “SMC keys”:

FS!   → fan mode
F0Tg  → fan target speed
F0Ac  → fan actual speed
DT0A  → disk temperature

The key discovery was that DT0A reports 00 00 and the SMC interprets this as thermal failure and turns the fans to maximum. The best approach would be to write some temperature to DT0A, so the system can adjust fan speeds as needed. Problem was that forcing this to another value was not possible, since it’s a read only key.

So a less desirable method was chosen, blindly forcing the fan to a lower speed. This is slightly problematic, since if the system would be heavily loaded, fans would just keep working at the set speed. So for long term usage I need to make a script of some sort that monitors the CPU temperature and adjusts the fan speeds as needed.

Claude then wrote a new function that lets me issue a command to set fan into manual mode and then set a new fan speed, I added it to asmc.c (in addition to the previous changes), compiled it and loaded it via kldload.

And it worked!

I will post the whole working solution below, but of course, this is a very specific hack. It works for my specific model, in this specific FreeBSD 15, and only because I replaced the hard drive.

Yes, I know, using LLMs is frowned upon by many. In this case, though, It was guiding me and making suggestions, where to poke, what to check, which files to inspect. I did not give it access to this system at all. It was a helper to find the real cause. Without the hand holding of Claude, I would have never been able to resolve this, so yes, to me, this is magical, this is awesome and this is absolutely the future.

So for any future adventurer, here is the full fix:

============================================================
FreeBSD asmc driver - iMac17,1 Fan Fix
============================================================

PROBLEM:
  iMac17,1 with SSD replacing original HDD. The SMC reads disk
  temp sensor key DT0A as 00 00 (no disk present), interprets
  this as a thermal emergency, and runs the fan at max (~3600 RPM).
  Writing fake temps to DT0A does NOT work - the iMac17,1 SMC
  firmware treats DT0A as read-only.

SOLUTION:
  Put fan into manual mode via FS! key, then set target RPM via
  F0Tg key. This bypasses the SMC thermal algorithm entirely.

TRADEOFF:
  Fan runs at a fixed speed. No automatic spin-up under load.
  At 1500 RPM and light desktop use, CPU stays around 29-31C.
  If you plan to run heavy workloads, consider a higher RPM or
  writing a daemon that reads CPU temp and adjusts fan speed.

============================================================
PATCH (against upstream sys/dev/asmc/asmc.c)
============================================================

1) ADD iMac17,1 MODEL ENTRY
   In the asmc_models[] array, add before the sentinel { NULL, NULL }:

   ---------------------------------------------------------------
   {
     "iMac17,1", "Apple SMC iMac17,1",
     ASMC_SMS_FUNCS_DISABLED,
     ASMC_FAN_FUNCS2,
     ASMC_LIGHT_FUNCS_DISABLED,
     ASMC_TEMPS_FUNCS_DISABLED
   },
   ---------------------------------------------------------------

   Notes:
   - ASMC_FAN_FUNCS2 (no safespeed, which this model lacks)
   - SMS and light disabled (iMac has neither)
   - Temps disabled (empty arrays) since we don't know all the
     valid temp key names for this model yet. CPU temp is still
     available via sysctl dev.cpu.0.temperature from coretemp(4).


2) ADD FORWARD DECLARATION
   Near the other static function declarations at the top, add:

   ---------------------------------------------------------------
   static int  asmc_sysctl_fanoverride(SYSCTL_HANDLER_ARGS);
   ---------------------------------------------------------------


3) ADD FANOVERRIDE SYSCTL REGISTRATION
   In asmc_attach(), after the existing SYSCTL_ADD_PROC calls
   (or add a new block), register the fanoverride sysctl:

   ---------------------------------------------------------------
   SYSCTL_ADD_PROC(sysctlctx,
       SYSCTL_CHILDREN(sysctlnode),
       OID_AUTO, "fanoverride",
       CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
       dev, 0, asmc_sysctl_fanoverride, "I",
       "Set fan 0 to manual mode at 1200 RPM "
       "(write 1 to enable, 0 to disable)");
   ---------------------------------------------------------------


4) ADD FANOVERRIDE FUNCTION
   Add this function (e.g. near the other sysctl handlers):

   ---------------------------------------------------------------
   static int
   asmc_sysctl_fanoverride(SYSCTL_HANDLER_ARGS)
   {
       device_t dev = (device_t)arg1;
       int error, v = 0;
       uint8_t buf[2];

       error = sysctl_handle_int(oidp, &v, 0, req);
       if (error || req->newptr == NULL)
           return (error);

       if (v == 1) {
           /* Set FS! to manual mode for fan 0 */
           buf[0] = 0x00;
           buf[1] = 0x01;      /* bit 0 = fan 0 manual */
           asmc_key_write(dev, "FS! ", buf, 2);

           /* Set F0Tg to 1200 RPM in fpe2 format */
           /* fpe2: RPM * 4, then big-endian 16-bit */
           /* 1200 * 4 = 4800 = 0x12c0 */
           buf[0] = 0x12;
           buf[1] = 0xc0;
           asmc_key_write(dev, "F0Tg", buf, 2);
       } else if (v == 0) {
           /* Set FS! back to auto mode */
           buf[0] = 0x00;
           buf[1] = 0x00;
           asmc_key_write(dev, "FS! ", buf, 2);
       }

       return (0);
   }
   ---------------------------------------------------------------


============================================================
SMC KEY REFERENCE (for this hack)
============================================================

  FS!  (ui16, len 2)  - Fan manual mode bitmask
       00 00 = all fans auto (SMC controls)
       00 01 = fan 0 manual (we control via F0Tg)

  F0Tg (fpe2, len 2)  - Fan 0 target speed
       fpe2 format: value = RPM * 4, stored big-endian
       Examples:
         1200 RPM = 4800  = 0x12c0
         1500 RPM = 6000  = 0x1770
         1800 RPM = 7200  = 0x1c20
         2400 RPM = 9600  = 0x2580

  F0Ac (fpe2, len 2)  - Fan 0 actual speed (read-only)
  F0Mn (fpe2, len 2)  - Fan 0 minimum speed
  F0Mx (fpe2, len 2)  - Fan 0 maximum speed

  DT0A (sp78, len 2)  - Disk temperature sensor A
       READ-ONLY on iMac17,1 firmware. Writes are silently
       ignored or immediately overwritten. Do not bother.

  TC0P (sp78, len 2)  - CPU proximity temperature
       sp78 format: signed 7.8 fixed point
       Useful if you ever write a temp-reactive fan daemon.


============================================================
BUILD & INSTALL
============================================================

  cd /usr/src/sys/modules/asmc
  make clean && make
  kldunload asmc 2>/dev/null
  kldload /usr/obj/usr/src/amd64.amd64/sys/modules/asmc/asmc.ko


============================================================
BOOT CONFIGURATION
============================================================

1) Auto-load the module - add to /boot/loader.conf:

     asmc_load="YES"

2) Enable manual fan mode - add to /etc/sysctl.conf:

     dev.asmc.0.fanoverride=1

3) Set desired RPM on boot - create /etc/rc.local:

     #!/bin/sh
     # Wait for asmc to be ready, then set fan speed
     sleep 5
     sysctl dev.asmc.0.fan.0.targetspeed=1500

     (and chmod +x /etc/rc.local)


============================================================
RUNTIME USAGE
============================================================

  # Enable manual mode (sets 1200 RPM initially)
  sysctl dev.asmc.0.fanoverride=1

  # Adjust RPM as needed
  sysctl dev.asmc.0.fan.0.targetspeed=1500

  # Check actual fan speed
  sysctl dev.asmc.0.fan.0.speed

  # Check CPU temp
  sysctl dev.cpu.0.temperature

  # Return to auto mode (fan will go back to 3600 RPM!)
  sysctl dev.asmc.0.fanoverride=0


============================================================
TESTED ON
============================================================

  Hardware:  iMac17,1 (Late 2015 Retina 5K) with SSD
  OS:        FreeBSD 15-CURRENT
  CPU temp:  29-31C at 1500 RPM, light desktop use
  Date:      March, 2026