Skip to content

Add AppleT2Smc module: Apple T2 SMC over MMIO - #71

Open
golirt1 wants to merge 6 commits into
namazso:mainfrom
golirt1:add-apple-t2-smc
Open

Add AppleT2Smc module: Apple T2 SMC over MMIO#71
golirt1 wants to merge 6 commits into
namazso:mainfrom
golirt1:add-apple-t2-smc

Conversation

@golirt1

@golirt1 golirt1 commented Jul 1, 2026

Copy link
Copy Markdown

Adds a module for the SMC on 2018-2020 Intel Macs with the Apple T2 chip.

On those machines the T2 intercepts the legacy SMC I/O-port protocol
(0x300/0x304), so the usual port handshake returns garbage under Windows. The T2
instead exposes the SMC through an MMIO window at physical 0xFE0B0000.

The module maps that window and performs the SMC request/response handshake in
kernel mode, exposing only SMC operations - never raw physical memory access:

  • ioctl_smc_read - READ (0x10), GET_KEY_BY_INDEX (0x12), GET_KEY_TYPE (0x13)
  • ioctl_smc_write - WRITE (0x11)

Writes are restricted by an allowlist to fan-control keys only (F<n>Tg,
F<n>Md, FS! ), so the module can't be used to poke arbitrary SMC state -
only fan speed and mode, which is its purpose.

Load-time gates

Nothing is written to the window until it has identified itself, in this order:

  1. non-x64 or non-Intel CPU -> STATUS_NOT_SUPPORTED, nothing is mapped at all
  2. map, then read the status register; 0xff -> unmap and decline, still
    without having written anything
  3. read LDKN (the SMC key-interface version) and require >= 2 - this is the
    first access that writes, and only runs once step 2 has passed
  4. anything else -> unmap and decline

Each gate logs its reason through debug_print, so an unsupported machine can
be told apart from a wrong base address or an SMC reporting an old LDKN.

Only 0x4006 is mapped (APPLESMC_IOMEM_MIN_SIZE), which is the last byte the
protocol needs, so no unrelated MMIO beyond the status register is ever mapped.

Reference

Register offsets, access widths (control registers are 32-bit writes;
status/length/error are byte reads; the data buffer is byte-addressed), the
completion-status bit (0x20), the exponential backoff, the 0xff status check
and the LDKN >= 2 gate all mirror the Linux T2 applesmc driver
(MCMrARM/mbp2018-etc, applesmc_t2_kmod.c). GET_KEY_TYPE's special MMIO
result layout (type@0, datalen@5, flags@6) is repacked into the conventional
[len, type, flags] form; the type code is read as a single dword to match the
driver's ioread32.

Known limitation

The Linux driver takes the MMIO base from the ACPI _CRS of device APP0001.
PawnIO has no ACPI access, so the base is a compile-time constant here, taken
from the machines it has been observed on. If a T2 model places the window
elsewhere, the gates above fail and the module declines to load rather than
touching the wrong device - but it would also mean no support on that model
until the address is known. I'd rather be explicit about this than hide it.

Notes

  • Compiles cleanly with the repo CI.
  • I'd most appreciate a review of the access widths and the write allowlist.
  • Does PawnIO serialize ioctls per module? The Linux driver holds a mutex around
    the register sequence and I don't see a locking primitive in the includes, so
    I'm not sure whether I need to handle that myself.

golirt1 added 2 commits June 27, 2026 13:29
On 2018-2020 Intel Macs the T2 chip intercepts the legacy SMC I/O-port protocol, so the SMC is only reachable through an MMIO window at 0xFE0B0000. This module maps that window and performs the SMC read/get-key/write handshake in kernel mode, exposing only SMC operations (no raw physical memory access). Writes are restricted by an allowlist to fan-control keys (F<n>Tg, F<n>Md, FS!). Register offsets, access widths and protocol follow the Linux T2 applesmc driver.
@golirt1

golirt1 commented Jul 25, 2026

Copy link
Copy Markdown
Author

Hi @namazso — friendly bump on this one, no rush 🙂

It's been a few weeks so I wanted to check whether there's anything you'd like changed before it could be considered for signing. Quick recap: the module only maps the T2 SMC MMIO window (0xFE0B0000) and exposes read / get-key / write, with writes restricted by an allowlist to fan-control keys so it can't poke arbitrary SMC state. CI builds green, and another T2 Mac owner has reviewed and tested it.

Happy to make any adjustments you'd want. Thanks for maintaining PawnIO!

The module mapped a hardcoded physical address and immediately wrote the
key/command registers to probe FNum, so on a machine where that window
belongs to another device it wrote there before finding out. Follow what
Apple's driver and the Linux port do instead:

- refuse to load on non-Intel CPUs
- read the status register first and bail on 0xff, before any write
- replace the FNum probe with the protocol's own LDKN >= 2 gate
- map 0x4006 (APPLESMC_IOMEM_MIN_SIZE) rather than a full 64K

