Add RadeonSMU module - #110
Conversation
SMU mailbox access for AMD Radeon (RDNA) GPUs, as a narrower replacement for the WinRing0/InpOut32 physical-MMIO pattern that AMD GPU tuning software currently relies on. Rather than exposing a physical read/write primitive, the module owns the SMN index/data sequence itself and allowlists the reachable SMN addresses: the 128-register MP1 C2PMSG file at 0x03B10900..0x03B10AFF, dword-aligned. That window is the SMU message mailbox, so every present and future PPSMC message works without gating on message id - the same approach as RyzenSMU. VRAM is read-only and only for one fixed-size read of the SmuMetrics_t buffer the AMD driver itself programmed; the address is recovered by the caller from C2PMSG_80/81 and still bounds-checked here rather than trusted. The module finds the GPU itself by PCI scan (largest VRAM aperture, so an AMD iGPU cannot shadow a discrete card) and validates vendor, PCI class and BAR type bits before trusting anything. It refuses to load with STATUS_NOT_SUPPORTED when no supported GPU is present. The range check avoids signed overflow by never adding to the address, and both probed BAR sizes are sanity-capped. Tested on an RX 9060 XT (Navi 44, PMFW 0.102.70.0): GetSmuVersion through the allowlisted ioctls returns the same version the AMD driver reports, the metrics read returns a live struct, and SMN 0x0, either side of the window, an unaligned address and an out-of-window write are all refused. Re-probed correctly with ReBAR on (16 GiB aperture) and off (256 MB).
|
I will add RDNA2 and 3 support based on the Linux implementation and then test it on
against HWiNFO as reference. |
|
RDNA3 support is now validated. I ported the ABI 2 implementation, including fixed-size SMU13.0.0/13.0.10 and SMU13.0.7 metrics reads, in 4975b24. I cannot push to this PR's head fork, so please cherry-pick that commit. |
|
@DevTechProfile Thank you for digging into this! I don't have anything but navi 44 so RDNA2/3 is not something I can try alone. Rather than cherry picking could you open a PR against that branch directly? https://github.com/miklebel/PawnIO.Modules/compare/radeonsmu...DevTechProfile:CX.PawnIO.Modules:radeonsmu-rdna3?expand=1 You kept A few things:
|
|
Once the RDNA2 support is validated, I will open a PR and look into the points you have mentioned. Just to be safe, I will also test the module with my RX 9070 XT. We also need a direct comparison against HWiNFO! |
|
The requested follow-up PR is now available at miklebel#1. It contains only |
|
Module consumer code: CXWorld/CapFrameX@cbdb98b |
Take the BAR0 size from the Resizable BAR capability instead of the write-all-ones probe, and drop the BAR5 probe entirely (the size was never consulted). Discovery no longer writes to the device. Reject unassigned BARs: a zero base passed the type checks and aimed every later bounds check at low system RAM. Require BAR5 to be a 32-bit memory BAR, since 0x28 is the Cardbus CIS Pointer and a 64-bit encoding at 0x24 cannot be BAR5. Bind only Navi 4x. On an AMD APU the allowlisted C2PMSG window is the CPU's SMU mailbox, so an unrestricted gate would hand an iGPU's caller the processor's SMU under a name that promises a graphics one. Check the BAR0-high and metrics-copy read statuses, require the PCI COMMAND memory-decode bit, and document what the mailbox can still be asked to do (C2PMSG_80/81 DMA) and the PCIE_INDEX2/DATA2 race with the display driver. Tested on RX 9060 XT (Navi 44): ReBAR reports the same 16 GB the old probe did; SMU mailbox live; out-of-window SMN denied.
|
Pushed a revision on top of the original submission. Summary of what changed:
ioctl ABI is unchanged. Tested on an RX 9060 XT: the ReBAR read returns the same 16 GB aperture the old probe measured, the mailbox is live, and out-of-window SMN addresses are denied. |
|
@DevTechProfile - credit where it's due: the revision I just pushed exists because of your PR. Reviewing your discovery rewrite is what made me notice the BAR probe was writing to a live GPU and that an unassigned BAR sailed through with a zero base. I'd have shipped both. Your RDNA2/3 validation and the HWiNFO comparisons are also more coverage than this module has ever had, and CapFrameX using it is exactly the kind of consumer it was written for. I've asked you to take the RDNA2/3 telemetry work to upstream directly once this lands rather than through my branch, and I want to be clear that's about review order, not about the work. I don't have the hardware to stand behind it, and you shouldn't need me in the middle. I'll review the rebase before you open it if that helps, and I'm happy to be pinged on it here. |
|
I think RDNA2 and RDNA3 support should be included before #110 is merged. The implementations have already been I agree that the branch should be rebased onto your revised discovery code rather than merged as-is. I can also My preference would therefore be to keep #110 open until the tested RDNA2/RDNA3 paths are integrated, instead of |







