Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8d859a3
build: produce static ARMv7 recovery tools
shaynelegault Nov 6, 2026
12928a3
boot: build Cascadia payloads from source
shaynelegault Nov 6, 2026
02dcc19
build: resolve ARMv7 recovery and distribution inputs
shaynelegault Nov 6, 2026
95933b7
recovery: bind ARMv7 RAM sessions to guarded host identity
shaynelegault Nov 6, 2026
000f135
kernel: scope shared patch layers by platform
shaynelegault Nov 6, 2026
7deef1c
p105: record PPN geometry and read qualification boundaries
shaynelegault Nov 6, 2026
c6b9cb6
mtd: export scoped PPN geometry and read support
shaynelegault Nov 6, 2026
c97d28b
p105: record complete-page programming qualification boundaries
shaynelegault Nov 6, 2026
bf97755
mtd: export default-off verified PPN programming
shaynelegault Nov 6, 2026
0b4bcc8
p105: record bounded native NAND reader evidence
shaynelegault Nov 6, 2026
ffa74e8
mtd: export bounded native VFL and SwissPPNFTL reads
shaynelegault Nov 6, 2026
a607325
ARM: config: build the bounded Apple SFTL reader as a module
shaynelegault Nov 6, 2026
3963389
boot: add a static checked LwVM root mapper
shaynelegault Nov 6, 2026
129b61a
boot: mount installed LwVM roots through the kernel provider
shaynelegault Nov 6, 2026
0db5f13
boot: preserve GPT USB setup and prepare handoff mounts first
shaynelegault Nov 6, 2026
1147263
build: select the installed LwVM backend by device data
shaynelegault Nov 6, 2026
8d33bb8
boot: require disjoint LwVM header copies
shaynelegault Nov 6, 2026
694b0e3
boot: load the selected SFTL module with an explicit policy
shaynelegault Nov 6, 2026
1a5ad49
boot: verify LwVM mappings before cleanup without storage reads
shaynelegault Nov 6, 2026
d58f912
ARM: enable the logical-root module and device mapper
shaynelegault Nov 6, 2026
78b3221
boot: reject incomplete LwVM command options before advancing argv
shaynelegault Nov 6, 2026
586a721
boot: preserve the install add-on session through root setup
shaynelegault Nov 6, 2026
07f9d4c
boot: restore RAM mounts after the install session exits
shaynelegault Nov 6, 2026
b2b860f
image: honor selected inputs when indexing platform layers
shaynelegault Nov 6, 2026
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
20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ the follow-ups under "Open work", so you can pick them up from there.
Patches are applied in the layer order of `kernel/series/<platform>`; within a
directory, in file-name order.

Platforms with different upstream bases can share a subsystem directory.
An adjacent `<patch>.patch.toml` restricts a patch to named platforms:

```toml
schema = 1
platforms = ["apple-armv7"]
```

A plain directory entry includes its unscoped patches and those scoped to
that platform. `scoped patches/drivers/usb` includes only patches explicitly
scoped to the platform. Use this form when adding a shared directory to a
series whose upstream base differs from the existing patches. Each patch
must be selected once; repeated and overlapping layers are refused.

- `kernel/patches/drivers/<subsystem>/`: changes to drivers and their
bindings, including new drivers. Use the subsystem directory that matches the
kernel's (`spi`, `input`, `power`, …); add one when none fits.
Expand All @@ -70,6 +84,12 @@ directory, in file-name order.
Number a new patch with the next free number in its directory. Don't renumber
existing patches.

After testing the source branch on hardware, use
`tools/kernel-tree/export.py --platform <platform>` to write scoped patches
and add missing scoped layers. Repeat `--platform` for each platform that
shares the generated kernel branch. The export keeps subsystem directories
and assigns the next free patch number.

A driver patch filed under `devices/` hides that it changes behaviour on other
devices, which is why the split matters.

Expand Down
14 changes: 14 additions & 0 deletions boot/cascadia/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
The ARMv7 cache-maintenance and Linux handoff sequence is derived from the
Project Cascadia A5 staging loader:

https://github.com/rzxvx/Project-Cascadia
reference revision: 091d441f19e101699f7e1835a80ccb48d6e54eec
license: GPL-2.0

The local P105 work fixed the executable entry by discarding ARM unwind
metadata and asserting the upload address in the linker script.

This producer keeps those entry safeguards. Hardware addresses come from
the device's profile. Orchard's configuration record and DTB editor add
bounded external-initramfs placement; that extension requires a fresh
hardware boot before it is described as working.
37 changes: 37 additions & 0 deletions boot/cascadia/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ARM32 iBEC staging payload

