diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 83b29a6c3f866..5690e198e6140 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -21,6 +21,8 @@ aliases { keyboard = &keyboard; + nvram = &nvram; + spi0 = &qspi; wifi0 = "/pcie@1cb0000000/pci@0,0/wifi@0,0"; bluetooth0 = "/pcie@1cb0000000/pci@0,0/bluetooth@0,1"; /* used by the loader to hand over the display */ @@ -1430,6 +1432,33 @@ status = "okay"; }; +/* + * Boot NOR. The partition is the J616s ADT /arm-io/qspi/spinor/anvram, + * flash-relative; asahi-bless and asahi-nvram find it by its label. + */ +&qspi { + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <10000000>; + spi-rx-bus-width = <1>; + spi-tx-bus-width = <1>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + nvram: partition@700000 { + label = "nvram"; + reg = <0x700000 0x100000>; + }; + }; + }; +}; + /* * Touch ID sensor (Mesa 0x3356). Its power is NUB GPIO 53 and its * data-ready line is AP GPIO 116, as the J616s ADT describes them. diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 961af1b7b5bd4..2f5ff4538294c 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -2103,7 +2103,7 @@ power-domains = <&ps_qspi>; #address-cells = <1>; #size-cells = <0>; - /* The firmware NOR needs a qualified partition/write policy. */ + /* Boards enable it with their flash and its nvram partition. */ status = "disabled"; }; diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index 25c4ee4183cc6..423c547805402 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -51,6 +51,8 @@ CONFIG_APPLE_WATCHDOG=y # m1n1 hands the ADT and its own log over as phram regions (/dev/mtd*). CONFIG_MTD=y CONFIG_MTD_PHRAM=m +# Apple NVRAM partitions are 4 KiB aligned (J700: 0x24000). +CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y CONFIG_VIDEO_APPLE_AVD=m CONFIG_VIDEO_APPLE_ISP=m CONFIG_DRM=y diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index ad875e80d8ba2..dbaf0d6949249 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -146,22 +146,23 @@ config SPI_APPLE_QSPI help This enables support for Apple's command-FIFO QSPIMC controller. Unlike the ordinary Apple SPIMC block, QSPIMC accepts packed SPI - memory descriptors and is used for the boot NOR on T8140 systems. + memory descriptors and is used for the boot NOR on M3 and later + systems. - T8122/T8132 controllers default to read-only access. Only the pinned - J613 NVRAM layout admits bounded writes after a runtime opt-in. - On T8140 only the nvram partition accepts programs and erases; the - rest of the boot NOR is read-only. + The boot NOR defaults to read-only access. On the J613, J713, J616s + and J700 a runtime opt-in admits bounded, verified page programs and + 4 KiB erases inside the nvram partition only; the rest of the boot + NOR stays read-only. config SPI_APPLE_QSPI_KUNIT_TEST - tristate "KUnit tests for the Apple QSPIMC boot NOR write window" if !KUNIT_ALL_TESTS + tristate "KUnit tests for the Apple QSPIMC NVRAM write policy" if !KUNIT_ALL_TESTS depends on KUNIT depends on SPI_APPLE_QSPI || COMPILE_TEST default KUNIT_ALL_TESTS help - Tests which T8140 boot NOR requests the QSPIMC driver admits: write - enable control, and programs and erases inside the nvram partition - only. No hardware is accessed. + Tests which boot NOR requests the QSPIMC driver admits: write + enable control, and page programs and 4 KiB erases inside the + nvram partition only. No hardware is accessed. config SPI_AR934X tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver" diff --git a/drivers/spi/spi-apple-qspi-policy-test.c b/drivers/spi/spi-apple-qspi-policy-test.c index 9ec58d587c60f..f6dc555057443 100644 --- a/drivers/spi/spi-apple-qspi-policy-test.c +++ b/drivers/spi/spi-apple-qspi-policy-test.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* - * KUnit tests for the T8140 boot NOR write window of the Apple QSPIMC driver. + * KUnit tests for the Apple QSPIMC NVRAM mutation policy, at the J700 and + * J613/J713/J616s nvram partitions. */ #include @@ -14,59 +15,57 @@ static int nv(unsigned int opcode, unsigned int addr_bytes, unsigned long long addr, unsigned int len, int data_out) { - return apple_qspi_window_allowed(opcode, addr_bytes, addr, len, - data_out, NV_START, NV_END); + return apple_qspi_mutation_allowed(opcode, addr_bytes, addr, len, + data_out, NV_START, NV_END); } -static void qspi_window_write_enable(struct kunit *test) +static void qspi_policy_write_enable(struct kunit *test) { KUNIT_EXPECT_TRUE(test, nv(0x06, 0, 0, 0, 0)); KUNIT_EXPECT_TRUE(test, nv(0x04, 0, 0, 0, 0)); KUNIT_EXPECT_FALSE(test, nv(0x06, 3, NV_START, 0, 0)); KUNIT_EXPECT_FALSE(test, nv(0x06, 0, 0, 1, 1)); - /* Write-enable control needs no window. */ - KUNIT_EXPECT_TRUE(test, apple_qspi_window_allowed(0x06, 0, 0, 0, 0, 0, 0)); + /* Write-enable control is virtual and needs no window. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_mutation_allowed(0x06, 0, 0, 0, 0, 0, 0)); } -static void qspi_window_program(struct kunit *test) +static void qspi_policy_program(struct kunit *test) { KUNIT_EXPECT_TRUE(test, nv(0x02, 3, NV_START, 256, 1)); KUNIT_EXPECT_TRUE(test, nv(0x02, 3, NV_START + 0x1234, 1, 1)); KUNIT_EXPECT_TRUE(test, nv(0x02, 3, NV_END - 256, 256, 1)); - KUNIT_EXPECT_TRUE(test, nv(0x12, 4, NV_START, 256, 1)); - /* Any byte outside, or a wrap within a page outside, is refused. */ + /* Any byte outside the partition is refused. */ KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START - 256, 256, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START - 1, 2, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_END, 1, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_END - 1, 2, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 3, 0, 256, 1)); - /* A program needs data out, and at most one 4 KiB block's worth. */ + /* A program stays within one page and needs data out. */ + KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START + 0xff, 2, 1)); + KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START, 257, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START, 0, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START, 256, 0)); - KUNIT_EXPECT_FALSE(test, nv(0x02, 3, NV_START, 4097, 1)); + /* Four-byte programs are not admitted. */ + KUNIT_EXPECT_FALSE(test, nv(0x12, 4, NV_START, 256, 1)); } -static void qspi_window_erase(struct kunit *test) +static void qspi_policy_erase(struct kunit *test) { KUNIT_EXPECT_TRUE(test, nv(0x20, 3, NV_START, 0, 0)); - KUNIT_EXPECT_TRUE(test, nv(0x20, 3, NV_START + 0x123, 0, 0)); - KUNIT_EXPECT_TRUE(test, nv(0x20, 3, NV_END - 1, 0, 0)); - KUNIT_EXPECT_TRUE(test, nv(0x21, 4, NV_START, 0, 0)); - KUNIT_EXPECT_FALSE(test, nv(0x20, 3, NV_START - 1, 0, 0)); + KUNIT_EXPECT_TRUE(test, nv(0x20, 3, NV_END - 0x1000, 0, 0)); + /* Only aligned 4 KiB sector erases inside the partition. */ + KUNIT_EXPECT_FALSE(test, nv(0x20, 3, NV_START + 0x123, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv(0x20, 3, NV_START - 0x1000, 0, 0)); KUNIT_EXPECT_FALSE(test, nv(0x20, 3, NV_END, 0, 0)); - /* 32 and 64 KiB erases cover the whole aligned block. */ - KUNIT_EXPECT_TRUE(test, nv(0x52, 3, 0x28000, 0, 0)); - KUNIT_EXPECT_FALSE(test, nv(0x52, 3, NV_START, 0, 0)); - KUNIT_EXPECT_TRUE(test, nv(0xd8, 3, 0x30000, 0, 0)); - KUNIT_EXPECT_TRUE(test, nv(0xdc, 4, 0x1e0000, 0, 0)); - KUNIT_EXPECT_FALSE(test, nv(0xd8, 3, NV_START, 0, 0)); - KUNIT_EXPECT_FALSE(test, nv(0xd8, 3, 0x1f0000, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv(0x21, 4, NV_START, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv(0x52, 3, 0x28000, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv(0xd8, 3, 0x30000, 0, 0)); /* An erase carries no data. */ KUNIT_EXPECT_FALSE(test, nv(0x20, 3, NV_START, 1, 0)); KUNIT_EXPECT_FALSE(test, nv(0x20, 3, NV_START, 0, 1)); } -static void qspi_window_refused_commands(struct kunit *test) +static void qspi_policy_refused_commands(struct kunit *test) { static const u8 refused[] = { 0x01, 0x31, 0x11, /* status register writes */ @@ -86,48 +85,114 @@ static void qspi_window_refused_commands(struct kunit *test) } } -static void qspi_window_address_width(struct kunit *test) +static void qspi_policy_address_width(struct kunit *test) { - /* Three address bytes cannot carry an address above 16 MiB. */ - KUNIT_EXPECT_FALSE(test, nv(0x02, 3, (1ULL << 24) + NV_START, 1, 1)); - KUNIT_EXPECT_FALSE(test, nv(0x20, 3, (1ULL << 24) + NV_START, 0, 0)); - KUNIT_EXPECT_FALSE(test, nv(0x12, 4, (1ULL << 32) + NV_START, 1, 1)); KUNIT_EXPECT_FALSE(test, nv(0x02, 2, NV_START, 1, 1)); + KUNIT_EXPECT_FALSE(test, nv(0x02, 4, NV_START, 1, 1)); KUNIT_EXPECT_FALSE(test, nv(0x20, 0, NV_START, 0, 0)); - KUNIT_EXPECT_FALSE(test, nv(0x20, 5, NV_START, 0, 0)); - /* A window that reaches the top of 24-bit addressing. */ - KUNIT_EXPECT_TRUE(test, apple_qspi_window_allowed(0x20, 3, 0xfff000, 0, 0, - 0xff0000, 1ULL << 24)); - /* A program that crosses 16 MiB cannot use three address bytes. */ - KUNIT_EXPECT_FALSE(test, apple_qspi_window_allowed(0x02, 3, 0xffffff, 2, 1, - 0xff0000, 1ULL << 25)); - KUNIT_EXPECT_TRUE(test, apple_qspi_window_allowed(0x12, 4, 0xffffff, 2, 1, - 0xff0000, 1ULL << 25)); + KUNIT_EXPECT_FALSE(test, nv(0x20, 4, NV_START, 0, 0)); + /* Addresses are not truncated to the wire width before the check. */ + KUNIT_EXPECT_FALSE(test, nv(0x02, 3, (1ULL << 24) + NV_START, 1, 1)); + KUNIT_EXPECT_FALSE(test, nv(0x20, 3, (1ULL << 24) + NV_START, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv(0x02, 3, ~0ULL, 1, 1)); +} + +static void qspi_policy_empty(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, apple_qspi_mutation_allowed(0x02, 3, 0, 1, 1, 0, 0)); + KUNIT_EXPECT_FALSE(test, apple_qspi_mutation_allowed(0x20, 3, 0, 0, 0, 0, 0)); + KUNIT_EXPECT_FALSE(test, apple_qspi_mutation_allowed(0x20, 3, NV_START, 0, 0, + NV_START, NV_START)); +} + +/* The J613, J713 and J616s nvram partition: 0x700000, 1 MiB. */ +#define NV7_START 0x700000ULL +#define NV7_END 0x800000ULL + +static int nv7(unsigned int opcode, unsigned int addr_bytes, + unsigned long long addr, unsigned int len, int data_out) +{ + return apple_qspi_mutation_allowed(opcode, addr_bytes, addr, len, + data_out, NV7_START, NV7_END); +} + +static void qspi_policy_nvram_700000(struct kunit *test) +{ + /* First and last page and sector of the partition. */ + KUNIT_EXPECT_TRUE(test, nv7(0x02, 3, NV7_START, 256, 1)); + KUNIT_EXPECT_TRUE(test, nv7(0x02, 3, NV7_END - 256, 256, 1)); + KUNIT_EXPECT_TRUE(test, nv7(0x20, 3, NV7_START, 0, 0)); + KUNIT_EXPECT_TRUE(test, nv7(0x20, 3, NV7_END - 0x1000, 0, 0)); + /* syscfg ends at 0x700000 and the NOR is 8 MiB: both stay out. */ + KUNIT_EXPECT_FALSE(test, nv7(0x02, 3, NV7_START - 1, 1, 1)); + KUNIT_EXPECT_FALSE(test, nv7(0x02, 3, NV7_START - 256, 256, 1)); + KUNIT_EXPECT_FALSE(test, nv7(0x20, 3, NV7_START - 0x1000, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv7(0x02, 3, NV7_END, 1, 1)); + KUNIT_EXPECT_FALSE(test, nv7(0x02, 3, NV7_END - 1, 2, 1)); + KUNIT_EXPECT_FALSE(test, nv7(0x20, 3, NV7_END, 0, 0)); + /* iBoot at 0 and the J700 window are outside this partition. */ + KUNIT_EXPECT_FALSE(test, nv7(0x02, 3, 0, 256, 1)); + KUNIT_EXPECT_FALSE(test, nv7(0x20, 3, NV_START, 0, 0)); + /* Larger erases and four-byte forms are still refused here. */ + KUNIT_EXPECT_FALSE(test, nv7(0x52, 3, NV7_START, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv7(0xd8, 3, NV7_START, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv7(0x21, 4, NV7_START, 0, 0)); + KUNIT_EXPECT_FALSE(test, nv7(0x12, 4, NV7_START, 256, 1)); + /* An address above 16 MiB is not aliased onto the partition. */ + KUNIT_EXPECT_FALSE(test, nv7(0x02, 3, (1ULL << 24) + NV7_START, 1, 1)); + KUNIT_EXPECT_FALSE(test, nv7(0x20, 3, (1ULL << 24) + NV7_START, 0, 0)); +} + +static void qspi_policy_status(struct kunit *test) +{ + /* BUSY and WEL are never admitted. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_status_allows_write(0x01, 0, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_status_allows_write(0x02, 0, 0, 0x1c)); + /* Block protect only where the board allows it. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_status_allows_write(0x04, 0, 0, 0)); + KUNIT_EXPECT_TRUE(test, apple_qspi_status_allows_write(0x04, 0x02, 0, 0x1c)); + /* CMP, SUS and WPS are refused everywhere. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_status_allows_write(0, 0x40, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_status_allows_write(0, 0x80, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_status_allows_write(0, 0, 0x04, 0x1c)); } -static void qspi_window_empty(struct kunit *test) +static void qspi_policy_mx_status(struct kunit *test) { - KUNIT_EXPECT_FALSE(test, apple_qspi_window_allowed(0x02, 3, 0, 1, 1, 0, 0)); - KUNIT_EXPECT_FALSE(test, apple_qspi_window_allowed(0x20, 3, 0, 0, 0, 0, 0)); - KUNIT_EXPECT_FALSE(test, apple_qspi_window_allowed(0x20, 3, NV_START, 0, 0, - NV_START, NV_START)); + /* The J616s Macronix part: no block protection is tolerated. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_mx_status_allows_write(0x00, 0)); + /* QE and SRWD stay as found and do not block a write. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_mx_status_allows_write(0x40, 0)); + KUNIT_EXPECT_TRUE(test, apple_qspi_mx_status_allows_write(0x80, 0)); + /* WIP and WEL are never admitted. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_mx_status_allows_write(0x01, 0x3c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_mx_status_allows_write(0x02, 0x3c)); + /* Each of BP0 to BP3, including BP3 at bit 5, which Winbond calls TB. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_mx_status_allows_write(0x04, 0)); + KUNIT_EXPECT_FALSE(test, apple_qspi_mx_status_allows_write(0x08, 0)); + KUNIT_EXPECT_FALSE(test, apple_qspi_mx_status_allows_write(0x10, 0)); + KUNIT_EXPECT_FALSE(test, apple_qspi_mx_status_allows_write(0x20, 0)); + KUNIT_EXPECT_TRUE(test, apple_qspi_mx_status_allows_write(0x3c, 0x3c)); } -static struct kunit_case apple_qspi_window_cases[] = { - KUNIT_CASE(qspi_window_write_enable), - KUNIT_CASE(qspi_window_program), - KUNIT_CASE(qspi_window_erase), - KUNIT_CASE(qspi_window_refused_commands), - KUNIT_CASE(qspi_window_address_width), - KUNIT_CASE(qspi_window_empty), +static struct kunit_case apple_qspi_policy_cases[] = { + KUNIT_CASE(qspi_policy_write_enable), + KUNIT_CASE(qspi_policy_program), + KUNIT_CASE(qspi_policy_erase), + KUNIT_CASE(qspi_policy_refused_commands), + KUNIT_CASE(qspi_policy_address_width), + KUNIT_CASE(qspi_policy_empty), + KUNIT_CASE(qspi_policy_nvram_700000), + KUNIT_CASE(qspi_policy_status), + KUNIT_CASE(qspi_policy_mx_status), {} }; -static struct kunit_suite apple_qspi_window_suite = { - .name = "apple-qspi-nvram-window", - .test_cases = apple_qspi_window_cases, +static struct kunit_suite apple_qspi_policy_suite = { + .name = "apple-qspi-nvram-policy", + .test_cases = apple_qspi_policy_cases, }; -kunit_test_suite(apple_qspi_window_suite); +kunit_test_suite(apple_qspi_policy_suite); -MODULE_DESCRIPTION("KUnit tests for the Apple QSPIMC boot NOR write window"); +MODULE_DESCRIPTION("KUnit tests for the Apple QSPIMC NVRAM mutation policy"); MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-apple-qspi-policy.h b/drivers/spi/spi-apple-qspi-policy.h index 5d788d69f4f8b..5acd9a62d5291 100644 --- a/drivers/spi/spi-apple-qspi-policy.h +++ b/drivers/spi/spi-apple-qspi-policy.h @@ -2,21 +2,33 @@ #ifndef APPLE_QSPI_POLICY_H #define APPLE_QSPI_POLICY_H -#define APPLE_QSPI_NVRAM_START 0x700000ULL -#define APPLE_QSPI_NVRAM_END 0x800000ULL #define APPLE_QSPI_PAGE_SIZE 256U #define APPLE_QSPI_ERASE_SIZE 4096U -/* Subtraction avoids overflow; addr is not truncated to wire width first. */ -static inline int apple_qspi_range_allowed(unsigned long long addr, unsigned int len) +/* + * [start, end) is the board's nvram partition. Subtraction avoids overflow; + * addr is not truncated to wire width first. + */ +static inline int apple_qspi_range_allowed(unsigned long long addr, + unsigned int len, unsigned long long start, + unsigned long long end) { - return len && addr >= APPLE_QSPI_NVRAM_START && - addr < APPLE_QSPI_NVRAM_END && len <= APPLE_QSPI_NVRAM_END - addr; + return len && start < end && addr >= start && addr < end && + len <= end - addr; } +/* + * May a write-path request reach the boot NOR? Write enable control is + * virtual and admitted. A single-wire, three-byte page program that stays + * within one 256-byte page, or an aligned 4 KiB sector erase, is admitted + * only when every byte it can change lies inside [start, end). Everything + * else (status and configuration writes, larger erases, chip erase, + * address-mode, reset and OTP commands) is refused. + */ static inline int apple_qspi_mutation_allowed(unsigned int opcode, unsigned int addr_bytes, unsigned long long addr, - unsigned int len, int data_out) + unsigned int len, int data_out, + unsigned long long start, unsigned long long end) { switch (opcode) { case 0x06: /* Virtual WREN, never sent independently. */ @@ -24,76 +36,44 @@ static inline int apple_qspi_mutation_allowed(unsigned int opcode, return !addr_bytes && !len && !data_out; case 0x02: /* PP: prevent page wrap, even if SPI-NOR gets geometry wrong. */ return addr_bytes == 3 && data_out && len <= APPLE_QSPI_PAGE_SIZE && - apple_qspi_range_allowed(addr, len) && + apple_qspi_range_allowed(addr, len, start, end) && len <= APPLE_QSPI_PAGE_SIZE - (addr & (APPLE_QSPI_PAGE_SIZE - 1)); case 0x20: /* SE: hard-code the entire affected 4 KiB range. */ return addr_bytes == 3 && !len && !data_out && !(addr & (APPLE_QSPI_ERASE_SIZE - 1)) && - apple_qspi_range_allowed(addr, APPLE_QSPI_ERASE_SIZE); + apple_qspi_range_allowed(addr, APPLE_QSPI_ERASE_SIZE, + start, end); default: return 0; } } /* - * T8140 boot NOR: may a request that does not read data reach the flash? - * Write-enable control is admitted. A page program, or a 4, 32 or 64 KiB - * erase, is admitted only when every byte it can change lies inside - * [start, end), the nvram partition. A program counts the whole 4 KiB - * blocks it touches, which covers a write that wraps within its page. Status - * and configuration register writes, chip erase, address-mode, reset and OTP - * commands are refused. The address must fit in addr_bytes: the controller - * sends only those bytes, so a wider value would alias a lower address. + * Protection and activity bits must permit a write; JEDEC ID is checked + * separately. BUSY/WEL, CMP/SUS and WPS are always refused. bp_ok names the + * SR1 block-protect bits a board's firmware leaves set at boot: they stay + * unchanged, the flash ignores programs and erases in the range they protect, + * and the driver's readback then latches it read-only. SRP, TB, SEC, QE and + * LB stay unchanged. */ -static inline int apple_qspi_window_allowed(unsigned int opcode, - unsigned int addr_bytes, unsigned long long addr, - unsigned int len, int data_out, - unsigned long long start, unsigned long long end) +static inline int apple_qspi_status_allows_write(unsigned int sr1, + unsigned int sr2, unsigned int sr3, unsigned int bp_ok) { - unsigned long long block, first, last; - - switch (opcode) { - case 0x06: /* WREN */ - case 0x04: /* WRDI */ - return !addr_bytes && !len && !data_out; - case 0x02: /* PP */ - case 0x12: /* PP_4B */ - if (!data_out || !len || len > APPLE_QSPI_ERASE_SIZE) - return 0; - block = APPLE_QSPI_ERASE_SIZE; - break; - case 0x20: /* BE_4K */ - case 0x21: /* BE_4K_4B */ - block = 0x1000; - break; - case 0x52: /* BE_32K */ - case 0x5c: /* BE_32K_4B */ - block = 0x8000; - break; - case 0xd8: /* SE */ - case 0xdc: /* SE_4B */ - block = 0x10000; - break; - default: - return 0; - } - if (opcode != 0x02 && opcode != 0x12 && (len || data_out)) - return 0; - if ((addr_bytes != 3 && addr_bytes != 4) || addr >> (8 * addr_bytes)) - return 0; - - /* [first, last) are the blocks the flash can change. */ - first = addr & ~(block - 1); - last = ((addr + (len ? len : 1) - 1) | (block - 1)) + 1; - return last <= 1ULL << (8 * addr_bytes) && - start <= first && last <= end; + return !(sr1 & 0x03) && !(sr1 & 0x1c & ~bp_ok) && + !(sr2 & 0xc0) && !(sr3 & 0x04); } -/* Protection and activity bits must permit a write; JEDEC ID is checked separately. */ -static inline int apple_qspi_status_allows_write(unsigned int sr1, - unsigned int sr2, unsigned int sr3) +/* JEDEC manufacturer ID of Macronix parts. */ +#define APPLE_QSPI_MFR_MACRONIX 0xc2 + +/* + * The Macronix status register holds WIP, WEL and four block-protect bits, + * BP[3:0] at bits 5:2. BUSY and WEL are always refused, and block-protect + * bits only where bp_ok allows them. QE and SRWD stay unchanged. + */ +static inline int apple_qspi_mx_status_allows_write(unsigned int sr, + unsigned int bp_ok) { - /* BUSY/WEL, BP[2:0]; CMP/SUS; WPS. SRP, TB, SEC, QE, LB stay unchanged. */ - return !(sr1 & 0x1f) && !(sr2 & 0xc0) && !(sr3 & 0x04); + return !(sr & 0x03) && !(sr & 0x3c & ~bp_ok); } #endif diff --git a/drivers/spi/spi-apple-qspi.c b/drivers/spi/spi-apple-qspi.c index c594a241dcef8..f4b4b6a0d5202 100644 --- a/drivers/spi/spi-apple-qspi.c +++ b/drivers/spi/spi-apple-qspi.c @@ -7,10 +7,10 @@ * command FIFO. This driver intentionally implements the PIO spi-mem path * only; that is sufficient for SPI NOR discovery and access. * - * On T8140 reads reach the whole boot NOR, but programs and erases are - * admitted only inside its unique, aligned nvram partition. Without that - * partition the NOR is read-only. - * + * The boot NOR starts read-only. On the qualified boards (J613, J713, + * J616s and the J700) root may opt in at runtime to bounded, verified + * programs and 4 KiB erases inside the nvram partition; everything else + * stays read-only. On T8140 data reads reach the whole NOR. */ #include @@ -88,6 +88,45 @@ #define APPLE_QSPI_MAX_HZ 40000000 #define APPLE_QSPI_TIMEOUT_MS 1000 +/* Per-SoC controller differences, from the compatible. */ +struct apple_qspi_soc { + /* T8140: data reads of any shape pass straight to the controller. */ + bool multi_io_reads; +}; + +/* + * A board whose NVRAM may be written: the controller compatible, the + * machine, the controller aperture, the nvram partition the device tree + * must describe, the boot NOR JEDEC ID and the SR1 block-protect bits its + * firmware leaves set at boot. + */ +struct apple_qspi_board { + const char *controller; + const char *machine; + resource_size_t base; + u32 nvram_start; + u32 nvram_size; + u8 jedec[3]; + u8 bp_ok; +}; + +#define APPLE_QSPI_JEDEC_W25Q64 { 0xef, 0x65, 0x17 } +#define APPLE_QSPI_JEDEC_W25Q16 { 0xef, 0x65, 0x15 } +#define APPLE_QSPI_JEDEC_MX_2B27 { 0xc2, 0x2b, 0x27 } + +static const struct apple_qspi_board apple_qspi_boards[] = { + { "apple,t8122-qspi", "apple,j613", 0x2a1118000ULL, + 0x700000, 0x100000, APPLE_QSPI_JEDEC_W25Q64, 0 }, + { "apple,t8132-qspi", "apple,j713", 0x3ad118000ULL, + 0x700000, 0x100000, APPLE_QSPI_JEDEC_W25Q64, 0 }, + /* Macronix; the ID macOS reports for the J616s boot NOR. */ + { "apple,t6041-qspi", "apple,j616s", 0x429118000ULL, + 0x700000, 0x100000, APPLE_QSPI_JEDEC_MX_2B27, 0 }, + /* iBoot leaves BP0 set on the J700, over the top 64 KiB. */ + { "apple,t8140-qspi", "apple,j700", 0x385110000ULL, + 0x24000, 0x1d0000, APPLE_QSPI_JEDEC_W25Q16, 0x1c }, +}; + static DEFINE_MUTEX(apple_qspi_policy_lock); static bool read_only = true; static bool runtime_ready; @@ -125,7 +164,7 @@ static const struct kernel_param_ops apple_qspi_read_only_ops = { .get = apple_qspi_get_read_only, }; module_param_cb(read_only, &apple_qspi_read_only_ops, &read_only, 0644); -MODULE_PARM_DESC(read_only, "Default true; root runtime opt-in for bounded J613/J713 NVRAM writes"); +MODULE_PARM_DESC(read_only, "Default true; root runtime opt-in for bounded NVRAM writes on qualified boards"); static bool apple_qspi_opcode_is_read(u8 opcode) { @@ -149,12 +188,16 @@ static bool apple_qspi_opcode_is_read(u8 opcode) } struct apple_qspi { - /* T8122/T8132 use the serialized, verified NVRAM policy. */ + /* Every write-path request goes through the verified NVRAM policy. */ bool nvram_policy; - /* T8140 admits programs and erases only inside nvram. */ + /* T8140: data reads of any shape pass straight to the controller. */ bool nvram_window; + /* The nvram partition [window_start, window_end). */ u64 window_start; u64 window_end; + /* Expected JEDEC ID and the SR1 block-protect bits left by firmware. */ + u8 jedec[3]; + u8 bp_ok; bool nvram_write_candidate; bool write_fault; void __iomem *base; @@ -596,7 +639,7 @@ static int apple_qspi_transfer_op(struct spi_mem *mem, return ret; } -/* Private transfers used by the serialized J613 write policy. */ +/* Private transfers used by the serialized NVRAM write policy. */ static int apple_qspi_read_register(struct spi_mem *mem, u8 opcode, u8 *buf, u32 len) { struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 1), @@ -623,12 +666,24 @@ static int apple_qspi_read_array(struct spi_mem *mem, u64 addr, u8 *buf, u32 len return apple_qspi_transfer_op(mem, &op); } -static int apple_qspi_status(struct spi_mem *mem, u8 sr[3]) +/* + * Winbond parts have three status registers (05h, 35h, 15h). On Macronix + * parts 35h is EQIO, which switches the flash to QPI mode, so only the + * status (05h) and configuration (15h) registers are read there. + */ +static int apple_qspi_status(struct spi_mem *mem, u8 mfr, u8 sr[3]) { - static const u8 opcode[] = { 0x05, 0x35, 0x15 }; - int i, ret; - - for (i = 0; i < 3; i++) { + static const u8 winbond[] = { 0x05, 0x35, 0x15 }; + static const u8 macronix[] = { 0x05, 0x15 }; + const u8 *opcode = winbond; + int i, n = ARRAY_SIZE(winbond), ret; + + if (mfr == APPLE_QSPI_MFR_MACRONIX) { + opcode = macronix; + n = ARRAY_SIZE(macronix); + } + sr[2] = 0; + for (i = 0; i < n; i++) { ret = apple_qspi_read_register(mem, opcode[i], &sr[i], 1); if (ret) return ret; @@ -652,15 +707,18 @@ static int apple_qspi_nvram_modify(struct spi_mem *mem, const struct spi_mem_op ret = apple_qspi_read_register(mem, 0x9f, id, sizeof(id)); if (ret) goto out; - if (id[0] != 0xef || id[1] != 0x65 || id[2] != 0x17) { + if (memcmp(id, qspi->jedec, sizeof(id))) { ret = -ENODEV; goto out; } - ret = apple_qspi_status(mem, before); + ret = apple_qspi_status(mem, id[0], before); if (ret) goto out; /* No unlocking. Conservatively refuse BP/CMP/WPS, BUSY/WEL or suspend. */ - if (!apple_qspi_status_allows_write(before[0], before[1], before[2])) { + if (id[0] == APPLE_QSPI_MFR_MACRONIX ? + !apple_qspi_mx_status_allows_write(before[0], qspi->bp_ok) : + !apple_qspi_status_allows_write(before[0], before[1], before[2], + qspi->bp_ok)) { ret = -EACCES; goto out; } @@ -713,7 +771,7 @@ static int apple_qspi_nvram_modify(struct spi_mem *mem, const struct spi_mem_op ret = apple_qspi_command(mem, 0x04); if (ret) goto fault; - ret = apple_qspi_status(mem, after); + ret = apple_qspi_status(mem, id[0], after); if (ret) goto fault; if (memcmp(before, after, sizeof(before))) { @@ -762,6 +820,12 @@ static int apple_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) if (!apple_qspi_supports_op(mem, op) || apple_qspi_shape_op(op, &shape)) return -EOPNOTSUPP; mutex_lock(&apple_qspi_policy_lock); + /* 35h is EQIO, not a register read, on Macronix parts. */ + if (op->cmd.opcode == 0x35 && + qspi->jedec[0] == APPLE_QSPI_MFR_MACRONIX) { + ret = -EOPNOTSUPP; + goto out; + } if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes && apple_qspi_opcode_is_read(op->cmd.opcode)) { ret = apple_qspi_transfer_op(mem, op); @@ -780,7 +844,8 @@ static int apple_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) } if (!apple_qspi_mutation_allowed(op->cmd.opcode, op->addr.nbytes, op->addr.val, op->data.nbytes, - op->data.dir == SPI_MEM_DATA_OUT)) { + op->data.dir == SPI_MEM_DATA_OUT, + qspi->window_start, qspi->window_end)) { denial = "mutation-policy"; goto out; } @@ -802,36 +867,14 @@ static int apple_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) return ret; } -/* T8140: data reads pass; anything else must stay inside the nvram window. */ -static int apple_qspi_window_exec_op(struct spi_mem *mem, - const struct spi_mem_op *op) -{ - struct apple_qspi *qspi = spi_controller_get_devdata(mem->spi->controller); - - if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes) - return apple_qspi_transfer_op(mem, op); - if (!op->dummy.nbytes && - apple_qspi_window_allowed(op->cmd.opcode, op->addr.nbytes, - op->addr.val, op->data.nbytes, - op->data.dir == SPI_MEM_DATA_OUT, - qspi->window_start, qspi->window_end)) - return apple_qspi_transfer_op(mem, op); - - dev_warn_ratelimited(&mem->spi->dev, - "boot NOR request denied: opcode=%02x addr=%#llx addr_bytes=%u len=%u dir=%u nvram=[%#llx, %#llx)\n", - op->cmd.opcode, (unsigned long long)op->addr.val, - op->addr.nbytes, op->data.nbytes, op->data.dir, - (unsigned long long)qspi->window_start, - (unsigned long long)qspi->window_end); - return -EACCES; -} - static int apple_qspi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct apple_qspi *qspi = spi_controller_get_devdata(mem->spi->controller); - if (qspi->nvram_window) - return apple_qspi_window_exec_op(mem, op); + /* T8140 keeps its multi-line data reads; spi-mem serializes them. */ + if (qspi->nvram_window && op->data.dir == SPI_MEM_DATA_IN && + op->data.nbytes) + return apple_qspi_transfer_op(mem, op); if (!qspi->nvram_policy) return apple_qspi_transfer_op(mem, op); return apple_qspi_exec_op(mem, op); @@ -844,57 +887,26 @@ static const struct spi_controller_mem_ops apple_qspi_mem_ops = { }; /* Keep write qualification tied to the board and its controller aperture. */ -static bool apple_qspi_nvram_write_board(struct device_node *node, - resource_size_t base) +static const struct apple_qspi_board * +apple_qspi_nvram_write_board(struct device_node *node, resource_size_t base) { - if (of_device_is_compatible(node, "apple,t8122-qspi")) - return of_machine_is_compatible("apple,j613") && - base == 0x2a1118000ULL; - if (of_device_is_compatible(node, "apple,t8132-qspi")) - return of_machine_is_compatible("apple,j713") && - base == 0x3ad118000ULL; - return false; -} - -/* Require the pinned partition and single physical chip select. */ -static bool apple_qspi_nvram_layout(struct device_node *node) -{ - struct device_node *flash, *parts, *part; - u32 reg[2], cs; - const char *label; - bool found = false; - - /* OF name matching excludes the unit address (@0); reg pins CS0. */ - flash = of_get_child_by_name(node, "flash"); - if (!flash) - return false; - if (!of_device_is_compatible(flash, "jedec,spi-nor") || - of_property_read_u32(flash, "reg", &cs) || cs) { - of_node_put(flash); - return false; + const struct apple_qspi_board *board; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(apple_qspi_boards); i++) { + board = &apple_qspi_boards[i]; + if (of_device_is_compatible(node, board->controller) && + of_machine_is_compatible(board->machine) && + base == board->base) + return board; } - parts = of_get_child_by_name(flash, "partitions"); - of_node_put(flash); - if (!parts) - return false; - if (of_device_is_compatible(parts, "fixed-partitions")) { - for_each_available_child_of_node(parts, part) { - if (!of_property_read_string(part, "label", &label) && - !strcmp(label, "nvram") && - of_property_count_u32_elems(part, "reg") == 2 && - !of_property_read_u32_array(part, "reg", reg, 2) && - reg[0] == APPLE_QSPI_NVRAM_START && - reg[1] == APPLE_QSPI_NVRAM_END - APPLE_QSPI_NVRAM_START) - found = true; - } - } - of_node_put(parts); - return found; + return NULL; } /* * The one "nvram" partition of the flash on CS0, 4 KiB aligned and within - * 32-bit addressing. Without it, no program or erase is admitted. + * three-byte addressing, since the policy only admits three-byte programs + * and erases. Without it, no program or erase is admitted. */ static void apple_qspi_nvram_window(struct device *dev, struct apple_qspi *qspi) { @@ -933,7 +945,7 @@ static void apple_qspi_nvram_window(struct device *dev, struct apple_qspi *qspi) } } of_node_put(parts); - if (found != 1 || start >= end || end > 1ULL << 32 || + if (found != 1 || start >= end || end > 1ULL << 24 || !IS_ALIGNED(start, APPLE_QSPI_ERASE_SIZE) || !IS_ALIGNED(end, APPLE_QSPI_ERASE_SIZE)) start = end = 0; @@ -941,8 +953,7 @@ static void apple_qspi_nvram_window(struct device *dev, struct apple_qspi *qspi) qspi->window_start = start; qspi->window_end = end; if (start < end) - dev_dbg(dev, "boot NOR read-only outside nvram [%#llx, %#llx)\n", - start, end); + dev_dbg(dev, "nvram partition [%#llx, %#llx)\n", start, end); else dev_dbg(dev, "boot NOR read-only: no usable nvram partition\n"); } @@ -950,22 +961,26 @@ static void apple_qspi_nvram_window(struct device *dev, struct apple_qspi *qspi) static int apple_qspi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + const struct apple_qspi_board *board; + const struct apple_qspi_soc *soc; struct spi_controller *ctlr; struct apple_qspi *qspi; struct resource *res; u32 depths, version; int irq, ret; + soc = of_device_get_match_data(dev); + if (!soc) + return -ENODEV; + ctlr = devm_spi_alloc_host(dev, sizeof(*qspi)); if (!ctlr) return -ENOMEM; qspi = spi_controller_get_devdata(ctlr); - qspi->nvram_window = of_device_is_compatible(dev->of_node, "apple,t8140-qspi"); - if (qspi->nvram_window) - apple_qspi_nvram_window(dev, qspi); - qspi->nvram_policy = of_device_is_compatible(dev->of_node, "apple,t8122-qspi") || - of_device_is_compatible(dev->of_node, "apple,t8132-qspi"); + qspi->nvram_policy = true; + qspi->nvram_window = soc->multi_io_reads; + apple_qspi_nvram_window(dev, qspi); if (of_device_is_compatible(dev->of_node, "apple,t8122-qspi") && !of_machine_is_compatible("apple,j613")) return -ENODEV; @@ -977,9 +992,14 @@ static int apple_qspi_probe(struct platform_device *pdev) return dev_err_probe(dev, -EINVAL, "QSPIMC aperture is too small\n"); - qspi->nvram_write_candidate = apple_qspi_nvram_write_board(dev->of_node, - res->start) && - apple_qspi_nvram_layout(dev->of_node); + /* The device tree must describe exactly the board's nvram partition. */ + board = apple_qspi_nvram_write_board(dev->of_node, res->start); + if (board && qspi->window_start == board->nvram_start && + qspi->window_end == (u64)board->nvram_start + board->nvram_size) { + memcpy(qspi->jedec, board->jedec, sizeof(qspi->jedec)); + qspi->bp_ok = board->bp_ok; + qspi->nvram_write_candidate = true; + } qspi->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(qspi->base)) return PTR_ERR(qspi->base); @@ -1016,7 +1036,7 @@ static int apple_qspi_probe(struct platform_device *pdev) ctlr->num_chipselect = 1; ctlr->max_native_cs = 1; ctlr->mode_bits = SPI_CPHA | SPI_CPOL; - if (!qspi->nvram_policy) + if (qspi->nvram_window) ctlr->mode_bits |= SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); @@ -1045,15 +1065,24 @@ static int apple_qspi_match_cs0(struct device *dev, const void *unused) !spi_get_chipselect(to_spi_device(dev), 0); } +static int apple_qspi_flash_read_reg(struct spi_mem *mem, u8 opcode, + u8 *buf, unsigned int len) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 1), + SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(len, buf, 1)); + + return spi_mem_exec_op(mem, &op); +} + static ssize_t flash_status_show(struct device *dev, struct device_attribute *attr, char *buf) { - static const u8 opcodes[] = { 0x9f, 0x05, 0x35, 0x15 }; struct spi_controller *ctlr = dev_get_drvdata(dev); struct spi_mem mem = { }; struct device *child; u8 *data; - int i, ret; + int ret; child = device_find_child(&ctlr->dev, NULL, apple_qspi_match_cs0); if (!child) @@ -1067,18 +1096,34 @@ static ssize_t flash_status_show(struct device *dev, /* spi-mem supplies queue, bus and I/O serialization for each read. * These are sequential samples, not an atomic protection snapshot. + * The JEDEC ID decides which registers exist: 35h is SR2 on Winbond + * parts but EQIO on Macronix ones, so it is sent to Winbond only. */ - for (i = 0; i < ARRAY_SIZE(opcodes); i++) { - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcodes[i], 1), - SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(i ? 1 : 3, data + (i ? i + 2 : 0), 1)); - - ret = spi_mem_exec_op(&mem, &op); - if (ret) - goto out_free; + ret = apple_qspi_flash_read_reg(&mem, 0x9f, data, 3) ?: + apple_qspi_flash_read_reg(&mem, 0x05, data + 3, 1); + if (ret) + goto out_free; + switch (data[0]) { + case 0xef: + ret = apple_qspi_flash_read_reg(&mem, 0x35, data + 4, 1) ?: + apple_qspi_flash_read_reg(&mem, 0x15, data + 5, 1); + if (!ret) + ret = sysfs_emit(buf, "jedec=%02x%02x%02x sr1=%02x sr2=%02x sr3=%02x\n", + data[0], data[1], data[2], data[3], + data[4], data[5]); + break; + case APPLE_QSPI_MFR_MACRONIX: + ret = apple_qspi_flash_read_reg(&mem, 0x15, data + 4, 1); + if (!ret) + ret = sysfs_emit(buf, "jedec=%02x%02x%02x sr=%02x cr=%02x\n", + data[0], data[1], data[2], data[3], + data[4]); + break; + default: + ret = sysfs_emit(buf, "jedec=%02x%02x%02x sr1=%02x\n", + data[0], data[1], data[2], data[3]); + break; } - ret = sysfs_emit(buf, "jedec=%02x%02x%02x sr1=%02x sr2=%02x sr3=%02x\n", - data[0], data[1], data[2], data[3], data[4], data[5]); out_free: kfree(data); out_put: @@ -1125,10 +1170,17 @@ static int apple_qspi_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(apple_qspi_pm_ops, apple_qspi_suspend, apple_qspi_resume); +static const struct apple_qspi_soc apple_qspi_single_io = { }; + +static const struct apple_qspi_soc apple_qspi_t8140 = { + .multi_io_reads = true, +}; + static const struct of_device_id apple_qspi_of_match[] = { - { .compatible = "apple,t8122-qspi" }, - { .compatible = "apple,t8132-qspi" }, - { .compatible = "apple,t8140-qspi" }, + { .compatible = "apple,t8122-qspi", .data = &apple_qspi_single_io }, + { .compatible = "apple,t8132-qspi", .data = &apple_qspi_single_io }, + { .compatible = "apple,t6041-qspi", .data = &apple_qspi_single_io }, + { .compatible = "apple,t8140-qspi", .data = &apple_qspi_t8140 }, {} }; MODULE_DEVICE_TABLE(of, apple_qspi_of_match); diff --git a/tools/asahi/qspi-nvram-tests.py b/tools/asahi/qspi-nvram-tests.py index 5b48039e8dbab..b458f3b06f3a8 100644 --- a/tools/asahi/qspi-nvram-tests.py +++ b/tools/asahi/qspi-nvram-tests.py @@ -5,6 +5,7 @@ import argparse import contextlib import json +import re import subprocess import tempfile @@ -68,6 +69,7 @@ def function(source, signature): struct apple_qspi { bool nvram_policy, nvram_write_candidate, write_fault, nvram_window; u64 window_start, window_end; + u8 jedec[3]; u8 bp_ok; }; struct spi_controller { struct apple_qspi *q; }; struct spi_device { struct spi_controller *controller; unsigned int cs; int dev; }; @@ -96,14 +98,14 @@ def function(source, signature): switch(o->cmd.opcode){ case 0x9f:memcpy(o->data.buf.in,jedec,o->data.nbytes);break; case 0x05:*(u8*)o->data.buf.in=sr[0]|(stuck_busy&&programmed?1:0);break; - case 0x35:*(u8*)o->data.buf.in=sr[1];break; + case 0x35:assert(jedec[0]!=0xc2);*(u8*)o->data.buf.in=sr[1];break; case 0x15:*(u8*)o->data.buf.in=sr[2];break; case 0x03: assert(o->addr.val+o->data.nbytes<=sizeof(flash)); memcpy(o->data.buf.in,flash+o->addr.val,o->data.nbytes); if(corrupt_readback&&programmed)((u8*)o->data.buf.in)[0]^=1; break; - case 0x06:assert(!sr[0]);sr[0]|=2;break; + case 0x06:assert(!(sr[0]&3));sr[0]|=2;break; case 0x04:sr[0]&=~2;break; case 0x02: assert(sr[0]&2);assert(o->addr.val>=0x700000&&o->addr.val+o->data.nbytes<=0x800000); @@ -123,7 +125,7 @@ def function(source, signature): static struct spi_mem mem={&spi}; static struct kernel_param parameter={&read_only}; static void reset(void){ - memset(flash,0xff,sizeof(flash));memset(sr,0,sizeof(sr));q=(struct apple_qspi){.nvram_policy=true,.nvram_write_candidate=true}; + memset(flash,0xff,sizeof(flash));memset(sr,0,sizeof(sr));q=(struct apple_qspi){.nvram_policy=true,.nvram_write_candidate=true,.window_start=0x700000,.window_end=0x800000,.jedec={0xef,0x65,0x17}}; read_only=true;runtime_ready=false;write_fault_latched=false;wire_count=0;fail_opcode=0;spi.cs=0; corrupt_readback=false;stuck_busy=false;programmed=false;deny_alloc=false;jiffies=0; jedec[0]=0xef;jedec[1]=0x65;jedec[2]=0x17; @@ -136,41 +138,47 @@ def function(source, signature): static bool sent(unsigned int o){for(unsigned int i=0;invram_start==0x700000&&qb->nvram_size==0x100000&&qb->jedec[0]==0xc2&&qb->jedec[1]==0x2b&&qb->jedec[2]==0x27&&!qb->bp_ok); + machine="apple,j700";qb=apple_qspi_nvram_write_board(&t8140,0x385110000ULL); + assert(qb&&qb->nvram_start==0x24000&&qb->nvram_size==0x1d0000&&qb->jedec[2]==0x15&&qb->bp_ok==0x1c); + puts("PASS J613/J713/J700/J616s board, controller and full-width aperture qualification (140 combinations)"); u8 data[256];memset(data,0x55,sizeof(data));reset(); struct spi_mem_op wren=SPI_MEM_OP(SPI_MEM_OP_CMD(0x06,1),SPI_MEM_OP_NO_ADDR,SPI_MEM_OP_NO_DUMMY,SPI_MEM_OP_NO_DATA); - /* T8140 uses a separate nvram window while the J613 gate stays read-only. - * This synthetic window shares the flash model's range; KUnit also covers - * the J700 partition's exact bounds. */ - q.nvram_policy=false;q.nvram_window=true; - q.window_start=0x700000;q.window_end=0x800000; - assert(!apple_qspi_mem_exec_op(&mem,&wren) && wire_count==1 && sr[0]==2); + /* T8140 (J700) shares the verified policy at its DT nvram window; its data + * reads pass without the opt-in. This synthetic window shares the flash + * model's range; KUnit covers the J700 partition's exact bounds. */ + q.nvram_window=true;q.bp_ok=0x1c; + u8 rdbuf[4];struct spi_mem_op t8140_read=SPI_MEM_OP(SPI_MEM_OP_CMD(0x03,1),SPI_MEM_OP_ADDR(3,0x10,1),SPI_MEM_OP_NO_DUMMY,SPI_MEM_OP_DATA_IN(4,rdbuf,1)); + assert(!apple_qspi_mem_exec_op(&mem,&t8140_read) && wire_count==1); + wire_count=0;assert(apple_qspi_mem_exec_op(&mem,&wren)==-EACCES && !wire_count); struct spi_mem_op window_program=program(0x700000,256,data); - assert(!apple_qspi_mem_exec_op(&mem,&window_program) && wire_count==2); - assert(!memcmp(flash+0x700000,data,256) && read_only); + assert(apple_qspi_mem_exec_op(&mem,&window_program)==-EACCES && !wire_count); + arm();sr[0]=4; + assert(!apple_qspi_mem_exec_op(&mem,&wren) && !wire_count); + assert(!apple_qspi_mem_exec_op(&mem,&window_program) && sent(0x02) && sr[0]==4); + assert(!memcmp(flash+0x700000,data,256)); const u64 outside_window[]={0,0x6fffff,0x800000,0x100700000ULL}; - for(unsigned int i=0;i= APPLE_QSPI_NVRAM_START', 'addr >= 1'), + ('controller', 'of_device_is_compatible(node, board->controller) &&', ''), + ('board', 'of_machine_is_compatible(board->machine) &&', ''), + ('aperture', 'base == board->base', 'base != 0'), + ('j616s-aperture', '"apple,j616s", 0x429118000ULL', '"apple,j616s", 0x429118800ULL'), + ('j616s-partition', '"apple,j616s", 0x429118000ULL,\n\t 0x700000', '"apple,j616s", 0x429118000ULL,\n\t 0x680000'), + ('range', 'addr >= start &&', 'addr >= 1 &&'), + ('bp-ok', '!(sr1 & 0x1c & ~bp_ok)', '1'), + ('mx-bp3', '!(sr & 0x3c & ~bp_ok)', '!(sr & 0x1c & ~bp_ok)'), + ('mx-eqio', 'opcode = macronix;', 'opcode = winbond;'), + ('mx-eqio-read', 'qspi->jedec[0] == APPLE_QSPI_MFR_MACRONIX) {', '0) {'), ('read-only', 'read_only || !qspi->nvram_write_candidate || qspi->write_fault', '!qspi->nvram_write_candidate || qspi->write_fault'), ('fault-latch', 'write_fault_latched = true;', 'write_fault_latched = false;')]: