Skip to content

spi: apple-qspi: runtime suspend T8140 between boot NOR accesses - #666

Closed
RyanTheTide wants to merge 8 commits into
aurora-wipfrom
fu-j700-qspi-runtime-pm
Closed

RyanTheTide wants to merge 8 commits into
aurora-wipfrom
fu-j700-qspi-runtime-pm

Conversation

@RyanTheTide

Copy link
Copy Markdown
Contributor

The T8140 QSPI controller has no runtime PM, so it holds its power domain between boot-NOR accesses. This enables runtime suspend on T8140 while keeping the verified NVRAM write sequence and multi-line reads intact.

Dependency order: #626 → #662 → this PR. The branch merges the original #662 ancestry (including #626) onto aurora-wip 8348f8244b20; neither prerequisite branch is changed. The new functional change is the final commit, e96eb52c5dbe. A merge-tree check of that order is conflict-free and produces exactly the tested tree. The dependency commits remain visible until their PRs merge.

  • Use a separate SoC match-data flag for runtime PM; other controllers keep their existing system-sleep behavior.
  • Hold spi-mem's runtime reference throughout each operation, including the entire verified JEDEC/status/WREN/program-or-erase/poll/WRDI/readback sequence. Keep the probe reference through registration and the final MMIO.
  • Use a fixed 100 ms autosuspend delay to group 4 KiB read chunks and userspace NVRAM access bursts. Mark last-busy after the complete operation because spi-mem releases its reference with plain pm_runtime_put(). Long mutation polls remain protected by the reference, regardless of the delay.
  • Drain and disable the IRQ before genpd gates the domain. Resume through the existing reset/configuration helper before enabling the IRQ. It restores fixed timing, FIFO thresholds and interrupt masks/status; mode, clock divider, descriptors and payload are programmed per transfer. A reset error keeps the IRQ disabled and prevents the access.
  • Use the runtime force helpers for system sleep, including when already runtime-idle, with SPI queue rollback/reopen preserved.

History review: the initial PIO driver (f795597317be) had no PM callbacks; no deliberate runtime-PM prohibition was found. 1e76f292c061 added configuration restoration after power loss in system sleep, and 0d8b9f1db4ac fixed the queue remaining suspended after reset failure. The new callbacks reuse those mechanisms. In the current T8140 DT, QSPI at 0x385110000 uses ps_qspi (0x350) below ps_spi_p (0x2f0); both are enabled without always-on or firmware-owned flags. No DT flag change is needed, and another consumer may keep the parent on.

Validation:

  • Arm64 LLVM W=1 QSPI driver and policy-test objects, defconfig + asahi.config with PM/sleep: no warnings or errors. Driver also builds cleanly with PM but no system sleep, and with PM disabled in a minimal Apple config. C-only builds used CONFIG_RUST=n: the initial Rust-enabled preparation failed in unrelated generated EFI binding alignment assertions.
  • checkpatch --strict: 0 errors, 0 warnings, 0 checks. git diff --check clean.
  • UML apple-qspi-nvram-policy: 9/9 pass. W=1 used CONFIG_WERROR=n for warnings outside QSPI (including proc_sysctl and Synaptics); this is a test-build setting only.
  • Host ASan/UBSan harness passes, including 140 board/controller/aperture combinations, J700 verified writes, Macronix EQIO guards, runtime-PM idle-stamp cases, and 13 changed-guard controls. The PM cases check reads, denied writes, full verified programs, transfer errors and latched write faults under a modeled outer spi-mem reference.
  • Additional source-extracted callbacks with fake registers/IRQ boundaries pass 100 power-loss cycles, reset timeout and queue rollback/reopen cases, plus three changed-guard controls. These model tests do not establish physical gating or timing.

