Skip to content
Merged
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
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
16 changes: 8 additions & 8 deletions drivers/spi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,20 @@ config SPI_APPLE_QSPI
Unlike the ordinary Apple SPIMC block, QSPIMC accepts packed SPI
memory descriptors and is used for the boot NOR on T8140 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 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
128 changes: 73 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 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,67 @@ 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));
}

static void qspi_policy_j613(struct kunit *test)
{
unsigned long long s = APPLE_QSPI_NVRAM_START, e = APPLE_QSPI_NVRAM_END;

KUNIT_EXPECT_TRUE(test, apple_qspi_mutation_allowed(0x02, 3, s, 256, 1, s, e));
KUNIT_EXPECT_TRUE(test, apple_qspi_mutation_allowed(0x20, 3, e - 0x1000, 0, 0, s, e));
KUNIT_EXPECT_FALSE(test, apple_qspi_mutation_allowed(0x02, 3, s - 1, 1, 1, s, e));
KUNIT_EXPECT_FALSE(test, apple_qspi_mutation_allowed(0x20, 3, e, 0, 0, s, e));
}

static void qspi_window_empty(struct kunit *test)
static void qspi_policy_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));
/* 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 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_j613),
KUNIT_CASE(qspi_policy_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");
95 changes: 32 additions & 63 deletions drivers/spi/spi-apple-qspi-policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,98 +2,67 @@
#ifndef APPLE_QSPI_POLICY_H
#define APPLE_QSPI_POLICY_H

/* The pinned J613/J713 nvram partition. */
#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. */
case 0x04: /* Virtual WRDI. */
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)
{
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;
}

/* 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)
unsigned int sr2, unsigned int sr3, 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 !(sr1 & 0x03) && !(sr1 & 0x1c & ~bp_ok) &&
!(sr2 & 0xc0) && !(sr3 & 0x04);
}
#endif
Loading