Also read the GET_KEY_TYPE type code with a single dword read, matching
the driver's ioread32 instead of four byte reads, and document that the
base address is a constant here because PawnIO has no ACPI access.
@golirt1

golirt1 commented Aug 24, 2026

Copy link
Copy Markdown
Author

I've pushed a revision that addresses what I now think was the actual problem with this PR, and I should have caught it earlier.

The module mapped a hardcoded physical address and then immediately wrote the key/command registers to probe FNum. On a machine where that window belongs to something else, it wrote there before finding out — the header comment even claimed it never touched non-SMC memory, which wasn't true. DellSMM.p has exactly the right shape for this and I had missed it.

What it does now, following Apple's driver and the Linux port:

  • refuses to load on non-Intel CPUs
  • reads the status register first and bails on 0xff, before any write
  • replaces the invented FNum probe with the protocol's own LDKN >= 2 gate
  • maps 0x4006 (APPLESMC_IOMEM_MIN_SIZE) instead of a full 64K

I also switched the GET_KEY_TYPE type code to a single dword read, matching the driver's ioread32 rather than four byte reads.

One limitation I want to be upfront about: the Linux driver takes the MMIO base from the ACPI _CRS of device APP0001, and PawnIO has no ACPI access, so the base is a constant here. If a T2 model puts the window elsewhere, the checks above fail and the module declines to load rather than touching the wrong device. That is documented in the header now.

On the "not proper first party drivers" guideline: there is no first-party driver here. Apple's Boot Camp package installs no SMC driver on Windows — on my Mac the only AppleSMC service registered belongs to a third-party commercial utility, and system32\drivers contains no Apple SMC binary. Today the only ways to set fan speed on a T2 Mac under Windows are that closed-source utility, or raw MMIO through something WinRing0-shaped.

Two things I would like your read on:

  1. Does PawnIO serialize ioctls per module? The Linux driver holds a mutex around the register sequence, and I don't see a locking primitive in the includes, so I want to know whether I need to handle that myself.
  2. I have two T2 owners willing to test. Testing needs the unrestricted edition, which neither of them wants to enable — if you would be willing to sign a build for validation before merging, I can have their results posted here.

Happy to change anything else you'd like.

Each of the load-time gates now prints its own reason, so a tester who
captures debug output can tell an unsupported machine apart from a wrong
base address or an SMC that answered but reported an old LDKN. Also add
the trailing newline the other modules use.
@namazso

namazso commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Does PawnIO serialize ioctls per module?

Yes, but not per module identity, ie two different programs can load separate copies of your module. You should probably recommend using some global mutant to userspace if such serialization is required, and hope for the best.

I have two T2 owners willing to test. Testing needs the unrestricted edition, which neither of them wants to enable — if you would be willing to sign a build for validation before merging, I can have their results posted here.

No, since a faulty signed module cannot be undone.

Additionally, we now have DMI checks possible via registry, see ValveLeds.p. I'd hope that it makes it possible to verify Apple hardware during load.

I put this back into a draft until it's tested at least.

@namazso
namazso marked this pull request as draft August 30, 2026 00:36
namazso pointed out that registry-backed DMI checks are now available
(LedsValve.p). The MMIO base here is a compile-time constant, so this is
the check that keeps it from being touched on a machine where that
address belongs to something else: read SystemManufacturer from
\Registry\Machine\HARDWARE\DESCRIPTION\System\BIOS and refuse to load
unless it says Apple. It runs before anything is mapped, and it is a
read, so nothing is written on hardware this module has no business on.

The status-register and LDKN gates stay as the second and third checks:
Apple firmware alone does not mean the window is a T2 SMC.
@golirt1

golirt1 commented Sep 7, 2026

Copy link
Copy Markdown
Author

Tested on real T2 hardware, two machines, PawnIO 2.0.1 unrestricted:

The tested binary is [sig_len=0] + the .amx this branch's CI produced, so it's exactly the code here.

One bug surfaced during testing and it was in my app, not the module: T2 fan keys are flt and I was byte-reversing them. The module itself is unchanged since the last push.

Note for anyone else testing: the "unrestricted" option in PawnIO_setup 2.1.0/2.2.0 installs a driver that still enforces signatures (PawnIO.Setup#11) — 2.0.1 works.

Marking ready for review.

@golirt1
golirt1 marked this pull request as ready for review September 7, 2026 16:37
@Kinkaliyov

Copy link
Copy Markdown

hey @namazso , just wanted to tell smth in as one of the testers.

ive been using this build on my MacBookPro15,1 (Windows 10) for daily use with RPMac, and the module has been solid. all SMC sensor readings, fan curves, and manual setpoints work smoothly with zero issues or instability.

huge thanks to @golirt1 for getting the DMI check integrated. really hoping to see this signed and merged whenever you have time to review!

RPMac_wF3pwJywYZ.mp4

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.

4 participants