RadeonSMU: SMU mailbox access for AMD Radeon (RDNA) GPUs
Replaces WinRing0/InpOut32-style raw physical MMIO in AMD GPU tuning
software. Rather than exposing a physical read/write primitive, the module
owns the SMN index/data sequence itself and allowlists the SMN addresses a
caller may reach, so the exposed surface is the MP1 message mailbox and
nothing else.
Why this rather than a first-party driver
There is no first-party interface for this. AMD's Windows display driver
exposes Overdrive through D3DKMT escapes, but not the SMU message mailbox,
and tools that need the mailbox today ship WinRing0 or InpOut32 — i.e. an
unrestricted physical read/write primitive available to any process that can
open the device. This module is a strictly narrower replacement for that
specific pattern, which the contribution guidelines list as preferred.
Surface
ioctl_read_smn[smn]→[value]ioctl_write_smn[smn, value]→[status]ioctl_read_metrics[pa]→ 65 dwordsioctl_get_bounds[_]→[ready, reg_bar, reg_size, vram_bar, vram_size]All four are
DEFINE_IOCTL_SIZED— no caller-sized arrays.SMN allowlist.
0x03B10900..0x03B10AFF— the 128-register MP1 C2PMSGfile, dword-aligned. That window is the SMU message mailbox: MSG
(C2PMSG_66), PARAM (C2PMSG_82), RESP (C2PMSG_90), and the addr_hi/lo pair
(C2PMSG_80/81) that PMFW populates for
TransferTableSmu2DramWithAddr.Every other SMN address is refused.
No message-id gating. Every PPSMC message is a write of an id to the same
MSG register, so allowlisting the registers rather than the messages keeps
present and future firmware messages working without widening the surface.
This mirrors
RyzenSMU, which likewise bounds SMN ranges and accepts anymessage id.
VRAM is read-only, and only for one buffer.
ioctl_read_metricsreadsthe
SmuMetrics_tDMA buffer that the AMD display driver itself allocatedand programmed; the module neither allocates it nor writes to it. The caller
recovers its address from C2PMSG_80/81, and the module still bounds-checks
that address against the probed BAR0 aperture rather than trusting it. There
is no write path to the framebuffer at all.
Device discovery
The module finds the AMD display device itself by PCI scan; the caller never
supplies a bus/device or a physical address for register access. Selection is
by largest VRAM aperture, not enumeration order, so an AMD iGPU that
enumerates first (e.g. Raphael, 256 MB carved from system RAM) cannot shadow
a discrete card.
ioctl_get_boundslets a caller confirm which device waschosen and observe the aperture across ReBAR states.
Compatibility checks before anything is trusted: vendor
0x1002, PCI class03:00(display/VGA), BAR0 must be a 64-bit memory BAR before0x14istreated as its high half, BAR5 must be a memory BAR, and the register
aperture must be large enough to contain PCIE_INDEX2/DATA2. On any failure —
non-x64, no AMD VGA device, an unreadable or implausible BAR —
main()returns
STATUS_NOT_SUPPORTEDand the module does not load at all, so acaller finds out at load time rather than through a run of
STATUS_ACCESS_DENIED.Bounds-check notes
-C64, so the naivepa + len <= base + sizecan wrap for apanear 2^63 and wrongly returntrue.
in_window()never adds topa: it checkspa >= base(which alsorejects a negative
pa), derives the offset by subtraction, and compareslenagainst the remaining window, which cannot overflow.REG_SIZE_MAX(16 MB) for BAR5with a fallback,
VRAM_SIZE_MAX(64 GB) for BAR0. An implausible read-backskips the device rather than widening a window.
sequence. Memory decode is not disabled during it — PCI config access
is PASSIVE_LEVEL and cannot be IRQL-protected — and each BAR is restored
immediately. Happy to change this if you would rather it were handled
differently.
Scope, stated plainly
The register BAR contains the SMN index/data pair, which is an aperture onto
the GPU's internal SMN bus. Bounding SMN addresses to the C2PMSG file is what
confines this module to the mailbox; without that allowlist, BAR5 access
would reach the whole SMN space. That is why the allowlist, not the BAR
bound, is the real boundary here.
Reference
amdgpu:drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c— mailbox protocol(poll RESP, clear RESP, write PARAM, write MSG, poll RESP).
amdgpu_device.c— PCIE_INDEX2/PCIE_DATA2 indirect SMN access.smu_v14_0.c/smu_v13_0.c—mmMP1_SMN_C2PMSG_66/82/90numbering.smu14_driver_if_v14_0.h— Navi 4xSmuMetrics_t(260 bytes = 65 dwords).Testing
Tested on an AMD RX 9060 XT (Navi 44, PCI
1002:7590, PMFW 0.102.70.0),with an NVIDIA card also present in the machine, under a locally built
PAWNIO_UNRESTRICTEDdriver (2.2.0 source) so the unsigned module could beloaded. Compiles with no warnings under the exact CI invocation
(
pawncc RadeonSMU.p -iinclude -C64 -;+ -(+ -p), against this repository'sown
include/.Full run against the submitted source (
0x80070005=ERROR_ACCESS_DENIED):The SMU version returned through the allowlisted ioctls matches what the AMD
driver reports, so the mailbox protocol works end to end inside the
allowlist. The
0x7FFFFFFFFFFFFFF0overflow probe is likewise refused; witha naive
pa + len <= base + sizecheck it wrapped and was allowed.Aperture tracking was exercised in both ReBAR states. With ReBAR off the
same code re-probes
vram_bar=0xF820000000 / 0x10000000(256 MB) and themetrics buffer moves with it to
0x800EFC6000 -> 0xF82EFC6000, ~16 MB belowthe top of the smaller window; with ReBAR on, the values above. Nothing
is cached across loads — the PCI scan and both BAR size probes re-run every
time. (The ReBAR-off pass was run against the immediately preceding
revision, which differs only in
main()returningSTATUS_NOT_SUPPORTEDwhen no GPU is found; the discovery and bounds code is identical.)
Consumer
Adrenalift — a closed-source AMD
RDNA tuning utility. Its current public release (0.8.0) uses InpOut32,
i.e. exactly the unrestricted physical read/write primitive this module is
meant to replace. An internal build has already been ported to the four
ioctls above and has no other hardware path: the physical-MMIO surface was
removed from the host rather than left as a fallback.
That build cannot be released until the module loads under the stock signed
driver, so this is not a speculative submission — it is the blocker on
dropping InpOut32 from a shipping product.