Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions arch/arm64/boot/dts/apple/t6040-j616s.dts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion arch/arm64/boot/dts/apple/t6040.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -2121,7 +2121,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";
};

Expand Down
2 changes: 2 additions & 0 deletions arch/arm64/configs/asahi.config
Original file line number Diff line number Diff line change
Expand Up @@ -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_AVE=m
CONFIG_VIDEO_APPLE_ISP=m
Expand Down
19 changes: 10 additions & 9 deletions drivers/spi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
175 changes: 120 additions & 55 deletions drivers/spi/spi-apple-qspi-policy-test.c
Original file line number Diff line number Diff line change
@@ -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 <kunit/test.h>
Expand All @@ -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 */
Expand All @@ -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");
Loading