~james/split

feat(pager): resolve write faults and recycle scrubbed frames

commit
034f655e01705b2f81bd72540021f2aeef3ef441 browse tree
parent
d5ec9d43bb40f942cd275ec0d52561516d6446ea
author
james <j@cpl0.zip>
signature
Verified ~james signed with a key registered by ~james
date
2026-07-29 19:16 UTC

7 files changed, +523 −0

Makefile added

@@ -0,0 +1,46 @@
1+SDK ?= $(HOME)/.cache/split/microkit-sdk-2.3.1
2+TOOL ?= $(HOME)/.cache/split/microkit-2.3.1/target/release/microkit
3+BOARD := $(SDK)/board/qemu_virt_aarch64/debug
4+CC := aarch64-linux-gnu-gcc
5+LD := aarch64-linux-gnu-ld
6+CFLAGS := -std=gnu11 -O2 -g -Wall -Wextra -Werror -Wno-unused-function -ffreestanding -nostdlib -mstrict-align -I$(BOARD)/include
7+QEMU := qemu-system-aarch64 -machine virt,virtualization=on -cpu cortex-a53 -m 2G -nographic -monitor none -serial stdio
8+
9+.PHONY: all proof clean
10+all: build/loader.img
11+
12+build:
13+ mkdir -p build
14+
15+build/pager.o: pager.c split.h | build
16+ $(CC) $(CFLAGS) -c $< -o $@
17+
18+build/alpha.o: client.c split.h | build
19+ $(CC) $(CFLAGS) -DCLIENT=0 -c $< -o $@
20+
21+build/beta.o: client.c split.h | build
22+ $(CC) $(CFLAGS) -DCLIENT=1 -c $< -o $@
23+
24+build/unsafe.o: pager.c split.h | build
25+ $(CC) $(CFLAGS) -DSHARED_WRITE=1 -c $< -o $@
26+
27+build/%.elf: build/%.o
28+ $(LD) -L$(BOARD)/lib $< -lmicrokit -Tmicrokit.ld -o $@
29+
30+build/loader.img: build/pager.elf build/alpha.elf build/beta.elf split.system
31+ MICROKIT_SDK=$(SDK) $(TOOL) --board qemu_virt_aarch64 --config debug --search-path build -o $@ -r build/report.txt split.system
32+
33+build/unsafe.img: build/unsafe.elf build/alpha.elf build/beta.elf split.system
34+ sed 's/pager.elf/unsafe.elf/' split.system > build/unsafe.system
35+ MICROKIT_SDK=$(SDK) $(TOOL) --board qemu_virt_aarch64 --config debug --search-path build -o $@ -r build/unsafe.txt build/unsafe.system
36+
37+proof:
38+ isabelle build -D .
39+
40+build/split.ml: Export.thy Reuse.thy Snapshot.thy Execution.thy Protocol.thy Isolation.thy Model.thy ROOT | build
41+ isabelle build -D .
42+ isabelle export -n -d . -O build/generated -x 'Split.Export:code/**' Split
43+ cp build/generated/Split.Export/code/split.ocaml $@
44+
45+clean:
46+ rm -rf build

client.c added

@@ -0,0 +1,19 @@
1+#include "split.h"
2+
3+void init(void) {}
4+
5+void notified(microkit_channel channel) {
6+ require(channel == 0, "unknown notification");
7+ volatile struct job *job = (void *)TASK;
8+ unsigned p = job->page, length = job->length;
9+ require(p < PAGES && length <= LIMIT, "edit bounds");
10+ volatile unsigned char *page = (void *)(BASE + p * BYTES);
11+ for (unsigned i = 0; i <= length; ++i) {
12+ page[i] = job->text[i];
13+ microkit_mr_set(0, p);
14+ microkit_mr_set(1, i);
15+ microkit_mr_set(2, page[i]);
16+ (void)microkit_ppcall(0, microkit_msginfo_new(1, 3));
17+ }
18+ (void)microkit_ppcall(0, microkit_msginfo_new(2, 0));
19+}

microkit.patch added

