Skip to content
Open
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
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