`build --profile devices/p105/cascadia.toml --output DIR` builds a loader
with Orchard's kernel toolchain through `buildenv`. The output directory
contains `loader.bin`, its ELF and a source/profile/toolchain receipt.
The ELF check requires executable ARM code at the upload address and checks
every allocated section against the raw bytes. ARM unwind tables are
discarded so they cannot displace the entry instruction.

`python -m orchard.image.cascadia --profile devices/p105/cascadia.toml
--loader DIR/loader.bin --loader-elf DIR/loader.elf --kernel zImage
--dtb s5l8942x-p105.dtb --initramfs recovery.cpio.gz --bootargs 'rdinit=/init'
--output linux.bin` assembles one device's payload. Kernel bytes remain
unchanged. The DTB editor preserves properties while updating `/chosen`
boot arguments and its initrd addresses, then reserves that destination.

The layout is a checked loader followed by padding to 32 KiB, the zImage,
the edited DTB and the supplied initramfs, with each data address aligned to
eight bytes. The loader validates its configuration checksum and bounds
before copying the initramfs to the profile's reserved high-memory range.
It then performs the Cascadia cache/MMU handoff with the DTB in `r2`.
The fixed loader stack uses the gap below offset `0x7000`; code must finish
below `0x6000`, and the configuration starts at `0x7f00`.

The zImage must carry the v6.12 ARM size table and end on an eight-byte
boundary. Its DTB follows immediately so the appended-DTB decompressor can
relocate it. Assembly bounds the inflated kernel together with the relocated
decompressor's workspace below the initramfs destination; unknown table
formats and overlapping layouts are refused.

The receipt describes every byte range and its hash. It records
`architecture = "arm"` and `initramfs_encoding = "cascadia-dtb"`, allowing a
distribution to reject an image assembled with another architecture or
payload format. This external-initramfs extension awaits hardware testing;
the earlier P105 boot embedded its initramfs in the kernel.

See `NOTICE` for source provenance. This producer contacts no device.
50 changes: 50 additions & 0 deletions boot/cascadia/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
"""Build the ARM32 staging loader from source and check its executable entry."""
import argparse
import json
from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / 'src'))
from orchard import buildenv
from orchard.image.cascadia import digest, profile, verify_loader


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--profile', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
described = profile(args.profile)
out = args.output.resolve()
if out.is_symlink() or (out.exists() and (not out.is_dir() or any(out.iterdir()))):
parser.error('The loader output must be a new or empty directory')
out.mkdir(parents=True, exist_ok=True)
(out / 'cascadia-generated.h').write_text(''.join(
f'#define {name.upper()} {hex(value)}\n' for name, value in sorted(described.items()) if name != 'schema'))
template = (ROOT / 'boot/cascadia/linux-boot.ld.in').read_text()
(out / 'linux-boot.ld').write_text(template.replace('@LOAD_ADDRESS@', hex(described['load_address'])))
image = 'localhost/orchard-hardware-kernel-toolchain:bookworm'
buildenv.build(image, ROOT / 'kernel/Containerfile', ROOT / 'kernel')
buildenv.run(image, ['sh', '/src/boot/cascadia/compile', '/out'],
[(ROOT, '/src', True), (out, '/out', False)])
binary, elf = (out / 'loader.bin').read_bytes(), (out / 'loader.elf').read_bytes()
verify_loader(binary, described, elf)
source = ROOT / 'boot/cascadia'
receipt = {'schema': 1, 'kind': 'cascadia-loader', 'architecture': 'arm',
'loader_format': 'cascadia', 'profile_sha256': digest(args.profile.read_bytes()),
'toolchain': buildenv.identity(image),
'sources': {path.name: digest(path.read_bytes()) for path in sorted(source.iterdir()) if path.is_file()},
'loader': {'bytes': len(binary), 'sha256': digest(binary)},
'elf': {'bytes': len(elf), 'sha256': digest(elf)},
'entry': described['load_address'], 'hardware_boot_verified': False}
(out / 'loader.json').write_text(json.dumps(receipt, indent=2, sort_keys=True) + '\n')
for name in ('linux_boot.o', 'linux_boot_start.o', 'cascadia-generated.h', 'linux-boot.ld'):
(out / name).unlink()
print(f'Loader: {out / "loader.bin"}')