Not hardware qualified. No full kernel Image build or measured power saving is claimed. Lead checks before merging:

  1. On J700, compare the prerequisite baseline f0f4ded041f8 with this commit using a one-shot kernel and a known-good boot fallback. Confirm JEDEC ID, partition bounds and default read-only policy; retain raw NVRAM snapshots privately.
  2. Repeat boot-NOR and asahi-nvram reads back-to-back and after >250 ms idle. Briefly opt in through the existing read_only parameter to write/read/delete a fresh benign test variable, restoring read-only after each mutation. Check latency, readback, fault latch and dmesg. Do not retry a latched mutation.
  3. Run asahi-bless -d <snapshot-copy> --next --set-boot <confirmed-target> -y only on a copy. Verify the byte diff, bit transitions and unchanged persistent selection.
  4. Capture genpd summaries before/after reads and after idle: require QSPI runtime-suspended and its qspi domain off. Explain remaining spi_p consumers. Repeat access checks after s2idle from idle and active states; require no MMIO fault, reset timeout, IRQ imbalance or stuck queue.
  5. Compare matched 10-minute, 1 Hz idle windows using macsmc power inputs and labels, including Total System Power and PPMC/PPSM/PDCP where available. Keep conditions and observer overhead equal; report per-key distributions and deltas without summing overlapping rails. Keep private NVRAM data out of results.

RyanTheTide and others added 8 commits October 30, 2026 18:07
asahi-bless cannot select a one-time boot volume on the MacBook Neo
(J700). The T8140 path admitted programs and erases inside the nvram
partition with no runtime opt-in and no readback, while the J613 and
J713 go through the read-only, opt-in, verified policy. Writes still
never reached the flash, because the partition was read-only in MTD.

Put the J700 on the same policy. The controller starts read-only and
root opts in at runtime through the read_only parameter. A write
needs the J700 board and the controller at 0x385110000. Programs are
single-wire, three-byte, within one 256-byte page; erases are aligned
4 KiB sectors; both must lie inside the nvram partition. Every
mutation checks the JEDEC ID and status first, is verified by
readback, and a fault latches the driver read-only.

The J613/J713 partition is pinned at 0x700000. The J700's comes from
its device tree (0x24000, size 0x1d0000), so the range check now takes
the partition bounds, and a DT window must fit in three-byte
addressing. The J700 flash is a W25Q16JV (JEDEC ef6515). iBoot leaves
BP0 set, which protects the top 64 KiB, so the J700 tolerates the
block-protect bits as found: the driver never changes them, the flash
ignores writes in that range, and readback then latches read-only.
CMP, suspend, WPS, BUSY and WEL are still refused.

T8140 data reads keep their multi-line pass-through. KUnit and the
host harness now cover the shared policy at the J700 bounds, the J700
board gate and the block-protect tolerance.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The J700 nvram partition starts at 0x24000 on its boot NOR. SPI-NOR
picks the largest uniform erase size unless MTD_SPI_NOR_USE_4K_SECTORS
is set, so the flash reports 64 KiB erase blocks and MTD forces the
partition read-only because it does not start on an erase boundary.
asahi-bless then cannot set a boot volume.

There is no device tree or per-flash way to choose the erase size;
this option is the only switch, and it is the upstream default. The
Fedora base config turns it off. Set it again. The Apple NVRAM
partitions are managed in 4 KiB units, and the QSPI NVRAM policy on
the J613, J713 and J700 only admits 4 KiB erases anyway. Other SPI NOR
users only see smaller erase blocks.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each board that may write its NVRAM was qualified by its own
compatible check in apple_qspi_nvram_write_board(), and probe then
picked the partition bounds, JEDEC ID and block-protect allowance from
whether the controller was T8140. The J613/J713 partition was a pair
of constants, checked by a second device tree walk, while the J700 one
came from the device tree unchecked.

Put the qualified boards in one table: controller compatible, machine,
controller aperture, the nvram partition the device tree must
describe, the flash JEDEC ID and the block-protect bits its firmware
leaves set. Every controller now reads its nvram partition from the
device tree with the same walk, and a board is a write candidate only
when that partition is exactly the one in its table entry. The T8140
multi-line read pass-through comes from the compatible match data.

