Skip to content

spi: apple-qspi: J616s NVRAM writes and boot NOR DT (asahi-bless on the M4 Pro) - #662

Closed
RyanTheTide wants to merge 6 commits into
aurora-wipfrom
qspi/j616s-nvram-write
Closed

RyanTheTide wants to merge 6 commits into
aurora-wipfrom
qspi/j616s-nvram-write

Conversation

@RyanTheTide

@RyanTheTide RyanTheTide commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

This lets asahi-bless --next pick macOS for one boot on the M4 Pro MacBook Pro (J616s), the way it works on the Neo.

It stacks on #626, which puts every QSPIMC board on the verified NVRAM write policy. Its two commits show here until it merges. The four new commits are the last four.

Changes

  • spi: apple-qspi: describe the qualified NVRAM boards as data. The write-qualified boards move into one table with the controller compatible, machine, controller aperture, the nvram partition the DT must describe, the flash JEDEC ID and the block-protect bits firmware leaves set. Every controller reads its partition from the DT with the same walk, and a board is a write candidate only when that partition matches its table entry exactly. The T8140 multi-line read pass-through now comes from the compatible match data. The J613/J713 pinned constants and the second DT walk are gone. J613, J713 and J700 keep the same bounds, ID and BP handling, and the J700 partition is now pinned too.
  • spi: apple-qspi: add T6041 and admit NVRAM writes on the J616s. apple,t6041-qspi binds with single-line transfers like T8122/T8132. The J616s qualifies at 0x429118000 with the 0x700000 / 1 MiB partition, with no BP bits tolerated.
  • arm64: dts: apple: t6040-j616s: enable the boot NOR and its nvram. &qspi gets flash@0 and nvram: partition@700000 from the J616s ADT (anvram, "nvram,nor"), single-line at 10 MHz like the J713, plus the nvram and spi0 aliases the J700 has.
  • spi: apple-qspi: use the J616s Macronix boot NOR ID and registers. macOS reports the J616s NOR as JEDEC c22b27 (Macronix), so the table entry pins that ID. On Macronix parts 35h is EQIO (enter QPI mode), not a status register read, and the status register has four BP bits (bits 5:2). For Macronix the policy reads only 05h and 15h, refuses 35h from SPI-NOR, and checks WIP, WEL and BP0 to BP3. flash_status reads the JEDEC ID first and sends 35h only to Winbond parts. SPI-NOR has no entry for c22b27 and binds it through the generic Macronix entry from SFDP, which never issues 35h on a single-line setup (the SFDP quad-enable method for Macronix is SR1 bit 6).

The partition and controller facts are from the macOS 27.0 ADT on the J616s: qspi@29118000 with windows at 0x429118000 and 0x429118800, IRQ 1573, and spinor@0 with firmware@0, syscfg@0x680000 and anvram@0x700000 (size 0x100000).

Not known yet

  • The J616s JEDEC ID c22b27 comes from macOS ioreg. Linux has not read it yet; flash_status should show jedec=c22b27 sr=.. cr=.. on first boot. The ID is checked before every program or erase, so a mismatch gets -ENODEV before any write enable.
  • c22b27 is not in the SPI-NOR table, so the flash only probes if it has SFDP. A part without SFDP would fail to probe, which leaves the NOR untouched.
  • The 4 KiB erase (20h) and 3-byte page program (02h) are standard on Macronix serial NOR. If SFDP reports a size over 16 MiB, SPI-NOR switches to 4-byte opcodes and the policy refuses every write, which fails safe.
  • The spi0 alias moves the Touch ID controller (spi2) from dynamic bus 0 to bus 1. Nothing in the tree names that bus, but Touch ID should be checked once on hardware.
  • The DT half reaches the J616s with the next Stage 2 DTB refresh, since the J616s takes its DT from Stage 2.

Tested

On the buildbox, on aurora-wip 8348f82 with PR 626 cherry-picked (the stack merges into aurora-wip with no conflicts and the same tree):

  • spi-apple-qspi.o and spi-apple-qspi-policy-test.o build with W=1 and no warnings (defconfig + asahi.config, LLVM).
  • make CHECK_DTBS=y for t6040-j616s, t6040-j616s-tether, t8140-j700, t8132-j713 and t8122-j613-25g83, no warnings. The J616s dtb has the flash, the partition and both aliases.
  • dt_binding_check DT_SCHEMA_FILES=spi/apple,qspi.yaml passes.
  • KUnit apple-qspi-nvram-policy under UML, 9/9 pass, including the 0x700000 partition edges and the Macronix status check.
  • Host harness tools/asahi/qspi-nvram-tests.py all PASS, now covering 140 board/controller/aperture combinations, a Macronix write sequence that asserts 35h never reaches the wire, BP0 to BP3 refusal, and changed-guard controls for the controller, board, aperture, J616s aperture and partition, Macronix BP3, and both EQIO guards.
  • checkpatch --strict is clean on all four commits.

Not tested on hardware. Please don't merge until a J616s run reads the NOR, shows the JEDEC ID and completes a --next set, macOS boot and return.

Assisted-by: LLM

🤖 Generated with Claude Code

RyanTheTide and others added 6 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>
@RyanTheTide

Copy link
Copy Markdown
Contributor Author

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

🤖 Generated with Claude Code

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