if __name__ == '__main__':
main()
14 changes: 14 additions & 0 deletions boot/cascadia/compile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env sh
# SPDX-License-Identifier: GPL-2.0-only
set -eu
out=$1
src=${ORCHARD_SRC:-$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)}
clang-19 --target=armv7a-none-eabi -march=armv7-a -marm -mfloat-abi=soft \
-ffreestanding -fno-builtin -fno-stack-protector -fno-unwind-tables \
-fno-asynchronous-unwind-tables -Os -Wall -Wextra -Werror \
-I "$out" -c "$src/boot/cascadia/linux_boot.c" -o "$out/linux_boot.o"
clang-19 --target=armv7a-none-eabi -march=armv7-a -marm -I "$out" \
-c "$src/boot/cascadia/linux_boot_start.S" -o "$out/linux_boot_start.o"
ld.lld-19 --build-id=none -T "$out/linux-boot.ld" \
"$out/linux_boot_start.o" "$out/linux_boot.o" -o "$out/loader.elf"
llvm-objcopy-19 -O binary "$out/loader.elf" "$out/loader.bin"
22 changes: 22 additions & 0 deletions boot/cascadia/linux-boot.ld.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
ENTRY(_start)
SECTIONS
{
. = @LOAD_ADDRESS@;
.text : {
KEEP(*(.text.start))
*(.text .text.*)
*(.rodata .rodata.*)
*(.data .data.*)
}
ASSERT(. <= @LOAD_ADDRESS@ + 0x6000, "loader overlaps its stack")
. = @LOAD_ADDRESS@ + 0x7f00;
.loader_config : { KEEP(*(.loader_config)) }
ASSERT(SIZEOF(.loader_config) == 68, "loader configuration changed")
.ARM.attributes 0 : { *(.ARM.attributes) }
/DISCARD/ : {
*(.comment) *(.note*) *(.eh_frame*)
*(.ARM.exidx*) *(.ARM.extab*)
}
}
ASSERT(_start == @LOAD_ADDRESS@, "loader entry differs from the upload address")
87 changes: 87 additions & 0 deletions boot/cascadia/linux_boot.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-only
/* ARM32 iBEC handoff. See NOTICE for the Cascadia source and local changes. */
typedef unsigned int u32;
typedef unsigned char u8;

#include "cascadia-generated.h"

struct config {
u8 magic[4];
u32 version, header_size, load_address, kernel_offset;
u32 dtb_offset, dtb_size, initrd_offset, initrd_size;
u32 initrd_address, image_size, watchdog_address, l2_address;
u32 ram_start, ram_end, initrd_limit, crc32;
};

/* The assembler fills the dynamic fields and checksum before upload. */
__attribute__((section(".loader_config"), used))
volatile const struct config cascadia_config = {
{ 'C', 'B', '3', '2' }, 1, sizeof(struct config), LOAD_ADDRESS,
KERNEL_OFFSET, 0, 0, 0, 0, INITRD_ADDRESS, 0, WATCHDOG_ADDRESS,
L2_ADDRESS, RAM_START, RAM_END, INITRD_LIMIT, 0,
};

static int range(u32 start, u32 size, u32 first, u32 end)
{
return start >= first && start < end && size && size <= end - start;
}

static u32 checksum(void)
{
volatile const u8 *p = (volatile const u8 *)&cascadia_config;
u32 crc = ~0u, i, bit;
for (i = 0; i < sizeof(struct config) - sizeof(u32); i++) {
crc ^= p[i];
for (bit = 0; bit < 8; bit++)
crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1)));
}
return ~crc;
}

static u32 big_endian(volatile const u8 *p)
{
return ((u32)p[0] << 24) | ((u32)p[1] << 16) |
((u32)p[2] << 8) | p[3];
}

u32 cascadia_main(void)
{
volatile const struct config *c = &cascadia_config;
volatile const u8 *dtb, *from;
volatile u8 *to;
u32 i;

if (c->magic[0] != 'C' || c->magic[1] != 'B' ||
c->magic[2] != '3' || c->magic[3] != '2' ||
c->version != 1 || c->header_size != sizeof(struct config) ||
c->load_address != LOAD_ADDRESS || c->kernel_offset != KERNEL_OFFSET ||
c->watchdog_address != WATCHDOG_ADDRESS || c->l2_address != L2_ADDRESS ||
c->ram_start != RAM_START || c->ram_end != RAM_END ||
c->initrd_limit != INITRD_LIMIT ||
checksum() != c->crc32)
return 0;
if (!range(LOAD_ADDRESS, c->image_size, RAM_START, RAM_END) ||
c->dtb_offset < KERNEL_OFFSET || c->dtb_size < 40 ||
c->dtb_offset >= c->image_size ||
c->dtb_size > c->image_size - c->dtb_offset ||
c->initrd_offset < c->dtb_offset + c->dtb_size ||
c->initrd_offset >= c->image_size ||
c->initrd_size > c->image_size - c->initrd_offset ||
c->initrd_address != INITRD_ADDRESS ||
!range(INITRD_ADDRESS, c->initrd_size, RAM_START, INITRD_LIMIT) ||
INITRD_ADDRESS < LOAD_ADDRESS + c->image_size)
return 0;
dtb = (volatile const u8 *)(LOAD_ADDRESS + c->dtb_offset);
if (big_endian(dtb) != 0xd00dfeedu || big_endian(dtb + 4) != c->dtb_size)
return 0;
if (WATCHDOG_ADDRESS) {
volatile u32 *wdt = (volatile u32 *)WATCHDOG_ADDRESS;
for (i = 0; i < 4; i++)
wdt[i] = 0;
}
from = (volatile const u8 *)(LOAD_ADDRESS + c->initrd_offset);
to = (volatile u8 *)INITRD_ADDRESS;
for (i = 0; i < c->initrd_size; i++)
to[i] = from[i];
return LOAD_ADDRESS + c->dtb_offset;
}
135 changes: 135 additions & 0 deletions boot/cascadia/linux_boot_start.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Derived from Project Cascadia; see NOTICE. */
#include "cascadia-generated.h"