The J613 and J713 keep the 0x700000 partition and the W25Q64 ID, and
the J700 keeps 0x24000, the W25Q16 ID and its BP tolerance, now with
its partition pinned as well. The KUnit 0x700000 case covers the
partition edges against its neighbours, and the host harness reads the
table and gains controller, aperture and partition controls.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
The M4 Pro (T6041) boot NOR sits behind the same QSPIMC controller as
the M3, M4 and A18 Pro parts, at 0x429118000. The J616s ADT places its
nvram partition (anvram, "nvram,nor") at 0x700000 with a size of 1 MiB,
the J613 and J713 layout, next to iboot at 0 and syscfg at 0x680000.
Without a T6041 match the driver does not bind, so asahi-bless cannot
select a boot volume from Linux.

Match apple,t6041-qspi with single-line transfers, as on T8122 and
T8132. Qualify the J616s for the verified NVRAM policy at its
controller aperture and that partition, with the W25Q64 ID the other
0x700000 boards use and no block-protect bits tolerated. The flash ID
is checked before every program or erase, so a different part is
refused before any write enable reaches it.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Describe the J616s boot NOR on the QSPIMC controller with the nvram
partition from its ADT (/arm-io/qspi/spinor/anvram, 0x700000, 1 MiB),
so asahi-nvram and asahi-bless can find it. Use single-line transfers
at 10 MHz, as on the J713. Add the nvram and spi0 aliases the J700
also carries. The SoC node stays disabled for boards that do not
describe their flash.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
macOS reports the J616s boot NOR JEDEC ID as c22b27, a Macronix part,
not the Winbond W25Q64 the J613 and J713 carry. Pin that ID in the
J616s table entry.

The write policy and the flash_status attribute read status registers
with Winbond opcodes: 05h, 35h and 15h. On Macronix parts 35h is EQIO,
which switches the flash to QPI mode, and the status register keeps
four block-protect bits, BP[3:0] at bits 5:2, where Winbond has three
and TB. For a Macronix part, read only the status (05h) and
configuration (15h) registers, refuse 35h from SPI-NOR as well, and
check WIP, WEL and all four BP bits before a write. flash_status reads
the JEDEC ID first and sends 35h only to Winbond parts.

The J616s still admits only 3-byte page programs and 4 KiB sector
erases (20h) inside its nvram partition, with readback verification.
SPI-NOR has no table entry for c22b27; it binds through the generic
Macronix entry and takes geometry and erase types from SFDP.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Carry the original #626 and #662 ancestry onto aurora-wip so the
runtime-PM change composes with their eventual merges without replaying
copies of the same driver changes.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
The T8140 boot NOR controller keeps its genpd domain active between
accesses. Enable runtime PM only for this SoC, independently of the
board qualification and NVRAM write policy.

Let spi-mem acquire the runtime reference around exec_op. This covers
all private JEDEC/status reads, WREN, completion polling, WRDI and
readback in a verified mutation. Mark the controller busy after the
whole operation, since spi-mem releases its reference with a plain
pm_runtime_put(). Use a 100 ms autosuspend delay to group successive
4 KiB read chunks and userspace NVRAM access bursts without retaining
the domain for long idle periods.

Hold an initial reference through registration, NOR discovery and the
last probe MMIO, and balance it on failure. Drain and disable the IRQ
before genpd can gate the block. On resume, reuse the reset helper to
restore timing, FIFO thresholds and interrupt state before enabling
the IRQ; mode, divider and transfer descriptors are set per operation.
A failed reset leaves the IRQ disabled and prevents the memory access.

Use the force helpers for system sleep so an already suspended block
is not accessed. Keep the SPI queue rollback and reopen behavior, and
leave other SoCs on their existing system-sleep path.

Extend the host harness to check that read, refusal, verified program
and error paths update the idle stamp under the outer SPI PM reference.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
@RyanTheTide

Copy link
Copy Markdown
Contributor Author

Superseded by #675, which carries the same change with the house trailers and is merged into aurora-wip after the fleet12 hardware run.

🤖 Generated with Claude Code

@RyanTheTide
RyanTheTide deleted the fu-j700-qspi-runtime-pm branch October 31, 2026 05:53
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.

1 participant