@@ -0,0 +1,73 @@
1+--- a/tool/microkit/src/sdf/cspace.rs
2++++ b/tool/microkit/src/sdf/cspace.rs
3+@@ -17,6 +17,7 @@
4+ Tcb,
5+ Sc,
6+ VSpace,
7++ Frame,
8+ }
9+
10+ #[derive(Debug, PartialEq, Eq)]
11+@@ -45,9 +46,9 @@
12+ // At the moment the four cap maps we support all have the 'pd' element,
13+ // so we can include it here. When that stops being the case we will
14+ // have to rework this a bit.
15+- check_attributes(xml_sdf, node, &["slot", "pd"])?;
16+-
17+- let pd = Rc::from(checked_lookup(xml_sdf, node, "pd")?);
18++ let source = if cap_type == CapMapType::Frame { "mr" } else { "pd" };
19++ check_attributes(xml_sdf, node, &["slot", source])?;
20++ let pd = Rc::from(checked_lookup(xml_sdf, node, source)?);
21+
22+ let slot: u64 = sdf_parse_required_attribute(xml_sdf, node, "slot")?;
23+
24+@@ -91,6 +92,7 @@
25+ "cap_tcb" => CapMap::from_xml(CapMapType::Tcb, xml_sdf, &*child)?,
26+ "cap_sc" => CapMap::from_xml(CapMapType::Sc, xml_sdf, &*child)?,
27+ "cap_vspace" => CapMap::from_xml(CapMapType::VSpace, xml_sdf, &*child)?,
28++ "cap_frame" => CapMap::from_xml(CapMapType::Frame, xml_sdf, &*child)?,
29+ child_name => {
30+ let location = loc_string(xml_sdf, child.range().start);
31+ if let Some(type_name) = child_name.strip_prefix("cap_") {
32+--- a/tool/microkit/src/sdf.rs
33++++ b/tool/microkit/src/sdf.rs
34+@@ -310,7 +310,11 @@
35+
36+ for pd in pds.values() {
37+ for cap_map in pd.cap_maps.iter() {
38+- if !pds.contains_key(&cap_map.pd) {
39++ if cap_map.cap_type == CapMapType::Frame {
40++ if !mrs.iter().any(|mr| mr.name.as_str() == cap_map.pd.as_ref()) {
41++ return Err(format!("Unknown frame region {}", cap_map.pd));
42++ }
43++ } else if !pds.contains_key(&cap_map.pd) {
44+ return Err(format!(
45+ "Error: unknown PD name '{}': {}",
46+ cap_map.pd,
47+--- a/tool/microkit/src/capdl/builder.rs
48++++ b/tool/microkit/src/capdl/builder.rs
49+@@ -1332,12 +1332,23 @@
50+ for cap_map in pd.cap_maps.iter() {
51+ // TODO: Once we add more CapMap options, they might not all have
52+ // the pd_name. But for now, they do.
53+- let pd_src_shadow_cspace = &pd_shadow_cspaces[&cap_map.pd];
54++ let pd_src_shadow_cspace = if cap_map.cap_type == CapMapType::Frame {
55++ &pd_shadow_cspaces[&pd.name]
56++ } else {
57++ &pd_shadow_cspaces[&cap_map.pd]
58++ };
59+
60+ let cap_map_obj = match cap_map.cap_type {
61+ CapMapType::Tcb => capdl_util_make_tcb_cap(pd_src_shadow_cspace.tcb),
62+ CapMapType::Sc => capdl_util_make_sc_cap(pd_src_shadow_cspace.sched_context),
63+ CapMapType::VSpace => capdl_util_make_page_table_cap(pd_src_shadow_cspace.vspace),
64++ CapMapType::Frame => {
65++ let frames = &mr_name_to_frames[cap_map.pd.as_ref()];
66++ if frames.len() != 1 {
67++ return Err(format!("Frame region {} must contain one page", cap_map.pd));
68++ }
69++ capdl_util_make_frame_cap(frames[0], true, true, false, true)
70++ },
71+ };
72+
73+ // Map this into the destination pd's cspace and the specified slot.

pager.c added

@@ -0,0 +1,249 @@
1+#include "split.h"
2+
3+#ifndef SHARED_WRITE
4+#define SHARED_WRITE 0
5+#endif
6+
7+static unsigned mapping[SLOTS], next = PAGES;
8+static unsigned available[FRAMES - PAGES], returned;
9+static seL4_CPtr caps[SLOTS];
10+static int started, busy = -1;
11+static char line[384];
12+static unsigned used;
13+static volatile uint32_t *uart = (void *)0x70000000ul;
14+
15+static unsigned char *frame(unsigned f) {
16+ return (unsigned char *)(BACKING + f * BYTES);
17+}
18+
19+static void done(void) { microkit_dbg_puts("@done\n"); }
20+
21+static seL4_Word physical(seL4_CPtr cap) {
22+ seL4_ARM_Page_GetAddress_t result = seL4_ARM_Page_GetAddress(cap);
23+ require(result.error == seL4_NoError, "frame address query");
24+ return result.paddr;
25+}
26+
27+static int length(const char *text, unsigned *size) {
28+ unsigned n = 0;
29+ while (text[n]) if (++n > LIMIT) return 0;
30+ *size = n;
31+ return 1;
32+}
33+
34+static void map(unsigned k, seL4_CPtr page, int write) {
35+ seL4_CapRights_t rights = seL4_CapRights_new(0, 0, 1, write);
36+ require(seL4_ARM_Page_Map(page, microkit_cspace_root_slot_to_cptr(1 + k / PAGES),
37+ BASE + (k % PAGES) * BYTES, rights,
38+ seL4_ARM_Default_VMAttributes | seL4_ARM_ExecuteNever) == seL4_NoError,
39+ "page map");
40+}
41+
42+static void table(void) {
43+ microkit_dbg_puts("SPLIT TABLE");
44+ for (unsigned k = 0; k < SLOTS; ++k) {
45+ microkit_dbg_puts(" "); number(mapping[k]);
46+ }
47+ microkit_dbg_puts("\n");
48+}
49+
50+void init(void) {
51+ for (unsigned f = 0; f < FRAMES; ++f)
52+ for (unsigned b = 0; b < BYTES; ++b) frame(f)[b] = 0;
53+ for (unsigned k = 0; k < SLOTS; ++k) {
54+ mapping[k] = k % PAGES;
55+ caps[k] = microkit_cspace_root_slot_to_cptr(10 + k);
56+ map(k, caps[k], SHARED_WRITE);
57+ }
58+ uart[0x30 / 4] |= 0x301;
59+ uart[0x2c / 4] = 0x60;
60+ uart[0x44 / 4] = 0x7ff;
61+ uart[0x38 / 4] = 0x10;
62+ microkit_irq_ack(5);
63+ table();
64+ microkit_dbg_puts("@ready\n");
65+}
66+
67+seL4_Bool fault(microkit_child child, microkit_msginfo info, microkit_msginfo *reply) {
68+ require(child < CLIENTS && microkit_msginfo_get_label(info) == seL4_Fault_VMFault,
69+ "unexpected fault");
70+ seL4_Word address = microkit_mr_get(seL4_VMFault_Addr);
71+ seL4_Word fsr = microkit_mr_get(seL4_VMFault_FSR);
72+ seL4_Word ip = microkit_mr_get(seL4_VMFault_IP);
73+ require(address >= BASE && address < BASE + PAGES * BYTES && (fsr & (1ul << 6)),
74+ "not a managed write fault");
75+ unsigned k = child * PAGES + (address - BASE) / BYTES;
76+ unsigned old = mapping[k], fresh = returned ? available[--returned] : next++;
77+ require(old < PAGES && fresh < FRAMES, "frame pool exhausted");
78+ seL4_Word source = physical(caps[k]);
79+ seL4_CPtr destination = microkit_cspace_root_slot_to_cptr(30 + fresh - PAGES);
80+ seL4_Word target = physical(destination);
81+ for (unsigned b = 0; b < BYTES; ++b) frame(fresh)[b] = frame(old)[b];
82+ require(seL4_ARM_Page_Unmap(caps[k]) == seL4_NoError, "page unmap");
83+ caps[k] = microkit_cspace_root_slot_to_cptr(30 + fresh - PAGES);
84+ map(k, caps[k], 1);
85+ mapping[k] = fresh;
86+ microkit_dbg_puts("SPLIT COPY "); number(k);
87+ microkit_dbg_puts(" "); number(old);
88+ microkit_dbg_puts(" "); number(fresh); microkit_dbg_puts("\n");
89+ table();
90+ microkit_dbg_puts("SPLIT FAULT "); number(k);
91+ microkit_dbg_puts(" "); number(address);
92+ microkit_dbg_puts(" "); number(ip);
93+ microkit_dbg_puts(" "); number(fsr);
94+ microkit_dbg_puts(" "); number(source);
95+ microkit_dbg_puts(" "); number(target); microkit_dbg_puts("\n");
96+ *reply = microkit_msginfo_new(0, 0);
97+ return seL4_True;
98+}
99+
100+microkit_msginfo protected(microkit_channel channel, microkit_msginfo info) {
101+ require(channel < CLIENTS && (int)channel == busy, "unexpected worker");
102+ if (microkit_msginfo_get_label(info) == 1) {
103+ unsigned p = microkit_mr_get(0), offset = microkit_mr_get(1), byte = microkit_mr_get(2);
104+ require(p < PAGES && offset <= LIMIT && byte < 256, "write bounds");
105+ unsigned k = channel * PAGES + p;
106+ require(frame(mapping[k])[offset] == byte, "write observation");
107+ microkit_dbg_puts("SPLIT W "); number(k);
108+ microkit_dbg_puts(" "); number(offset);
109+ microkit_dbg_puts(" "); number(byte); microkit_dbg_puts("\n");
110+ } else {
111+ require(microkit_msginfo_get_label(info) == 2, "unknown worker request");
112+ busy = -1;
113+ done();
114+ }
115+ return microkit_msginfo_new(0, 0);
116+}
117+
118+static void request(void) {
119+ if (busy >= 0) { microkit_dbg_puts("@error worker busy\n"); return; }
120+ if (line[0] == 'B' && !started && line[1] == ' ' && line[2] >= '0' && line[2] <= '3' && line[3] == ' ') {
121+ unsigned p = line[2] - '0', size;
122+ if (!length(line + 4, &size)) { microkit_dbg_puts("@error field too long\n"); return; }
123+ for (unsigned b = 0; b < BYTES; ++b) frame(p)[b] = 0;
124+ for (unsigned i = 0; i < size; ++i) frame(p)[i] = line[4 + i];
125+ done();
126+ } else if (line[0] == 'S' && !line[1] && !started) {
127+ started = 1;
128+ done();
129+ } else if (!started) {
130+ microkit_dbg_puts("@error document not opened\n");
131+ } else if (line[0] == 'E' && line[1] == ' ' && line[2] >= '0' && line[2] <= '1' && line[3] == ' ' && line[4] >= '0' && line[4] <= '3' && line[5] == ' ') {
132+ unsigned c = line[2] - '0', p = line[4] - '0', size;
133+ if (!length(line + 6, &size)) { microkit_dbg_puts("@error field too long\n"); return; }
134+ volatile struct job *job = (void *)(TASK + c * BYTES);
135+ job->page = p;
136+ for (unsigned i = 0; i < size; ++i) job->text[i] = line[6 + i];
137+ job->text[size] = 0;
138+ job->length = size;
139+ busy = c;
140+ microkit_notify(c);
141+ } else if (line[0] == 'Q' && line[1] == ' ' && line[2] >= '0' && line[2] <= '2' && !line[3]) {
142+ unsigned c = line[2] - '0';
143+ for (unsigned p = 0; p < PAGES; ++p) {
144+ unsigned k = c * PAGES + p, f = c == 2 ? p : mapping[k];
145+ if (c < CLIENTS) {
146+ for (unsigned b = 0; b <= LIMIT; ++b) {
147+ microkit_dbg_puts("SPLIT R "); number(k);
148+ microkit_dbg_puts(" "); number(b);
149+ microkit_dbg_puts(" "); number(frame(f)[b]); microkit_dbg_puts("\n");
150+ if (!frame(f)[b]) break;
151+ }
152+ }
153+ microkit_dbg_puts("@field "); number(p); microkit_dbg_puts(" ");
154+ for (unsigned b = 0; b < LIMIT && frame(f)[b]; ++b) {
155+ char text[2] = {(char)frame(f)[b], 0}; microkit_dbg_puts(text);
156+ }
157+ microkit_dbg_puts("\n");
158+ }
159+ done();
160+ } else if (line[0] == 'P' && !line[1]) {
161+ for (unsigned k = 0; k < SLOTS; ++k) {
162+ microkit_dbg_puts("@map "); number(k);
163+ microkit_dbg_puts(" "); number(BASE + (k % PAGES) * BYTES);
164+ microkit_dbg_puts(" "); number(physical(caps[k]));
165+ microkit_dbg_puts(" "); number(mapping[k] >= PAGES); microkit_dbg_puts("\n");
166+ }
167+ done();
168+ } else if (line[0] == 'D' && line[1] == ' ' && line[2] >= '0' && line[2] <= '1' && !line[3]) {
169+ unsigned c = line[2] - '0';
170+ require(seL4_TCB_Suspend(BASE_TCB_CAP + c) == seL4_NoError, "worker suspend");
171+ microkit_dbg_puts("SPLIT STOP "); number(c); microkit_dbg_puts("\n");
172+ for (unsigned p = 0; p < PAGES; ++p) {
173+ unsigned k = c * PAGES + p, f = mapping[k];
174+ if (f < PAGES) continue;
175+ seL4_Word old = physical(caps[k]);
176+ require(seL4_ARM_Page_Unmap(caps[k]) == seL4_NoError, "private page unmap");
177+ caps[k] = microkit_cspace_root_slot_to_cptr(10 + k);
178+ map(k, caps[k], 0);
179+ mapping[k] = p;
180+ volatile unsigned char *bytes = frame(f);
181+ for (unsigned b = 0; b < BYTES; ++b) bytes[b] = 0;
182+ unsigned cleared = 0;
183+ for (unsigned b = 0; b < BYTES; ++b) cleared += bytes[b] == 0;
184+ require(cleared == BYTES && returned < FRAMES - PAGES, "frame scrub");
185+ available[returned++] = f;
186+ microkit_dbg_puts("SPLIT RELEASE "); number(k);
187+ microkit_dbg_puts(" "); number(f);
188+ microkit_dbg_puts(" "); number(old);
189+ microkit_dbg_puts(" "); number(physical(caps[k]));
190+ microkit_dbg_puts(" "); number(cleared); microkit_dbg_puts("\n");
191+ }
192+ table();
193+ require(seL4_TCB_Resume(BASE_TCB_CAP + c) == seL4_NoError, "worker resume");
194+ microkit_dbg_puts("SPLIT RUN "); number(c); microkit_dbg_puts("\n");
195+ done();
196+ } else if (line[0] == 'F' && !line[1]) {
197+ for (unsigned i = returned; i > 0; --i) {
198+ unsigned f = available[i - 1], cleared = 0;
199+ volatile unsigned char *bytes = frame(f);
200+ for (unsigned b = 0; b < BYTES; ++b) cleared += bytes[b] == 0;
201+ microkit_dbg_puts("@free "); number(f);
202+ microkit_dbg_puts(" "); number(physical(microkit_cspace_root_slot_to_cptr(30 + f - PAGES)));
203+ microkit_dbg_puts(" "); number(cleared); microkit_dbg_puts("\n");
204+ for (unsigned b = 0; b < 32; ++b) {
205+ microkit_dbg_puts("@zero "); number(f);
206+ microkit_dbg_puts(" "); number(b);
207+ microkit_dbg_puts(" "); number(bytes[b]); microkit_dbg_puts("\n");
208+ }
209+ }
210+ done();
211+ } else if (line[0] == 'H' && line[1] == ' ' && line[2] >= '0' && line[2] <= '2' && line[3] == ' ' && line[4] >= '0' && line[4] <= '3' && !line[5]) {
212+ unsigned c = line[2] - '0', p = line[4] - '0', k = c * PAGES + p;
213+ unsigned f = c == 2 ? p : mapping[k];
214+ seL4_CPtr cap = c == 2 ? microkit_cspace_root_slot_to_cptr(10 + p) : caps[k];
215+ microkit_dbg_puts("@addr "); number(c == 2 ? BACKING + p * BYTES : BASE + p * BYTES);
216+ microkit_dbg_puts(" "); number(physical(cap)); microkit_dbg_puts("\n");
217+ for (unsigned b = 0; b < 64; ++b) {
218+ if (c < CLIENTS) {
219+ microkit_dbg_puts("SPLIT R "); number(k);
220+ microkit_dbg_puts(" "); number(b);
221+ microkit_dbg_puts(" "); number(frame(f)[b]); microkit_dbg_puts("\n");
222+ }
223+ microkit_dbg_puts("@byte "); number(b);
224+ microkit_dbg_puts(" "); number(frame(f)[b]); microkit_dbg_puts("\n");
225+ }
226+ done();
227+ } else if (line[0] == 'M' && !line[1]) {
228+ table();
229+ done();
230+ } else {
231+ microkit_dbg_puts("@error unknown request\n");
232+ }
233+}
234+
235+void notified(microkit_channel channel) {
236+ require(channel == 5, "unknown interrupt");
237+ uart[0x44 / 4] = 0x7ff;
238+ while (!(uart[0x18 / 4] & 0x10)) {
239+ unsigned ch = uart[0] & 255;
240+ if (ch == '\n') {
241+ line[used] = 0;
242+ request();
243+ used = 0;
244+ } else if (ch >= 32 && ch < 127 && used < sizeof(line) - 1) {
245+ line[used++] = ch;
246+ }
247+ }
248+ microkit_irq_ack(5);
249+}

setup.sh added

@@ -0,0 +1,17 @@
1+#!/bin/sh
2+set -eu
3+cache="${SPLIT_CACHE:-$HOME/.cache/split}"
4+mkdir -p "$cache"
5+if [ ! -f "$cache/sdk.tar.gz" ]; then
6+ curl -fL --retry 2 https://github.com/seL4/microkit/releases/download/2.3.1/microkit-sdk-2.3.1-linux-x86-64.tar.gz -o "$cache/sdk.tar.gz"
7+fi
8+if [ ! -f "$cache/source.tar.gz" ]; then
9+ curl -fL --retry 2 https://codeload.github.com/seL4/microkit/tar.gz/refs/tags/2.3.1 -o "$cache/source.tar.gz"
10+fi
11+printf '%s %s\n' b1b285b8785db7557eba6d0665dd4fbf2f265ea5c0b39d6f91ae3dcc43d2c424 "$cache/sdk.tar.gz" 67ae034628465c35169c398dd7d9ea61dbe8bd93c7a9adb05b9bb31bae25c22f "$cache/source.tar.gz" | sha256sum -c -
12+if [ ! -d "$cache/microkit-sdk-2.3.1" ]; then
13+ tar -xzf "$cache/sdk.tar.gz" -C "$cache"
14+fi
15+tar -xzf "$cache/source.tar.gz" -C "$cache"
16+patch -s -p1 -d "$cache/microkit-2.3.1" < "$(dirname "$0")/microkit.patch"
17+cargo build --release --locked -p microkit-tool --manifest-path "$cache/microkit-2.3.1/Cargo.toml"

split.h added

@@ -0,0 +1,43 @@
1+#ifndef SPLIT_H
2+#define SPLIT_H
3+#include <microkit.h>
4+#include <stdint.h>
5+
6+#define PAGES 4u
7+#define CLIENTS 2u
8+#define SLOTS (PAGES * CLIENTS)
9+#define FRAMES (PAGES + SLOTS)
10+#define BYTES 4096u
11+#define BASE 0x40000000ul
12+#define BACKING 0x60000000ul
13+#define TASK 0x50000000ul
14+#define LIMIT 255u
15+
16+struct job {
17+ unsigned page, length;
18+ char text[LIMIT + 1];
19+};
20+
21+static inline void number(unsigned long n) {
22+ char s[24];
23+ unsigned i = sizeof(s) - 1;
24+ s[i] = 0;
25+ do {
26+ s[--i] = '0' + n % 10;
27+ n /= 10;
28+ } while (n);
29+ microkit_dbg_puts(s + i);
30+}
31+
32+static inline unsigned char seed(unsigned page, unsigned offset) {
33+ return (page * 17u + offset) % 256u;
34+}
35+
36+static inline void require(int ok, const char *message) {
37+ if (ok) return;
38+ microkit_dbg_puts("@error ");
39+ microkit_dbg_puts(message);
40+ microkit_dbg_puts("\n");
41+ for (;;) seL4_Yield();
42+}
43+#endif

split.system added

@@ -0,0 +1,76 @@
1+<system>
2+ <memory_region name="frame0" size="0x1000" page_size="0x1000" />
3+ <memory_region name="frame1" size="0x1000" page_size="0x1000" />
4+ <memory_region name="frame2" size="0x1000" page_size="0x1000" />
5+ <memory_region name="frame3" size="0x1000" page_size="0x1000" />
6+ <memory_region name="frame4" size="0x1000" page_size="0x1000" />
7+ <memory_region name="frame5" size="0x1000" page_size="0x1000" />
8+ <memory_region name="frame6" size="0x1000" page_size="0x1000" />
9+ <memory_region name="frame7" size="0x1000" page_size="0x1000" />
10+ <memory_region name="frame8" size="0x1000" page_size="0x1000" />
11+ <memory_region name="frame9" size="0x1000" page_size="0x1000" />
12+ <memory_region name="frame10" size="0x1000" page_size="0x1000" />
13+ <memory_region name="frame11" size="0x1000" page_size="0x1000" />
14+ <memory_region name="reserve0" size="0x1000" page_size="0x1000" />
15+ <memory_region name="reserve1" size="0x1000" page_size="0x1000" />
16+ <protection_domain name="pager" priority="254">
17+ <program_image path="pager.elf" />
18+ <map mr="frame0" vaddr="0x60000000" perms="rw" />
19+ <map mr="frame1" vaddr="0x60001000" perms="rw" />
20+ <map mr="frame2" vaddr="0x60002000" perms="rw" />
21+ <map mr="frame3" vaddr="0x60003000" perms="rw" />
22+ <map mr="frame4" vaddr="0x60004000" perms="rw" />
23+ <map mr="frame5" vaddr="0x60005000" perms="rw" />
24+ <map mr="frame6" vaddr="0x60006000" perms="rw" />
25+ <map mr="frame7" vaddr="0x60007000" perms="rw" />
26+ <map mr="frame8" vaddr="0x60008000" perms="rw" />
27+ <map mr="frame9" vaddr="0x60009000" perms="rw" />
28+ <map mr="frame10" vaddr="0x6000a000" perms="rw" />
29+ <map mr="frame11" vaddr="0x6000b000" perms="rw" />
30+ <cspace>
31+ <cap_vspace slot="1" pd="alpha" />
32+ <cap_frame slot="10" mr="frame0" />
33+ <cap_frame slot="11" mr="frame1" />
34+ <cap_frame slot="12" mr="frame2" />
35+ <cap_frame slot="13" mr="frame3" />
36+ <cap_vspace slot="2" pd="beta" />
37+ <cap_frame slot="14" mr="frame0" />
38+ <cap_frame slot="15" mr="frame1" />
39+ <cap_frame slot="16" mr="frame2" />
40+ <cap_frame slot="17" mr="frame3" />
41+ <cap_frame slot="30" mr="frame4" />
42+ <cap_frame slot="31" mr="frame5" />
43+ <cap_frame slot="32" mr="frame6" />
44+ <cap_frame slot="33" mr="frame7" />
45+ <cap_frame slot="34" mr="frame8" />
46+ <cap_frame slot="35" mr="frame9" />
47+ <cap_frame slot="36" mr="frame10" />
48+ <cap_frame slot="37" mr="frame11" />
49+ </cspace>
50+ <protection_domain name="alpha" priority="100" id="0">
51+ <program_image path="alpha.elf" />
52+ <map mr="reserve0" vaddr="0x40010000" perms="r" />
53+ <map mr="task0" vaddr="0x50000000" perms="r" />
54+ </protection_domain>
55+ <protection_domain name="beta" priority="99" id="1">
56+ <program_image path="beta.elf" />
57+ <map mr="reserve1" vaddr="0x40010000" perms="r" />
58+ <map mr="task1" vaddr="0x50000000" perms="r" />
59+ </protection_domain>
60+ <map mr="uart" vaddr="0x70000000" perms="rw" cached="false" />
61+ <irq irq="33" id="5" trigger="level" />
62+ <map mr="task0" vaddr="0x50000000" perms="rw" />
63+ <map mr="task1" vaddr="0x50001000" perms="rw" />
64+ </protection_domain>
65+ <channel>
66+ <end pd="pager" id="0" />
67+ <end pd="alpha" id="0" pp="true" />
68+ </channel>
69+ <channel>
70+ <end pd="pager" id="1" />
71+ <end pd="beta" id="0" pp="true" />
72+ </channel>
73+ <memory_region name="uart" size="0x1000" page_size="0x1000" phys_addr="0x09000000" />
74+ <memory_region name="task0" size="0x1000" page_size="0x1000" />
75+ <memory_region name="task1" size="0x1000" page_size="0x1000" />
76+</system>