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
13 changes: 9 additions & 4 deletions devices/p105/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ public recipe or release.

## Open work

- Storage: the NAND driver (OrchardLinux/orchard#86, #87) is in
`kernel/patches/armv7/mtd`. The iOS disk translation layer and its append
writes (#89, #91) follow on top of it. Reclamation (#93) is unfinished:
until it lands, writes stop once the blank flash blocks are used up.
- Storage: the NAND driver (OrchardLinux/orchard#86, #87), the iOS disk
translation layer and its append writes (#89, #91) are in
`kernel/patches/armv7/mtd`; the disk is `applesftlN`, on the 64 GB model
only. Reclamation (#93) is unfinished: until it lands, writes stop once
the blank flash blocks are used up.
- A Linux root on LwVM: the kernel reads LwVM partitions
(`kernel/patches/armv7/block`), but the shared initrd finds its root by
reading a GPT itself and needs to learn LwVM partitions.
Expand All @@ -48,3 +49,7 @@ public recipe or release.
is part of the driver.
- #90's gadget config change (39b60b1, built-in CDC gadget off): not taken
yet; it waits until 32-bit boots bind their own configfs gadget.
- #89's read budget and lifetime module parameters (ffa74e8, source
e7d118292) and its module build (a607325): dropped; the reader is built in.
- #91's `MTD_APPLE_SFTL_WRITE` option and `write_enable` parameter (e049bb4):
removed, writing is part of the driver.
79 changes: 79 additions & 0 deletions docs/bring-up/p105-sftl-append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# P105 native append writes

The kernel `apple-sftl` writer appends to Apple's shared VFL/SwissPPNFTL
medium on the measured 64 GB P105. Native records carry the updated logical
map across reload. The boot reader must follow VFL and SFTL mapping to find
Linux data. LwVM partitions sit above the recovered `applesftlN` disk.
The [reader note](p105-sftl-reader.md) describes geometry and context recovery.

## Source and format

Kernel branch `p105/07-ftl-append` ends at
`c63274165870417f01cc3e1264067cef71d9c0b2`. It contains the verified PPN
program callback (`ab137f3be`), append/replay path (`cb20cc76a`) and request
allocation guard (`c63274165`), following the read-only reader and program
primitive dependencies. Kernel patch export remains pending integration.

The allocator reserves the boot and package tails, every saved SFTL context,
validated VFL bootstrap members and blocks referenced by the recovered map.
It checks every data and metadata byte across a candidate's active lanes
before programming. An occupied candidate is skipped. A failed storage transaction stops the
instance; exhausted blank space returns `ENOSPC` while reads
remain available. The allocator performs no erase or reclamation.

A program writes a complete 16 KiB page with 64 canonical metadata bytes.
One through four logical 4 KiB entries occupy its chunks; short requests
repeat the last entry with a distinct sequence for each copy. The provider
waits for completion and verifies exact readback before map publication.
Filled blocks receive eight summary pages containing native type-1c runs.
The span field at metadata offset eight is the next sequence minus the block
base; it reserves no additional eight sequence slots.

A fresh loader replays open data records or a completed summary. An
interrupted summary can be ignored only after its entire data prefix
validates. Torn or ambiguous metadata stops recovery. Recovery retains its
contiguous journal rule, so an allocation floor beyond the final visible
record prevents writes before mutation. The saved measured map has equal
allocation and journal sequence 187831212.

The frontend preserves untouched sectors during partial 4 KiB updates and
groups up to four logical pages per physical program. Flush and FUA complete
after synchronous readback. Request processing uses a NOIO allocation scope,
which prevents map or provider allocations from recursing into writeback
while the request lock is held.

## Operating policy

Orchard carries the writer in `kernel/patches/armv7/mtd` with writing as part
of the driver: there is no build option, module parameter or read budget. The
disk is writable when the provider can program pages and the replayed journal
ends at the allocation floor; otherwise it is read-only. Raw MTD devices stay
available beside it.

## Evidence and qualification

The exact source passed these software checks:

- Current ARMv7 core, block, fixture and H2FMI objects compiled in read-only
and write-enabled variants with `W=1` and warnings as errors. The existing
`vmstat.h` enum-arithmetic warning remained a warning. Built-in parameter
prefixes were verified from the objects.
- All 26 tests passed in a Linux KUnit kernel using pinned pristine v6.12,
exact core/block/test sources and a validation-only Kconfig dependency
symbol for the mock program provider. This run excludes H2FMI hardware.
Cases cover all 16 lanes, 8160 separate footer entries and fresh replay.
- Host ASan/UBSan checks passed 26 core cases and 13 read-only cases with
allocation, mutex and tree-API shims. Extracted request helpers passed 25
cases plus 2048 varied requests. Queue/policy and provider-wrapper checks
cover deadline failures and balanced allocation scope.
- The actual C core reconstructed saved private pages and matched 28830 map
boundary checks against the Python reference, plus native LwVM and System
prefixes. This issued zero device operations. Public fixtures generate
synthetic bytes.

The exact kernel writer has no physical-device write qualification. The
earlier unplugged Omarchy desktop used the local Python backend. A fresh
approved boot must establish recovery access and bounded reads before any
approved write work. Native iOS recovery after writes, reboot persistence
and interruption handling need physical evidence. P105 `storage_write`
is `untested` until a device test.
61 changes: 61 additions & 0 deletions docs/bring-up/p105-sftl-reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# P105 native VFL and SwissPPNFTL reader

H2FMI exposes physical PPN pages. Apple's VFL and SwissPPNFTL records supply
the logical medium above them. The proposed `apple-sftl` kernel driver reads
those records and publishes a read-only `applesftlN` disk. LwVM partition
mapping belongs above that disk, where encrypted iOS contents retain their
encryption.

The measured profile has two buses and eight CAUs per bus, each with 2,088
blocks of 128 normal-view pages. H2FMI supplies each 16 KiB data page with its
64 metadata bytes through a canonical provider. Context reads select the
separate SLC address view. This provider offers no program or erase callback.

Recovery requires agreeing VFL context replicas and lane tables. It validates
the saved SFTL context fragments, then replays later data and trim records in
sequence order. Completed-block summaries preserve allocation floors; open
blocks are scanned fully so an erased lane cannot hide a later completed
stripe. Malformed or incomplete maps prevent disk registration. Unsupported
geometry returns an error.

The map contains 15,625,016 entries, including sixteen internal keys. The
exported medium contains 15,625,000 logical 4 KiB pages, or 64,000,000,000 bytes,
and accepts 512-byte sector reads. The frontend serializes access to its
preallocated request buffer and drains requests before releasing the provider.

## Bounded qualification

The latest reader candidate is
[`e7d118292d828b9796d85d19872653f7d4a8cb67`](https://github.com/shaynelegault/linux/commit/e7d118292d828b9796d85d19872653f7d4a8cb67).
It follows the canonical H2FMI provider and native-map reader commits
`9b494217444fc61adae2f95bc2ec05e461d45049` and
`e5960c537455b9c40c57deb53823e80ca9b13bb4`.

Orchard carries this reader in `kernel/patches/armv7/mtd` without the read
budget and lifetime module parameters of that candidate: a mounted root reads
for as long as the system runs. A provider error still stops the reader.

## Software checks and limits

The unchanged mapping core reconstructed a saved private capture through
sequence 187831212. An independent Python reference agreed at all 28,830
checked endpoints of the map union. Both LwVM headers and the System prefix
matched previously captured native iOS logical reads. This comparison used
saved pages and performed zero device operations. Captured pages remain
private; public fixtures generate synthetic media.

At `e5960c537`, eight tests passed in an ARM64 QEMU KUnit kernel built from
pristine pinned Linux v6.12 plus the exact reader/test sources and required
Kbuild entries. This ran the mapping code without Cascadia or an H2FMI
hardware backend. The host version of those fixtures also passed under
ASan/UBSan with allocation, mutex and tree-API shims.

The canonical H2FMI provider shares controller code with the imported
two-CAU profile. That profile needs a regression check; native-map recovery
currently accepts only the measured 64 GB geometry.

The exact candidate still needs a complete qualification image and an
approved P105 boot with working USB recovery, followed by bounded native-map
reads. Host reconstruction and an ARM64 KUnit kernel do not qualify ARMv7
controller timing. P105 `storage_read` stays `untested` until then. Sustained
writes and interruption recovery belong to the dependent writer work.
1 change: 1 addition & 0 deletions kernel/configs/apple-armv7/config
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,7 @@ CONFIG_MTD_OF_PARTS=y
# CONFIG_MTD_OOPS is not set
# CONFIG_MTD_SWAP is not set
# CONFIG_MTD_PARTITIONED_MASTER is not set
CONFIG_MTD_APPLE_SFTL=y

#
# RAM/ROM/Flash chip drivers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shayne Legault <8741557+shaynelegault@users.noreply.github.com>
Date: Fri, 6 Nov 2026 11:48:07 -0600
Subject: [PATCH] mtd: apple-h2fmi: Provide page reads and programs to the FTL

Give the flash translation layer one provider for the whole NAND: it reads
complete pages, in the normal or the SLC context address view, and programs
them through the verified program path. Every call holds the bus lock for
the whole controller transaction and returns data and OOB separately. A
retired page returns its status without copying stale bytes.

Register the apple-sftl child for the supported 64 GB package geometry,
and remove it before the controller goes away.

[Orchard: from shaynelegault/orchard#89 ffa74e8 (source e7d118292) and
#91 e049bb4 (source ab137f3be); folded the program callback into this
patch; shortened the commit message]

Assisted-by: LLM
---
drivers/mtd/devices/mtd-apple-h2fmi.c | 90 +++++++++++++++++++++++++++
include/linux/mtd/apple-ppn.h | 33 ++++++++++
2 files changed, 123 insertions(+)
create mode 100644 include/linux/mtd/apple-ppn.h

diff --git a/drivers/mtd/devices/mtd-apple-h2fmi.c b/drivers/mtd/devices/mtd-apple-h2fmi.c
index a8f89c004d52..46a6bc603454 100644
--- a/drivers/mtd/devices/mtd-apple-h2fmi.c
+++ b/drivers/mtd/devices/mtd-apple-h2fmi.c
@@ -34,6 +34,7 @@
#include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/mtd/apple-ppn.h>
#include <linux/mtd/mtd.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
@@ -107,6 +108,7 @@ struct h2fmi {
struct mtd_info *mtd;
struct h2fmi_die *die;
unsigned int ndies;
+ struct platform_device *sftl;
};

/*
@@ -596,6 +598,85 @@ static int h2fmi_read_page_at(struct mtd_info *mtd, loff_t from,
return ret;
}

+/* Canonical normal/context reads share the same complete transaction lock. */
+static int h2fmi_ppn_read(void *priv, unsigned int bus, unsigned int cau,
+ unsigned int block, unsigned int page, bool context,
+ u8 *data, u8 *oob, u8 *status)
+{
+ struct h2fmi *fmi = priv;
+ struct h2fmi_bus *b;
+ u32 row;
+ int ret;
+
+ if (bus >= H2FMI_BUSES || !data || !oob || !status)
+ return -EINVAL;
+ b = &fmi->bus[bus];
+ if (context)
+ ret = h2fmi_slc_row(&b->geometry, cau, block, page, &row);
+ else
+ ret = h2fmi_page_row(&b->geometry, cau, block, page, &row);
+ if (ret)
+ return ret;
+
+ /* Retired pages have no data phase. Never return a stale page buffer. */
+ memset(data, 0, H2FMI_PAGE_DATA);
+ memset(oob, 0, H2FMI_PAGE_OOB);
+ mutex_lock(&b->lock);
+ ret = h2fmi_read_transaction(fmi, b, 0x0a, 0x37, row, 4,
+ b->page, H2FMI_PAGE_RAW, status);
+ if (!ret && h2fmi_page_status(*status) >= 0) {
+ if (*status == PPN_ST_ERASED && memchr_inv(b->page, 0xff, H2FMI_PAGE_RAW))
+ ret = -EIO;
+ else
+ h2fmi_split_page(b->page, data, oob);
+ } else if (!ret && *status != PPN_ST_RETIRED) {
+ ret = -EIO;
+ }
+ mutex_unlock(&b->lock);
+ return ret;
+}
+
+static int h2fmi_ppn_program(void *priv, unsigned int bus, unsigned int cau,
+ unsigned int block, unsigned int page,
+ const u8 *data, const u8 *oob)
+{
+ struct h2fmi *fmi = priv;
+
+ if (bus >= H2FMI_BUSES)
+ return -EINVAL;
+ return h2fmi_program_page(fmi, &fmi->bus[bus], cau, block, page, data, oob);
+}
+
+static const struct apple_ppn_ops h2fmi_ppn_ops = {
+ .read_page = h2fmi_ppn_read,
+ .program_page = h2fmi_ppn_program,
+};
+
+static int h2fmi_register_sftl(struct h2fmi *fmi)
+{
+ const struct h2fmi_geometry *g = &fmi->bus[0].geometry;
+ struct apple_ppn provider = {
+ .dev = fmi->dev,
+ .ops = &h2fmi_ppn_ops,
+ .priv = fmi,
+ .buses = H2FMI_BUSES,
+ .caus = g->caus,
+ .blocks = g->blocks,
+ .pages = g->pages,
+ };
+
+ /* This recovery implementation supports the measured 64 GB profile. */
+ if (!IS_ENABLED(CONFIG_MTD_APPLE_SFTL) || g->caus != 8 ||
+ g->blocks != 2088 || g->pages != 128 || g->slc_pages != 64)
+ return 0;
+ fmi->sftl = platform_device_register_data(fmi->dev, "apple-sftl",
+ PLATFORM_DEVID_AUTO,
+ &provider, sizeof(provider));
+ if (IS_ERR(fmi->sftl))
+ return PTR_ERR(fmi->sftl);
+ return 0;
+}
+
/*
* Turn the PPN's status byte into what MTD expects back from _read_oob: a
* count of corrected bitflips, or a negative errno.
@@ -841,6 +922,13 @@ static int h2fmi_probe(struct platform_device *pdev)
}
}

+ ret = h2fmi_register_sftl(fmi);
+ if (ret) {
+ for (i = 0; i < fmi->ndies; i++)
+ mtd_device_unregister(&fmi->mtd[i]);
+ return ret;
+ }
+
platform_set_drvdata(pdev, fmi);
dev_info(&pdev->dev, "%u CAUs, %u blocks of %u KiB each\n",
fmi->ndies, fmi->bus[0].geometry.blocks,
@@ -853,6 +941,8 @@ static void h2fmi_remove(struct platform_device *pdev)
struct h2fmi *fmi = platform_get_drvdata(pdev);
int i;

+ if (fmi->sftl)
+ platform_device_unregister(fmi->sftl);
for (i = 0; i < fmi->ndies; i++)
mtd_device_unregister(&fmi->mtd[i]);
}
diff --git a/include/linux/mtd/apple-ppn.h b/include/linux/mtd/apple-ppn.h
new file mode 100644
index 000000000000..73fba467d00f
--- /dev/null
+++ b/include/linux/mtd/apple-ppn.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LINUX_MTD_APPLE_PPN_H
+#define _LINUX_MTD_APPLE_PPN_H
+
+#include <linux/types.h>
+
+struct device;
+
+#define APPLE_PPN_PAGE_SIZE 16384
+#define APPLE_PPN_OOB_SIZE 64
+
+/* All buffers have canonical data/OOB layout, including context reads. */
+struct apple_ppn_ops {
+ /* Success means the complete page and metadata passed exact readback. */
+ int (*program_page)(void *priv, unsigned int bus, unsigned int cau,
+ unsigned int block, unsigned int page,
+ const u8 *data, const u8 *oob);
+ int (*read_page)(void *priv, unsigned int bus, unsigned int cau,
+ unsigned int block, unsigned int page, bool context,
+ u8 *data, u8 *oob, u8 *status);
+};
+
+struct apple_ppn {
+ struct device *dev;
+ const struct apple_ppn_ops *ops;
+ void *priv;
+ u16 buses;
+ u16 caus;
+ u16 blocks;
+ u16 pages;
+};
+
+#endif
Loading
Loading