.arch armv7-a
.syntax unified
.arm

.equ KERNEL_ENTRY, LOAD_ADDRESS + KERNEL_OFFSET

.section .text.start, "ax"
.global _start
.global cascadia_main
.global linux_boot_jump
_start:
cpsid if
msr cpsr_c, #0xd3

ldr sp, =LOAD_ADDRESS + 0x7000
bl cascadia_main
/* main validates the image and returns DTB physical address. */
cmp r0, #0
beq 1f
b linux_boot_jump
1: wfi
b 1b

/*
* r0 = DTB physical address
* Tear down MMU/caches, then bx KERNEL_ENTRY with
* r0=0, r1=~0, r2=DTB
*/
linux_boot_jump:
mov r4, r0 /* save DTB */

bl v7_flush_dcache_all
bl l2x0_flush_all

mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #(1 << 0) /* MMU */
bic r0, r0, #(1 << 2) /* D-cache */
bic r0, r0, #(1 << 12) /* I-cache */
bic r0, r0, #(1 << 11) /* Z */
mcr p15, 0, r0, c1, c0, 0
isb

mov r0, #0
mcr p15, 0, r0, c7, c5, 0
mcr p15, 0, r0, c7, c5, 6
mcr p15, 0, r0, c8, c7, 0
dsb
isb

mov r0, #0
mvn r1, #0
mov r2, r4
ldr r3, =KERNEL_ENTRY
bx r3

1: wfi
b 1b

v7_flush_dcache_all:
push {r4-r11, lr}
dmb
mrc p15, 1, r0, c0, c0, 1
ands r3, r0, #0x7000000
mov r3, r3, lsr #23
beq .Lfinished
mov r10, #0
.Lloop_level:
add r2, r10, r10, lsr #1
mov r1, r0, lsr r2
and r1, r1, #7
cmp r1, #2
blt .Lskip_level
mcr p15, 2, r10, c0, c0, 0
isb
mrc p15, 1, r1, c0, c0, 0
and r2, r1, #7
add r2, r2, #4
movw r4, #0x3ff
ands r4, r4, r1, lsr #3
clz r5, r4
movw r7, #0x7fff
ands r7, r7, r1, lsr #13
.Lloop_way:
mov r9, r7
.Lloop_set:
orr r11, r10, r4, lsl r5
orr r11, r11, r9, lsl r2
mcr p15, 0, r11, c7, c14, 2
subs r9, r9, #1
bge .Lloop_set
subs r4, r4, #1
bge .Lloop_way
.Lskip_level:
add r10, r10, #2
cmp r3, r10
bgt .Lloop_level
.Lfinished:
mov r10, #0
mcr p15, 2, r10, c0, c0, 0
dsb
isb
pop {r4-r11, pc}

l2x0_flush_all:
push {r0, r1, r2, lr}
ldr r0, =L2_ADDRESS
cmp r0, #0
beq 2f
ldr r1, [r0]
lsr r2, r1, #6
and r2, r2, #0x3f
cmp r2, #3
cmpne r2, #2
cmpne r2, #1
bne 2f
ldr r1, [r0, #0x100]
tst r1, #1
beq 2f
mov r1, #0xff00
orr r1, r1, #0xff
str r1, [r0, #0x7fc]
ldr r2, =1000000
1: ldr r1, [r0, #0x7fc]
tst r1, #1
beq 2f
subs r2, r2, #1
bne 1b
3: wfi
b 3b
2: dsb
pop {r0, r1, r2, pc}
Loading
Loading