Compare commits

..

54 Commits

Author SHA1 Message Date
1401af3d82 feat(pointer): virtio tablet as 2nd input dev (driver per-instance VAs, abs/btn decode), input server serves 2 devices on one notif; display software cursor, bar click switches window, presses/releases to front window in its px; k-ui hit-tests (press focuses, release on same presses, field only focuses) 2026-10-28 16:50:20 +01:00
0de13f124d fix: pm running apps grow (death-bit bound 60); spawntest stress: 30 files open, apps until BUSY then room comes back; docs: limits and why 2026-10-28 16:22:19 +01:00
1f2134fcec fix: storage tables grow (handles, open files, encrypted opens, grants: Slots/Vec, no 8/16 caps; release follows all narrowed grants); storage says FULL, init spawn STORAGE_BUSY (was NOT_FOUND), pm BUSY; cap slots per process 64 -> 255 2026-10-28 16:18:15 +01:00
3b71f29f4c fix: capacities single-sourced in types (display MAX_WINDOWS/BUFFER_PAGES, storage MAX_CLIENTS), display + storage budgets derived from them, const asserts on VA layouts; kernel regions 64 -> 256 2026-10-28 16:06:55 +01:00
495bf03484 fix: stacks at top of user VA in own 1 MiB band (guard, heap kept below), 8/16 pages default; kernel fault msg names pid, kind, flags stack overflow; flaky OVERFLOW + spawntest/test-qemu check it 2026-10-28 16:03:06 +01:00
18e8b3ecb8 feat(desktop): home app (system-only launch cap, LAUNCH_BADGE: apps+launch only), pm starts it with session (relaunch <=5); display keys F1/Alt+Tab next, F2 home, F4 close app (ROLE from launcher, terminal exempt), close returns to previous window; closed window -> app exits; shell 'name &'; launch by id; pm stack 8, storage 16 clients 2026-10-28 15:50:05 +01:00
c3eb70191b feat: room for many apps: qemu 512M, pool sized from RAM (448M, 114688 frames), user VA 32M -> 1G, display 8 windows (buffers at 0x1000_0000, stack 14 pages), pm app budget 24 MiB, init 16 children (notif bits moved) 2026-10-28 15:31:30 +01:00
8c518b84aa refactor(k-ui): retire glass look (wallpaper, blur, shadow, gloss): flat surfaces; theme drops [wallpaper]/[glass] tokens + orb contrast checks; light accent back to #0b6bcb; settings/tally 3 MiB 2026-10-28 15:09:38 +01:00
7804934008 perf(k-ui): own pixel loops (pixels.rs): float only in corners, SWAR blends, banded shadow; screen fade only over content toward wallpaper (was full-window tiny-skia fill, ~600ms); blur at quarter res, row/column-sum passes, SWAR 2x upscale; ring frame ~75 -> ~20ms 2026-10-28 15:03:19 +01:00
815a2f9fe2 perf(display): present copies window rows as-is (scanout R8G8B8X8 = window byte order, Host hands out row bytes, Framebuffer::write_rgba_row); Window copy_from/copy_to one fenced memcpy; full present ~65ms -> ~3ms 2026-10-28 14:47:06 +01:00
d26b265d38 feat(k-ui): glass look: orb wallpaper + blurred, tinted glass panels (half-res backdrop per theme+size, integer code, frames only copy), soft analytic shadow, top gloss; contrast checked over wallpaper/glass per orb (light accent -> #0a5cb0); partial frames drawn in place; Rect geometry, tree lookups split out; settings/tally 6 MiB 2026-10-28 14:19:54 +01:00
3b71243dea feat(k-ui): motion: ring springs to focus, toggle knob slides, screens slide+fade in (theme motion tokens, 0 = off); 16ms frame timer only while moving; motion-only frames drawn+presented in part (whole elements, pixel-snapped rects, == full draw); virtio fb flushes only damage; TICKS_PER_MS in types 2026-10-28 13:58:38 +01:00
e952a149ec feat(k-ui): text_input (label above, app owns text via on_change, caret, erase, submit, secret, placeholder); keys Left/Right/Space/Char/Erase; settings app filter; apps 8-page stack (k-ui overflowed 16K), args page -> 0x1000 2026-10-28 13:48:08 +01:00
2b82f052ae feat(theme): per-app accent: pm gives window manifest accent before app runs (display ACCENT, refused after BUFFER); theme::with_accent keeps hue, shifts lightness until contrast passes, else theme accent; tally pink 2026-10-28 13:40:38 +01:00
6827e1455d feat(theme): themes as files (theme crate: toml subset + ints, contrast check, wire); whimsy dark + light; display reads themes/, serves THEME/THEME_NAME, init SET_THEME -> THEME_CHANGED to all windows, bar themed; k-ui follows; settings appearance; init keeps choice in sealed users/<id>/settings/, reapplies at sign-in + display restart 2026-10-28 13:36:57 +01:00
29becf297c feat(settings): settings app: apps list + per-capability allow/revoke, about (who/role), sign out; system-only caps permissions/session (install refused); pm MANAGE_BADGE copy (list/apps/allow/revoke only), APPS op; init WHO + session pass-on; factory decisions seeded for new system apps; k-ui list_item/toggle, screens/back/scroll 2026-10-28 13:14:35 +01:00
a2d4a7d085 perf(kernel): kernel static all-zero -> .bss (image 812K -> 173K): domain::Slot (repr(u8) Empty=0) for tables, zero generation counters, core ids by position, pool/platform installed at boot; Option-backed Slot under kani (cbmc speed); build fails if .data > 16K 2026-10-28 13:01:52 +01:00
419b715a1f fix(libkos): heap leaked padding/remainders + never coalesced (UI churn OOM'd tally); address-ordered free list, merge on free, split align padding; Heap<Pages> host-tested 2026-10-28 13:01:52 +01:00
fe73b89525 feat(k-ui): first slice: App trait + run loop, builders (column/row/text/button), flex layout, theme tokens (Whimsy Dark), tiny-skia + fontdue render (Noto Sans Latin subset), keyboard focus ring; tally demo; manifest memory key (pm spawns with it) 2026-10-28 03:20:41 +01:00
c256f4ecf3 feat(kernel): lazy user FPU: off until first FP insn (illegal-insn trap -> zeroed regs, retry), save on dirty trap, restore on switch, off for threads without + kernel; spawntest per-thread FP check 2026-10-28 03:20:41 +01:00
34a44b5928 fix(libkos): as_chunks for canvas pixels (clippy) 2026-10-28 02:57:21 +01:00
07a0575908 feat(init,display): loader streams ELF segments (any size, kills half-built proc); window pixels RGBA8 premul (tiny-skia native), canvas mapped plain memory + pixels_mut/touch 2026-10-28 02:57:05 +01:00
864f039bc7 feat(display): windows: full-screen, F1 cycles, OS bar (names, front, user); canvas-only (display-owned buffers, 4 max, reused); launchers OPEN (pm per app, terminal own), window caps minted by display; FORGET on launcher death via init courier; raster crate (font, chunked text); F-keys in input; terminal+snake on canvas; REDRAW gone 2026-10-28 02:48:39 +01:00
d04dcfffa1 feat(ipc): saved replies: SaveReply/ReplySaved (8 per thread, released on thread death, unforgeable); terminal READ saved until Enter (line.rs editor, reader queue), now pinged 2026-10-28 02:09:34 +01:00
3d69c10079 feat(init): watchdog: pinger thread PINGs servers (framework answers, no server code), main kills after 5s unanswered -> crash path; not pinged: terminal, pm (handlers wait for user); give-up drains queued ping; spawntest hang check 2026-10-28 01:51:39 +01:00
28f1ace00a feat(init): supervise servers+session progs: restart on death, same endpoint, backoff 0/1/5/30s then give up, restarts_clients cascade; release grants/spawn at stop (stale-slot safe); storage death fatal; init output via printer thread (never blocks on log); terminal Ctrl+C via CONTROL-badged copy, latest wins; flaky test server + spawntest restart/backoff check 2026-10-28 01:44:24 +01:00
c4871320a0 fix(clippy): make clippy = host libs + riscv programs (shared PROGRAMS); libkos alloc spinlock (was unlocked across threads); keystore Account; gpu Rect; factory Entry; Default impls, let-chains, div_ceil, iter loops 2026-10-28 01:17:21 +01:00
91f7d28834 feat(users): per-user sealed folders users/<id>/{home,apps,data,permissions}; storage SET_USER/LOCK/PURGE; init sign-in/sign-out loop (purge fresh/removed/guest, seed from factory, session start/stop); shell logout; pm user+system app shelves; terminal SIGNED_OUT; per-program stack (gatekeeper 12p); 128K load scratch 2026-10-28 01:08:50 +01:00
29a25bd8c2 feat(users): keystore v2 (8 slots, per-user salt+name tag, owner admin key opens sealed user dir, per-user duress); gatekeeper login by name+passcode, owner menu (add/remove/guests), passcode change, guest sessions 2026-10-28 00:29:07 +01:00
9dc751c466 feat(storage): per-app/owner/system encryption live: gatekeeper hands seed + fresh flag, storage unlocks layer, init seeds areas from factory/ (empties on new key); rmdir op; test-qemu checks sealed home + reboot persistence 2026-10-28 00:07:41 +01:00
e8191be160 feat(storage): encrypted areas layer (home, system, data/<id>) over any FS; locked until key; plaintext refused; tamper/truncation detected; tests on memory + real ext4 2026-10-27 23:55:35 +01:00
eb9e3efaac feat(ext4): seek, rmdir (e2fsck-clean) 2026-10-27 23:53:13 +01:00
24b0734c73 feat(filecrypt): per-area keys (BLAKE2s), per-file salt, XChaCha20-Poly1305 chunks bound to index+last, seed DRBG w/ fast key erasure 2026-10-27 23:51:29 +01:00
3b7d004285 feat(boot): gatekeeper gates boot (init→UI→gatekeeper→data); keystore disk + virtio-rng discovery; terminal READ_SECRET; storage SET_KEY; PageCount ≤8192, alloc cap gone; device window 0x01D0_0000; test-qemu types passcode over 2 boots 2026-10-27 23:44:05 +01:00
b7ef67ffd8 feat(gatekeeper): auth flow (setup, count-before-check, threshold/duress wipe, torn-write-safe resume) over Keystore/Random/Prompt ports; host tests 2026-10-27 23:17:18 +01:00
600140d518 feat(keystore): sealed real/duress slots (argon2id 16MiB/3, ChaCha20-Poly1305), torn-write-safe 2-copy record, wipe state; virtio-rng driver + Entropy port; RustCrypto deps 2026-10-27 23:06:25 +01:00
6b13bf8bbf feat(kernel): 64 MiB user pool (frame table 16384) 2026-10-27 23:03:19 +01:00
90e11eb2e8 feat(apps): permission manager equips apps (manifest+consent+decisions in /system), launches via init w/ file grant; shell unprivileged (/home); install/apps/allow/revoke; revoke stops running app; app-image tool; counter app 2026-10-27 22:56:44 +01:00
9bc31a9bbe feat(manifest): app manifest + decision crate (strict TOML subset, checked newtypes, capability catalog); docs/apps.md spec 2026-10-27 22:32:40 +01:00
ec3bc0d2a1 feat(snake): 8 steps/s on fixed-period clock (draw time no longer adds to step) 2026-10-27 22:14:35 +01:00
c8056ddac6 perf: fast display (row runs, bulk fills, pixel scroll), terminal draws changed cells only + scrolls pixels, print! batched (was IPC per fmt fragment), snake sleeps on timer 2026-10-27 22:09:33 +01:00
7b8258b231 refactor: ByteOffset newtype for window/register/ring offsets; named virtio-gpu cmd fields 2026-10-27 21:52:33 +01:00
842d14987c refactor: unsafe only in adapter + libkos; forbid(unsafe_code) everywhere else; libkos Window (volatile, bounds-checked MMIO/DMA/shared mem), entry!, thread::spawn; MapRegion returns pages; kernel const-built (with_cores/install_platform); newtype addrs 2026-10-27 21:43:24 +01:00
83809bbe94 feat(userspace): programs in /bin w/o .elf; bin manifests (needs = display) so shell runs snake; Ctrl+C kills foreground via terminal interrupt notification; ctrl keys; sleep 2026-10-27 21:16:00 +01:00
1fc5a54f06 feat(storage): scoped grants (subtree, ro/rw), narrow-only, release revokes all holders; IPC KEEP_DELEGATE opt-in; shell cd/pwd, programs get ro cwd 2026-10-27 20:20:34 +01:00
c603a578c0 feat(userspace): shell runs programs (ls, cat) via spawn w/ terminal+storage+args page; ext4 holes read as zeros; storage re-attach same buffer 2026-10-27 17:22:08 +01:00
0fd568cdad feat(storage): ext4-only disk via mkfs.ext4 -d; drop FAT16; feature-gated mount (ro on unknown ro_compat); ext4 read spans blocks, delete clears inode, mkdir counts dir; virtio-blk writes; test-qemu e2fsck gate 2026-10-27 17:09:35 +01:00
4030dd4315 feat(adapter): init+storage packed into kernel image; drop adapter FAT16 reader + virtio-blk 2026-10-27 16:58:11 +01:00
b80303f553 feat(userspace): terminal server + shell desktop; US keymap w/ shift; libkos allocator multi-page fix; panics print 2026-10-27 16:50:35 +01:00
2845d190f0 feat(display): input routed via display server to focused app; input Subscribe; uses caps badged + Delegate 2026-10-27 16:38:16 +01:00
64ec5df379 feat(display): DrawText with built-in Spleen 8x16 font (BSD-2) 2026-10-27 16:33:26 +01:00
8587661684 feat(init): spawn service over IPC with per-client allowance refunded on child death; up to 4 death watchers; spawntest on test image 2026-10-27 16:21:08 +01:00
817a38993b feat(kernel): WatchDeath + SetTimer events as notification signals; adapter wakes at earliest deadline 2026-10-27 16:06:04 +01:00
a4f467f45d feat(userspace): log server sole console holder; init boots from manifest; adapter loads only init+storage 2026-10-27 15:40:19 +01:00
348 changed files with 22265 additions and 3347 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
/target /target
.DS_Store .DS_Store
*.img *.img

508
Cargo.lock generated
View File

@@ -12,16 +12,61 @@ version = "0.1.0"
dependencies = [ dependencies = [
"domain", "domain",
"elf", "elf",
"hw-virtio",
"kernel", "kernel",
"platform", "platform",
"types", "types",
] ]
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]] [[package]]
name = "alarm-server" name = "alarm-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "app-image"
version = "0.1.0"
dependencies = [
"manifest",
]
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
]
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]] [[package]]
name = "audio-capture-server" name = "audio-capture-server"
version = "0.1.0" version = "0.1.0"
@@ -30,10 +75,40 @@ version = "0.1.0"
name = "audio-playback-server" name = "audio-playback-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bluetooth-server" name = "bluetooth-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]] [[package]]
name = "calendar-server" name = "calendar-server"
version = "0.1.0" version = "0.1.0"
@@ -42,6 +117,55 @@ version = "0.1.0"
name = "camera-server" name = "camera-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "cat"
version = "0.1.0"
dependencies = [
"libkos",
"types",
]
[[package]]
name = "cfg-if"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher",
"poly1305",
"zeroize",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]] [[package]]
name = "clipboard-server" name = "clipboard-server"
version = "0.1.0" version = "0.1.0"
@@ -50,6 +174,53 @@ version = "0.1.0"
name = "contacts-server" name = "contacts-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "core_maths"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
dependencies = [
"libm",
]
[[package]]
name = "counter"
version = "0.1.0"
dependencies = [
"libkos",
"types",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]] [[package]]
name = "display-qemu" name = "display-qemu"
version = "0.1.0" version = "0.1.0"
@@ -67,8 +238,11 @@ name = "display-server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"driver-framebuffer-mock", "driver-framebuffer-mock",
"libkos",
"port-framebuffer", "port-framebuffer",
"raster",
"server-framework", "server-framework",
"theme",
"types", "types",
] ]
@@ -126,6 +300,16 @@ dependencies = [
"types", "types",
] ]
[[package]]
name = "driver-rng-virtio"
version = "0.1.0"
dependencies = [
"hw-virtio",
"libkos",
"port-entropy",
"types",
]
[[package]] [[package]]
name = "echo" name = "echo"
version = "0.1.0" version = "0.1.0"
@@ -138,14 +322,86 @@ dependencies = [
name = "elf" name = "elf"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "equivalent"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
[[package]]
name = "filecrypt"
version = "0.1.0"
dependencies = [
"blake2",
"chacha20",
"chacha20poly1305",
"zeroize",
]
[[package]]
name = "flaky"
version = "0.1.0"
dependencies = [
"libkos",
"server-framework",
"types",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "fontdue"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e796cb6894e6418daacea84b28dd4f98d3b32c74d5bb452ba1ecfbf5ad8e84ca"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]] [[package]]
name = "gatekeeper" name = "gatekeeper"
version = "0.1.0" version = "0.1.0"
dependencies = [
"driver-block-virtio",
"driver-rng-virtio",
"keystore",
"libkos",
"port-block-device",
"port-entropy",
"types",
"zeroize",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "gps-server" name = "gps-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]] [[package]]
name = "hello" name = "hello"
version = "0.1.0" version = "0.1.0"
@@ -153,10 +409,21 @@ dependencies = [
"libkos", "libkos",
] ]
[[package]]
name = "home"
version = "0.1.0"
dependencies = [
"k-ui",
"libkos",
"manifest",
"types",
]
[[package]] [[package]]
name = "hw-virtio" name = "hw-virtio"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"libkos",
"types", "types",
] ]
@@ -167,9 +434,19 @@ dependencies = [
"elf", "elf",
"hw-virtio", "hw-virtio",
"libkos", "libkos",
"server-framework",
"types", "types",
] ]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "input-qemu" name = "input-qemu"
version = "0.1.0" version = "0.1.0"
@@ -193,6 +470,19 @@ dependencies = [
"types", "types",
] ]
[[package]]
name = "k-ui"
version = "0.1.0"
dependencies = [
"fontdue",
"libkos",
"libm",
"port-framebuffer",
"theme",
"tiny-skia",
"types",
]
[[package]] [[package]]
name = "kernel" name = "kernel"
version = "0.1.0" version = "0.1.0"
@@ -206,13 +496,42 @@ dependencies = [
name = "keychain-server" name = "keychain-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "keystore"
version = "0.1.0"
dependencies = [
"argon2",
"blake2",
"chacha20poly1305",
"zeroize",
]
[[package]]
name = "libc"
version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]] [[package]]
name = "libkos" name = "libkos"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"port-framebuffer",
"types", "types",
] ]
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]] [[package]]
name = "log-server" name = "log-server"
version = "0.1.0" version = "0.1.0"
@@ -222,6 +541,18 @@ dependencies = [
"types", "types",
] ]
[[package]]
name = "ls"
version = "0.1.0"
dependencies = [
"libkos",
"types",
]
[[package]]
name = "manifest"
version = "0.1.0"
[[package]] [[package]]
name = "media-index-server" name = "media-index-server"
version = "0.1.0" version = "0.1.0"
@@ -234,6 +565,12 @@ version = "0.1.0"
name = "notification-server" name = "notification-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]] [[package]]
name = "package-manager" name = "package-manager"
version = "0.1.0" version = "0.1.0"
@@ -243,6 +580,7 @@ name = "permission-manager"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"libkos", "libkos",
"manifest",
"server-framework", "server-framework",
"types", "types",
] ]
@@ -254,10 +592,25 @@ dependencies = [
"types", "types",
] ]
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]] [[package]]
name = "port-block-device" name = "port-block-device"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "port-entropy"
version = "0.1.0"
[[package]] [[package]]
name = "port-framebuffer" name = "port-framebuffer"
version = "0.1.0" version = "0.1.0"
@@ -277,6 +630,15 @@ version = "0.1.0"
name = "print-server" name = "print-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "raster"
version = "0.1.0"
dependencies = [
"driver-framebuffer-mock",
"port-framebuffer",
"types",
]
[[package]] [[package]]
name = "server-framework" name = "server-framework"
version = "0.1.0" version = "0.1.0"
@@ -294,6 +656,33 @@ dependencies = [
"types", "types",
] ]
[[package]]
name = "settings"
version = "0.1.0"
dependencies = [
"k-ui",
"libkos",
"manifest",
"types",
]
[[package]]
name = "shell"
version = "0.1.0"
dependencies = [
"libkos",
"manifest",
"types",
]
[[package]]
name = "sleep"
version = "0.1.0"
dependencies = [
"libkos",
"types",
]
[[package]] [[package]]
name = "smptest" name = "smptest"
version = "0.1.0" version = "0.1.0"
@@ -306,9 +695,18 @@ dependencies = [
name = "snake" name = "snake"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"display-server",
"libkos", "libkos",
"server-framework", "port-framebuffer",
"types",
]
[[package]]
name = "spawntest"
version = "0.1.0"
dependencies = [
"flaky",
"libkos",
"manifest",
"types", "types",
] ]
@@ -325,24 +723,116 @@ name = "storage-server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"driver-block-virtio", "driver-block-virtio",
"filecrypt",
"libkos", "libkos",
"port-block-device", "port-block-device",
"server-framework", "server-framework",
"types", "types",
] ]
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "sync-server" name = "sync-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "tally"
version = "0.1.0"
dependencies = [
"k-ui",
"libkos",
]
[[package]] [[package]]
name = "telephony-server" name = "telephony-server"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "terminal-server"
version = "0.1.0"
dependencies = [
"libkos",
"port-framebuffer",
"raster",
"server-framework",
"types",
]
[[package]]
name = "theme"
version = "0.1.0"
dependencies = [
"libm",
"manifest",
"types",
]
[[package]]
name = "tiny-skia"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
dependencies = [
"arrayref",
"arrayvec",
"bytemuck",
"cfg-if",
"log",
"tiny-skia-path",
]
[[package]]
name = "tiny-skia-path"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
dependencies = [
"arrayref",
"bytemuck",
"libm",
"strict-num",
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
dependencies = [
"core_maths",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]] [[package]]
name = "types" name = "types"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle",
]
[[package]] [[package]]
name = "usb-server" name = "usb-server"
version = "0.1.0" version = "0.1.0"
@@ -350,3 +840,15 @@ version = "0.1.0"
[[package]] [[package]]
name = "user-manager" name = "user-manager"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "zeroize"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"

View File

@@ -1,12 +1,31 @@
[workspace] [workspace]
members = [ members = [
"crates/adapter-riscv", "crates/adapter-riscv",
"crates/raster",
"crates/k-ui",
"crates/apps/flaky",
"crates/apps/tally",
"crates/apps/settings",
"crates/apps/home",
"crates/theme",
"crates/apps/hello", "crates/apps/hello",
"crates/apps/shell",
"crates/apps/cat",
"crates/apps/counter",
"crates/apps/ls",
"crates/apps/echo", "crates/apps/echo",
"crates/apps/sleep",
"crates/apps/smptest", "crates/apps/smptest",
"crates/apps/spawntest",
"crates/apps/snake", "crates/apps/snake",
"crates/domain", "crates/domain",
"crates/elf", "crates/elf",
"crates/manifest",
"crates/keystore",
"crates/filecrypt",
"crates/ports/entropy",
"crates/drivers/rng/virtio",
"crates/tools/app-image",
"crates/init", "crates/init",
"crates/kernel", "crates/kernel",
"crates/platform", "crates/platform",
@@ -34,6 +53,7 @@ members = [
"crates/servers/power-server", "crates/servers/power-server",
"crates/servers/print-server", "crates/servers/print-server",
"crates/servers/session-server", "crates/servers/session-server",
"crates/servers/terminal-server",
"crates/servers/storage-server", "crates/servers/storage-server",
"crates/servers/sync-server", "crates/servers/sync-server",
"crates/servers/telephony-server", "crates/servers/telephony-server",
@@ -62,6 +82,18 @@ resolver = "2"
types = { path = "crates/types" } types = { path = "crates/types" }
domain = { path = "crates/domain" } domain = { path = "crates/domain" }
elf = { path = "crates/elf" } elf = { path = "crates/elf" }
manifest = { path = "crates/manifest" }
theme = { path = "crates/theme" }
keystore = { path = "crates/keystore" }
filecrypt = { path = "crates/filecrypt" }
# Audited RustCrypto implementations; never our own.
argon2 = { version = "0.5", default-features = false }
chacha20poly1305 = { version = "0.10", default-features = false }
blake2 = { version = "0.10", default-features = false }
zeroize = { version = "1", default-features = false }
chacha20 = { version = "0.9", default-features = false }
port-entropy = { path = "crates/ports/entropy" }
driver-rng-virtio = { path = "crates/drivers/rng/virtio" }
platform = { path = "crates/platform" } platform = { path = "crates/platform" }
kernel = { path = "crates/kernel" } kernel = { path = "crates/kernel" }
server-framework = { path = "crates/server-framework" } server-framework = { path = "crates/server-framework" }
@@ -102,6 +134,8 @@ libkos = { path = "crates/libkos" }
# ports # ports
port-framebuffer = { path = "crates/ports/framebuffer" } port-framebuffer = { path = "crates/ports/framebuffer" }
raster = { path = "crates/raster" }
k-ui = { path = "crates/k-ui" }
port-block-device = { path = "crates/ports/block-device" } port-block-device = { path = "crates/ports/block-device" }
port-input-device = { path = "crates/ports/input-device" } port-input-device = { path = "crates/ports/input-device" }

View File

@@ -28,8 +28,11 @@ check:
fmt: fmt:
cargo fmt --all -- --check cargo fmt --all -- --check
# Libraries on the host; programs (no_std, they link only for RISC-V) for
# their target, as `build` builds them. The adapter is `kernel-clippy`'s.
clippy: clippy:
cargo clippy --workspace -- -D warnings cargo clippy --workspace --exclude adapter-riscv --lib -- -D warnings
cargo clippy --target $(RISCV_TARGET) --release $(PROGRAMS) -- -D warnings
test: test:
cargo test --workspace cargo test --workspace
@@ -48,36 +51,53 @@ kani-kernel:
kani: kani-types kani-domain kani-kernel kani: kani-types kani-domain kani-kernel
PROGRAMS = -p init -p echo -p sleep -p snake \
-p display-qemu -p input-qemu -p storage-qemu \
-p log-server -p permission-manager -p session-server -p smptest -p spawntest -p hello \
-p terminal-server -p shell -p cat -p ls -p counter -p gatekeeper -p flaky -p tally -p settings -p home
# Programs first: the kernel image packs init and storage (see the
# adapter's build.rs).
build: build:
cargo build --target $(RISCV_TARGET) --release \ cargo build --target $(RISCV_TARGET) --release $(PROGRAMS)
-p adapter-riscv -p init -p echo -p snake \ cargo build --target $(RISCV_TARGET) --release -p adapter-riscv
-p display-qemu -p input-qemu -p storage-qemu \ @# The kernel's tables are one static: if every empty entry is zero it
-p log-server -p permission-manager -p session-server -p smptest @# lives in .bss and costs nothing in the image (see domain::slot).
@data=$$(llvm-size -A $(KERNEL_BIN) | awk '$$1 == ".data" { print $$2 }'); \
if [ "$${data:-0}" -gt 16384 ]; then \
echo "kernel .data is $$data bytes: some table's empty value is not all zero bytes (use domain::slot::Slot)"; exit 1; \
fi
# The keystore holds the owner's sealed keys: made once, never rebuilt.
KEYS_IMG ?= keys.img
QEMU_KEYS = -drive file=$(KEYS_IMG),if=none,format=raw,id=ks -device virtio-blk-device,drive=ks \
-device virtio-rng-device
image: build image: build
scripts/make-disk-image.sh scripts/make-disk-image.sh
[ -f $(KEYS_IMG) ] || truncate -s 64K $(KEYS_IMG)
# Boots a test image (normal image + smptest.elf) on $(SMP) harts; init runs # Boots a test image (normal image + bin/smptest) on $(SMP) harts; init runs
# the SMP self-test and halts. Passes only on "SMPTEST PASS". # the SMP self-test and halts. Passes only on "SMPTEST PASS".
test-qemu: build test-qemu: build
IMG=test.img SMPTEST=1 scripts/make-disk-image.sh > /dev/null IMG=test.img SMPTEST=1 scripts/make-disk-image.sh > /dev/null
SMP=$(SMP) scripts/test-qemu.sh test.img SMP=$(SMP) scripts/test-qemu.sh test.img
run: build run: build
qemu-system-riscv64 -machine virt -smp $(SMP) -nographic -bios default \ qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -nographic -bios default \
-kernel $(KERNEL_BIN) -kernel $(KERNEL_BIN)
run-disk: image run-disk: image
qemu-system-riscv64 -machine virt -smp $(SMP) -nographic -bios default \ qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -nographic -bios default \
-kernel $(KERNEL_BIN) \ -kernel $(KERNEL_BIN) \
-drive file=boot.img,if=none,format=raw,id=hd0 \ -drive file=boot.img,if=none,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 -device virtio-blk-device,drive=hd0 $(QEMU_KEYS)
run-gui: image run-gui: image
qemu-system-riscv64 -machine virt -smp $(SMP) -bios default \ qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -bios default \
-kernel $(KERNEL_BIN) \ -kernel $(KERNEL_BIN) \
-drive file=boot.img,if=none,format=raw,id=hd0 \ -drive file=boot.img,if=none,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \ -device virtio-blk-device,drive=hd0 $(QEMU_KEYS) \
-device virtio-gpu-device \ -device virtio-gpu-device \
-device virtio-keyboard-device \ -device virtio-keyboard-device -device virtio-tablet-device \
-serial stdio -serial stdio

View File

@@ -14,7 +14,6 @@ domain = { workspace = true }
elf = { workspace = true } elf = { workspace = true }
platform = { workspace = true } platform = { workspace = true }
kernel = { workspace = true } kernel = { workspace = true }
hw-virtio = { workspace = true }
[profile.release] [profile.release]
panic = "abort" panic = "abort"

View File

@@ -1,5 +1,29 @@
use std::path::PathBuf;
fn main() { fn main() {
let ld = std::path::Path::new("linker.ld").canonicalize().unwrap(); let ld = std::path::Path::new("linker.ld").canonicalize().unwrap();
println!("cargo:rustc-link-arg=-T{}", ld.display()); println!("cargo:rustc-link-arg=-T{}", ld.display());
println!("cargo:rerun-if-changed=linker.ld"); println!("cargo:rerun-if-changed=linker.ld");
// Init and storage are packed into the kernel image: they are what
// reads the disk, so they cannot come from it. `make build` builds them
// first; without them the image boots to "init missing".
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let bins = manifest.join("../../target/riscv64gc-unknown-none-elf/release");
let out = PathBuf::from(std::env::var("OUT_DIR").unwrap());
for (var, bin) in [
("KOS_INIT_ELF", "init"),
("KOS_STORAGE_ELF", "storage-qemu"),
] {
let path = bins.join(bin);
println!("cargo:rerun-if-changed={}", path.display());
let path = if path.is_file() {
path
} else {
let empty = out.join(bin);
std::fs::write(&empty, []).unwrap();
empty
};
println!("cargo:rustc-env={}={}", var, path.display());
}
} }

View File

@@ -1,12 +1,14 @@
pub const RAM_BASE: usize = 0x80000000; pub const RAM_BASE: usize = 0x80000000;
pub const RAM_SIZE: usize = 128 * 1024 * 1024; /// What the Makefile and scripts give QEMU (`-m 512M`).
pub const RAM_SIZE: usize = 512 * 1024 * 1024;
pub const UART_BASE: usize = 0x10000000; pub const UART_BASE: usize = 0x10000000;
pub const VIRTIO_BASE: usize = 0x10001000; pub const VIRTIO_BASE: usize = 0x10001000;
pub const VIRTIO_SIZE: usize = 0x8000; pub const VIRTIO_SIZE: usize = 0x8000;
pub const PLIC_BASE: usize = 0x0C000000; pub const PLIC_BASE: usize = 0x0C000000;
pub const CLINT_BASE: usize = 0x02000000; pub const CLINT_BASE: usize = 0x02000000;
pub const USER_POOL_BASE: usize = 0x84000000; pub const USER_POOL_BASE: usize = 0x84000000;
pub const USER_POOL_PAGES: usize = 4096; /// The rest of RAM, from `USER_POOL_BASE` (448 MiB).
pub const USER_POOL_PAGES: usize = (RAM_BASE + RAM_SIZE - USER_POOL_BASE) / 4096;
pub const TICK_INTERVAL: u64 = 10_000_000; pub const TICK_INTERVAL: u64 = 10_000_000;
/// QEMU virt's PLIC lines. /// QEMU virt's PLIC lines.

View File

@@ -5,22 +5,20 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() } unsafe { &mut *crate::kernel_ptr() }
} }
pub fn grant_all( /// Init gets storage's process and endpoint (slots 1 and 2), storage its
uart: &Uart, /// endpoint (slot 0); everything else init starts itself.
init_pid: ProcessId, pub fn grant_all(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId) {
storage_pid: ProcessId,
display_pid: ProcessId,
input_pid: ProcessId,
) {
let all_perms = types::permission::PermissionSet::empty() let all_perms = types::permission::PermissionSet::empty()
.with(types::permission::Permission::Read) .with(types::permission::Permission::Read)
.with(types::permission::Permission::Write) .with(types::permission::Permission::Write)
.with(types::permission::Permission::Execute) .with(types::permission::Permission::Execute)
.with(types::permission::Permission::Delegate); .with(types::permission::Permission::Delegate);
// Delegate so storage can mint the badged copies it grants.
let ipc_perms = types::permission::PermissionSet::empty() let ipc_perms = types::permission::PermissionSet::empty()
.with(types::permission::Permission::Read) .with(types::permission::Permission::Read)
.with(types::permission::Permission::Execute); .with(types::permission::Permission::Execute)
.with(types::permission::Permission::Delegate);
let storage_ep = kern() let storage_ep = kern()
.create_endpoint(storage_pid) .create_endpoint(storage_pid)
@@ -49,74 +47,5 @@ pub fn grant_all(
) )
.unwrap_or_else(|_| crate::halt(uart, "grant failed")); .unwrap_or_else(|_| crate::halt(uart, "grant failed"));
let display_ep = kern()
.create_endpoint(display_pid)
.unwrap_or_else(|_| crate::halt(uart, "display endpoint failed"));
kern()
.grant_capability(
display_pid,
domain::resource::Resource::Endpoint(display_ep),
ipc_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Endpoint(display_ep),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Process(display_pid),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
let input_ep = kern()
.create_endpoint(input_pid)
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
kern()
.grant_capability(
input_pid,
domain::resource::Resource::Endpoint(input_ep),
ipc_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Endpoint(input_ep),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
kern()
.grant_capability(
init_pid,
domain::resource::Resource::Process(input_pid),
all_perms,
)
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
uart.write_str("k-os: capabilities granted\n"); uart.write_str("k-os: capabilities granted\n");
} }
/// Until the log server owns the console, the servers loaded here print
/// through a console cap of their own, its slot passed in `a0`.
pub fn grant_console(uart: &Uart, servers: &[super::loader::LoadedProcess]) {
let write =
types::permission::PermissionSet::empty().with(types::permission::Permission::Write);
for server in servers {
let slot = kern()
.grant_capability(server.pid, domain::resource::Resource::Console, write)
.unwrap_or_else(|_| crate::halt(uart, "console grant failed"));
if let Some(ctx) = kern().thread_context_mut(server.tid) {
ctx.registers_mut()[10] = types::start_arg::encode(Some(slot), None);
}
}
}

View File

@@ -1,6 +1,5 @@
use crate::hw::uart::Uart; use crate::hw::uart::Uart;
use crate::hw::virtio_blk::VirtioBlk; use crate::{board, mmu, platform, trap};
use crate::{KERNEL, board, mmu, platform, trap};
unsafe extern "C" { unsafe extern "C" {
static _bss_start: u8; static _bss_start: u8;
@@ -57,31 +56,15 @@ pub fn init(uart: &Uart, boot_hart: usize) -> &'static mut mmu::PageTable {
} }
let riscv_platform = let riscv_platform =
platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8); platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8);
// SAFETY: `init_in_place` writes every field of the kernel. crate::kern_mut().install_platform(riscv_platform);
unsafe { crate::kern_mut().install_pool(
KERNEL.init_with(|slot| { types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
kernel::Kernel::init_in_place( board::USER_POOL_PAGES,
slot, );
riscv_platform,
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
board::USER_POOL_PAGES,
)
});
}
kernel_pt kernel_pt
} }
pub fn probe_block_device(uart: &Uart) -> Option<VirtioBlk> {
let blk = VirtioBlk::probe();
if blk.is_some() {
uart.write_str("k-os: VirtIO block device found\n");
} else {
uart.write_str("k-os: no block device\n");
}
blk
}
fn zero_bss() { fn zero_bss() {
unsafe { unsafe {
let start = &_bss_start as *const u8 as *mut u8; let start = &_bss_start as *const u8 as *mut u8;

View File

@@ -1,6 +1,5 @@
use crate::hw::uart::Uart; use crate::hw::uart::Uart;
use crate::hw::virtio_blk::VirtioBlk; use crate::mmu;
use crate::{boot_fs, mmu};
use types::address::VirtualAddress; use types::address::VirtualAddress;
use types::budget::Limits; use types::budget::Limits;
use types::id::{ProcessId, ThreadId}; use types::id::{ProcessId, ThreadId};
@@ -10,35 +9,20 @@ const PAGE_SIZE: usize = mmu::PAGE_SIZE;
const USER_VA_BASE: usize = 0x10000; const USER_VA_BASE: usize = 0x10000;
/// The stack sits just below the image and stops short of the null page. /// The stack sits just below the image and stops short of the null page.
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1; const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
const MAX_FILE_SIZE: usize = 64 * 1024;
#[derive(Clone, Copy)]
pub struct LoadedProcess { pub struct LoadedProcess {
pub pid: ProcessId, pub pid: ProcessId,
pub tid: ThreadId,
} }
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> { fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() } unsafe { &mut *crate::kernel_ptr() }
} }
/// Scratch space for reading ELF files, allocated once from the boot heap. /// The two programs boot starts, packed into the kernel image.
fn elf_buffer() -> &'static mut [u8] { pub static INIT_ELF: &[u8] = include_bytes!(env!("KOS_INIT_ELF"));
static mut BUF: usize = 0; pub static STORAGE_ELF: &[u8] = include_bytes!(env!("KOS_STORAGE_ELF"));
// SAFETY: boot is single threaded; the buffer is kernel-only memory
// from the boot allocator and is never handed to a process.
unsafe {
if BUF == 0 {
BUF = mmu::allocator::alloc_zeroed_page();
for _ in 1..MAX_FILE_SIZE / PAGE_SIZE {
mmu::allocator::alloc_zeroed_page();
}
}
core::slice::from_raw_parts_mut(BUF as *mut u8, MAX_FILE_SIZE)
}
}
pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess { pub fn load_init(uart: &Uart) -> LoadedProcess {
let (pid, tid) = kern() let (pid, tid) = kern()
.boot( .boot(
types::bounded::Priority::DEFAULT, types::bounded::Priority::DEFAULT,
@@ -46,56 +30,40 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
) )
.unwrap_or_else(|_| crate::halt(uart, "boot failed")); .unwrap_or_else(|_| crate::halt(uart, "boot failed"));
let entry = load_elf(uart, blk, pid, b"init.elf"); let entry = load_elf(uart, pid, INIT_ELF);
setup_thread(tid, entry); setup_thread(tid, entry);
uart.write_str("k-os: init loaded\n"); uart.write_str("k-os: init loaded\n");
LoadedProcess { pid, tid } LoadedProcess { pid }
} }
/// The servers boot loads, their budgets carved from init's. /// Storage's budget, carved from init's.
pub const STORAGE: Limits = Limits { const STORAGE: Limits = {
pages: 256, use types::storage::{BUFFER_PAGES, MAX_CLIENTS};
endpoints: 2, Limits {
notifications: 2, // A buffer per client, and its image, heap and cache.
// A buffer per client (8) and the disk's DMA. pages: MAX_CLIENTS * BUFFER_PAGES + 256,
regions: 12, endpoints: 2,
}; notifications: 2,
/// The framebuffer alone is ~1000 pages at 1280x800. // A buffer per client, and the disk's DMA.
pub const DISPLAY: Limits = Limits { regions: MAX_CLIENTS + 4,
pages: 1280, }
endpoints: 2,
notifications: 2,
regions: 4,
};
pub const INPUT: Limits = Limits {
pages: 64,
endpoints: 2,
notifications: 2,
regions: 4,
}; };
pub fn load_server( /// Storage, which init needs to read anything else, its budget carved
uart: &Uart, /// from init's.
blk: &mut VirtioBlk, pub fn load_storage(uart: &Uart, init: ProcessId) -> LoadedProcess {
init: ProcessId,
filename: &[u8],
label: &str,
limits: Limits,
) -> LoadedProcess {
let pid = kern() let pid = kern()
.create_process_internal(init, limits) .create_process_internal(init, STORAGE)
.unwrap_or_else(|| crate::halt(uart, "process create failed")); .unwrap_or_else(|| crate::halt(uart, "process create failed"));
let entry = load_elf(uart, blk, pid, filename); let entry = load_elf(uart, pid, STORAGE_ELF);
let tid = kern() let tid = kern()
.spawn_thread_internal(pid) .spawn_thread_internal(pid)
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed")); .unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
setup_thread(tid, entry); setup_thread(tid, entry);
uart.write_str("k-os: "); uart.write_str("k-os: storage loaded\n");
uart.write_str(label);
uart.write_str(" loaded\n");
LoadedProcess { pid, tid } LoadedProcess { pid }
} }
fn setup_thread(tid: ThreadId, entry: usize) { fn setup_thread(tid: ThreadId, entry: usize) {
@@ -143,13 +111,10 @@ fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
/// Loads `filename` into `pid` through the kernel's own mapping API, so /// Loads `filename` into `pid` through the kernel's own mapping API, so
/// every page is tracked, charged and checked like any other. Halts on any /// every page is tracked, charged and checked like any other. Halts on any
/// malformed or overlapping image: boot servers are trusted to be sane. /// malformed or overlapping image: boot servers are trusted to be sane.
fn load_elf(uart: &Uart, blk: &mut VirtioBlk, pid: ProcessId, filename: &[u8]) -> usize { fn load_elf(uart: &Uart, pid: ProcessId, file: &[u8]) -> usize {
let buf = elf_buffer(); if file.is_empty() {
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0); crate::halt(uart, "program missing from the kernel image (make build)");
if file_size == 0 {
crate::halt(uart, "ELF not found");
} }
let file = &buf[..file_size];
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF")); let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
for segment in elf_info.segments() { for segment in elf_info.segments() {

View File

@@ -1,143 +0,0 @@
use crate::hw::virtio_blk::VirtioBlk;
const SECTOR_SIZE: usize = 512;
struct BootFat16 {
reserved_sectors: u16,
root_dir_start: u32,
root_dir_sectors: u32,
data_start: u32,
sectors_per_cluster: u8,
}
impl BootFat16 {
fn parse(boot_sector: &[u8; SECTOR_SIZE]) -> Option<Self> {
if boot_sector[510] != 0x55 || boot_sector[511] != 0xAA {
return None;
}
let bytes_per_sector = u16::from_le_bytes([boot_sector[11], boot_sector[12]]);
if bytes_per_sector != SECTOR_SIZE as u16 {
return None;
}
let sectors_per_cluster = boot_sector[13];
let reserved_sectors = u16::from_le_bytes([boot_sector[14], boot_sector[15]]);
let num_fats = boot_sector[16];
let root_entry_count = u16::from_le_bytes([boot_sector[17], boot_sector[18]]);
let sectors_per_fat = u16::from_le_bytes([boot_sector[22], boot_sector[23]]);
let fat_start = reserved_sectors as u32;
let root_dir_start = fat_start + (num_fats as u32) * (sectors_per_fat as u32);
let root_dir_sectors = ((root_entry_count as u32) * 32).div_ceil(512);
let data_start = root_dir_start + root_dir_sectors;
Some(Self {
reserved_sectors,
root_dir_start,
root_dir_sectors,
data_start,
sectors_per_cluster,
})
}
fn cluster_to_sector(&self, cluster: u16) -> u32 {
self.data_start + ((cluster as u32) - 2) * (self.sectors_per_cluster as u32)
}
}
fn to_fat_name(name: &[u8]) -> [u8; 11] {
let mut fat = [b' '; 11];
let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len());
for i in 0..dot_pos.min(8) {
fat[i] = name[i].to_ascii_uppercase();
}
if dot_pos < name.len() {
for i in 0..(name.len() - dot_pos - 1).min(3) {
fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase();
}
}
fat
}
pub fn read_file_from_disk(
blk: &mut VirtioBlk,
filename: &[u8],
out_buf: &mut [u8],
) -> Option<usize> {
let mut sector_buf = [0u8; SECTOR_SIZE];
if !blk.read_sector(0, &mut sector_buf) {
return None;
}
let fs = BootFat16::parse(&sector_buf)?;
let fat_name = to_fat_name(filename);
let mut first_cluster: u16 = 0;
let mut file_size: u32 = 0;
let mut found = false;
for s in 0..fs.root_dir_sectors {
if !blk.read_sector(fs.root_dir_start + s, &mut sector_buf) {
continue;
}
for i in 0..(SECTOR_SIZE / 32) {
let off = i * 32;
if sector_buf[off] == 0x00 {
break;
}
if sector_buf[off] == 0xE5 {
continue;
}
if sector_buf[off..off + 11] == fat_name {
first_cluster = u16::from_le_bytes([sector_buf[off + 26], sector_buf[off + 27]]);
file_size = u32::from_le_bytes([
sector_buf[off + 28],
sector_buf[off + 29],
sector_buf[off + 30],
sector_buf[off + 31],
]);
found = true;
break;
}
}
if found {
break;
}
}
if !found {
return None;
}
let size = (file_size as usize).min(out_buf.len());
let mut read = 0;
let mut cluster = first_cluster;
let sectors_per_cluster = fs.sectors_per_cluster as u32;
while read < size && (2..0xFFF8).contains(&cluster) {
for s in 0..sectors_per_cluster {
if read >= size {
break;
}
let sector = fs.cluster_to_sector(cluster) + s;
if !blk.read_sector(sector, &mut sector_buf) {
return Some(read);
}
let chunk = (size - read).min(SECTOR_SIZE);
out_buf[read..read + chunk].copy_from_slice(&sector_buf[..chunk]);
read += chunk;
}
let fat_offset = (cluster as u32) * 2;
let fat_sector = fs.reserved_sectors as u32 + fat_offset / SECTOR_SIZE as u32;
let fat_byte = (fat_offset % SECTOR_SIZE as u32) as usize;
if !blk.read_sector(fat_sector, &mut sector_buf) {
break;
}
cluster = u16::from_le_bytes([sector_buf[fat_byte], sector_buf[fat_byte + 1]]);
}
Some(read)
}

View File

@@ -1,4 +1,3 @@
pub mod plic; pub mod plic;
pub mod timer; pub mod timer;
pub mod uart; pub mod uart;
pub mod virtio_blk;

View File

@@ -1,4 +1,10 @@
use core::sync::atomic::{AtomicU64, Ordering};
use crate::board; use crate::board;
use crate::smp::{MAX_HARTS, hart_id};
/// When each hart's timer is set to go off next.
static NEXT: [AtomicU64; MAX_HARTS] = [const { AtomicU64::new(u64::MAX) }; MAX_HARTS];
pub fn read_time() -> u64 { pub fn read_time() -> u64 {
let time: u64; let time: u64;
@@ -21,15 +27,27 @@ fn sbi_set_timer(stime: u64) {
} }
pub fn init() { pub fn init() {
let now = read_time(); set_next(read_time() + board::TICK_INTERVAL);
sbi_set_timer(now + board::TICK_INTERVAL);
} }
pub fn set_next(deadline: u64) { pub fn set_next(deadline: u64) {
if let Some(next) = NEXT.get(hart_id()) {
next.store(deadline, Ordering::Relaxed);
}
sbi_set_timer(deadline); sbi_set_timer(deadline);
} }
pub fn handle_interrupt() { /// Goes off at `deadline` too, if that is before the next tick.
let now = read_time(); pub fn wake_at(deadline: u64) {
sbi_set_timer(now + board::TICK_INTERVAL); if NEXT
.get(hart_id())
.is_some_and(|n| deadline < n.load(Ordering::Relaxed))
{
set_next(deadline);
}
}
/// Re-arms the tick; the kernel then re-arms for its earliest timer.
pub fn handle_interrupt() {
set_next(read_time() + board::TICK_INTERVAL);
} }

View File

@@ -1,63 +0,0 @@
use crate::mmu::{phys_to_virt, virt_to_phys};
use hw_virtio::block::{self, DmaAddrs};
use hw_virtio::mmio::{self, DirectMmio};
use hw_virtio::queue::{QUEUE_SIZE, QueueAddrs, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
const SECTOR_SIZE: usize = 512;
#[repr(C, align(4096))]
struct VirtqMem {
_data: [u8; 8192],
}
static mut VQUEUE: VirtqMem = VirtqMem { _data: [0; 8192] };
static mut REQ_HEADER: block::BlockReqHeader = block::BlockReqHeader {
req_type: 0,
reserved: 0,
sector: 0,
};
static mut REQ_STATUS: u8 = 0xFF;
static mut SECTOR_BUF: [u8; SECTOR_SIZE] = [0; SECTOR_SIZE];
pub struct VirtioBlk {
mmio: DirectMmio,
queue: VirtioQueue,
dma: DmaAddrs,
}
impl VirtioBlk {
pub fn probe() -> Option<Self> {
let mmio = mmio::scan_for_device(phys_to_virt(mmio::MMIO_FIRST), mmio::DEVICE_ID_BLOCK)?;
// The device sees physical addresses; the CPU uses kernel ones.
let vq_va = core::ptr::addr_of!(VQUEUE) as usize;
mmio::init_legacy_device(&mmio, virt_to_phys(vq_va), QUEUE_SIZE as u32);
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_va));
let pair = |va: usize| {
(
PhysicalAddress::from_raw(virt_to_phys(va)),
VirtualAddress::from_raw(va),
)
};
let (header_pa, header_va) = pair(core::ptr::addr_of_mut!(REQ_HEADER) as usize);
let (status_pa, status_va) = pair(core::ptr::addr_of_mut!(REQ_STATUS) as usize);
let (data_pa, data_va) = pair(core::ptr::addr_of_mut!(SECTOR_BUF) as usize);
let dma = DmaAddrs {
header_pa,
header_va,
status_pa,
status_va,
data_pa,
data_va,
};
Some(Self { mmio, queue, dma })
}
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf)
}
}

View File

@@ -1,9 +1,7 @@
use core::cell::UnsafeCell; use core::cell::UnsafeCell;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicBool, Ordering};
pub struct KernelCell<T> { pub struct KernelCell<T> {
initialized: AtomicBool,
inner: UnsafeCell<MaybeUninit<T>>, inner: UnsafeCell<MaybeUninit<T>>,
} }
@@ -11,25 +9,16 @@ pub struct KernelCell<T> {
unsafe impl<T> Sync for KernelCell<T> {} unsafe impl<T> Sync for KernelCell<T> {}
impl<T> KernelCell<T> { impl<T> KernelCell<T> {
pub const fn uninit() -> Self { /// Holds `value` from the start: built at compile time, so a large `T`
/// never lives on the stack.
pub const fn new(value: T) -> Self {
Self { Self {
initialized: AtomicBool::new(false), inner: UnsafeCell::new(MaybeUninit::new(value)),
inner: UnsafeCell::new(MaybeUninit::uninit()),
} }
} }
/// Initializes in place, so a large `T` never lives on the stack.
///
/// # Safety
/// `build` must fully initialize the `T` behind the pointer.
pub unsafe fn init_with(&self, build: impl FnOnce(*mut T)) {
build(unsafe { (*self.inner.get()).as_mut_ptr() });
self.initialized.store(true, Ordering::Release);
}
/// Raw pointer to the kernel; callers must avoid aliasing `&mut`. /// Raw pointer to the kernel; callers must avoid aliasing `&mut`.
pub fn as_ptr(&self) -> *mut T { pub fn as_ptr(&self) -> *mut T {
debug_assert!(self.initialized.load(Ordering::Acquire));
unsafe { (*self.inner.get()).as_mut_ptr() } unsafe { (*self.inner.get()).as_mut_ptr() }
} }
} }

View File

@@ -3,7 +3,6 @@
mod board; mod board;
mod boot; mod boot;
mod boot_fs;
mod devices; mod devices;
mod entry; mod entry;
mod hw; mod hw;
@@ -16,7 +15,16 @@ mod trap;
use hw::uart::Uart; use hw::uart::Uart;
use kernel_cell::KernelCell; use kernel_cell::KernelCell;
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit(); /// Built at compile time with nothing in it (no platform, no cores, no
/// pool), so every byte is zero and it lives in `.bss`, not in the image;
/// boot installs the platform and the pool before anything runs.
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> =
KernelCell::new(kernel::Kernel::with_cores(
platform::RiscVPlatform::new(0, 0),
0,
types::address::PhysicalAddress::from_raw(0),
0,
));
fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> { fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
// SAFETY: boot runs with the kernel lock held and before any other // SAFETY: boot runs with the kernel lock held and before any other
@@ -36,36 +44,15 @@ extern "C" fn kmain(hart: usize) -> ! {
boot::hardware::init(&uart, hart); boot::hardware::init(&uart, hart);
// Not before init: zeroing .bss resets the lock. // Not before init: zeroing .bss resets the lock.
smp::lock(); smp::lock();
let mut blk = // Both come from the kernel image; init starts everything else from disk.
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device")); let init = boot::loader::load_init(&uart);
let storage = boot::loader::load_storage(&uart, init.pid);
let init = boot::loader::load_init(&uart, &mut blk);
use boot::loader::{DISPLAY, INPUT, STORAGE, load_server};
let storage = load_server(
&uart,
&mut blk,
init.pid,
b"storage.elf",
"storage",
STORAGE,
);
let display = load_server(
&uart,
&mut blk,
init.pid,
b"display.elf",
"display",
DISPLAY,
);
let input = load_server(&uart, &mut blk, init.pid, b"input.elf", "input", INPUT);
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table")); let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
kern_mut() kern_mut()
.register_board(init.pid, &board) .register_board(init.pid, &board)
.unwrap_or_else(|_| halt(&uart, "board registration failed")); .unwrap_or_else(|_| halt(&uart, "board registration failed"));
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid); boot::caps::grant_all(&uart, init.pid, storage.pid);
// Last, so the servers' fixed slots stay where they expect them.
boot::caps::grant_console(&uart, &[storage, display, input]);
// After grant_all: init already relies on those slots. // After grant_all: init already relies on those slots.
let mut placed = [None; board::BOARD_DEVICES]; let mut placed = [None; board::BOARD_DEVICES];
let boot_caps = kern_mut() let boot_caps = kern_mut()

View File

@@ -1,9 +1,10 @@
use super::pte::{PAGE_SIZE, PageTable, PageTableEntry, Sv39Flags, table_at, vpn}; use super::pte::{PAGE_SIZE, PageTable, PageTableEntry, Sv39Flags, table_at, vpn};
use platform::{FrameSource, MapError}; use platform::{FrameSource, MapError};
/// User mappings live in root slot 0, in a private L1 per address space. /// User mappings live in root slot 0, in a private L1 per address space:
/// all of its 1 GiB (leaf tables are made as pages are mapped).
pub const USER_VA_START: usize = 0x1000; pub const USER_VA_START: usize = 0x1000;
pub const USER_VA_END: usize = 0x0200_0000; pub const USER_VA_END: usize = 0x4000_0000;
fn fresh_table(frames: &mut dyn FrameSource) -> Result<usize, MapError> { fn fresh_table(frames: &mut dyn FrameSource) -> Result<usize, MapError> {
let pa = frames let pa = frames

View File

@@ -4,10 +4,9 @@ use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress}, address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
id::{CoreId, IrqId, ProcessId}, id::{CoreId, IrqId, ProcessId},
mapping::PagePerms, mapping::PagePerms,
time::{Ticks, TimerDuration}, time::Ticks,
}; };
use crate::board;
use crate::hw::{plic::Plic, timer}; use crate::hw::{plic::Plic, timer};
use crate::mmu; use crate::mmu;
use crate::smp; use crate::smp;
@@ -21,7 +20,7 @@ pub struct RiscVPlatform {
} }
impl RiscVPlatform { impl RiscVPlatform {
pub fn new(kernel_pt_addr: usize, num_cores: u8) -> Self { pub const fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
Self { Self {
num_cores, num_cores,
kernel_pt: kernel_pt_addr, kernel_pt: kernel_pt_addr,
@@ -136,9 +135,8 @@ impl Platform for RiscVPlatform {
} }
} }
fn set_timer(&mut self, _duration: TimerDuration) { fn wake_at(&mut self, deadline: Ticks) {
let now = timer::read_time(); timer::wake_at(deadline.raw());
timer::set_next(now + board::TICK_INTERVAL);
} }
fn current_ticks(&self) -> Ticks { fn current_ticks(&self) -> Ticks {

View File

@@ -0,0 +1,165 @@
//! Floating point for user threads, lazily. A thread starts with the FPU
//! off (`sstatus.FS`); its first floating-point instruction traps as an
//! illegal instruction, and it is given zeroed registers and the FPU and
//! retries. From then on its registers are saved when a trap finds them
//! changed (FS dirty) and loaded whenever it is switched to; a thread
//! without them runs with the FPU off, so it never sees another's. The
//! kernel itself runs with the FPU off.
use core::arch::asm;
use domain::thread::{FpState, SavedContext};
use super::TrapFrame;
const FS: usize = 3 << 13;
const FS_OFF: usize = 0;
const FS_CLEAN: usize = 2 << 13;
const FS_DIRTY: usize = 3 << 13;
/// Turns the FPU off for the kernel; what the trap frame says the thread
/// returns with is unchanged.
pub(super) fn kernel_off() {
// SAFETY: clears sstatus.FS only.
unsafe { asm!("csrc sstatus, {0}", in(reg) FS) };
}
fn hardware_on() {
// SAFETY: sets sstatus.FS to (at least) clean, so the FP registers can
// be read and written; `kernel_off` turns it off again.
unsafe { asm!("csrs sstatus, {0}", in(reg) FS_CLEAN) };
}
/// On a trap from a thread: its registers into its context if it changed
/// them, and the frame then says they are saved.
pub(super) fn save_if_dirty(frame: &mut TrapFrame, ctx: &mut SavedContext) {
if frame.sstatus & FS != FS_DIRTY {
return;
}
if let Some(state) = ctx.fp_mut() {
hardware_on();
save(state);
frame.sstatus = (frame.sstatus & !FS) | FS_CLEAN;
}
}
/// Returning to a thread: its registers into the FPU, or the FPU off.
pub(super) fn load_for(frame: &mut TrapFrame, ctx: &SavedContext) {
frame.sstatus &= !FS;
match ctx.fp() {
Some(state) => {
hardware_on();
load(state);
frame.sstatus |= FS_CLEAN;
}
None => frame.sstatus |= FS_OFF,
}
}
/// An illegal instruction from a thread with the FPU off and no registers
/// yet: it gets them, zeroed, and retries. Whether that was it (otherwise
/// the instruction is a real fault).
pub(super) fn first_use(frame: &mut TrapFrame, ctx: &mut SavedContext) -> bool {
if frame.sstatus & FS != FS_OFF || ctx.fp().is_some() {
return false;
}
let state = ctx.enable_fp();
hardware_on();
load(state);
// sepc still names the instruction, so it runs again.
frame.sstatus = (frame.sstatus & !FS) | FS_CLEAN;
true
}
/// `$op` (fsd or fld) for every FP register, at `{base}` + 8 each.
macro_rules! fp_registers {
($op:literal) => {
concat!(
$op,
" f0, 0({base})\n",
$op,
" f1, 8({base})\n",
$op,
" f2, 16({base})\n",
$op,
" f3, 24({base})\n",
$op,
" f4, 32({base})\n",
$op,
" f5, 40({base})\n",
$op,
" f6, 48({base})\n",
$op,
" f7, 56({base})\n",
$op,
" f8, 64({base})\n",
$op,
" f9, 72({base})\n",
$op,
" f10, 80({base})\n",
$op,
" f11, 88({base})\n",
$op,
" f12, 96({base})\n",
$op,
" f13, 104({base})\n",
$op,
" f14, 112({base})\n",
$op,
" f15, 120({base})\n",
$op,
" f16, 128({base})\n",
$op,
" f17, 136({base})\n",
$op,
" f18, 144({base})\n",
$op,
" f19, 152({base})\n",
$op,
" f20, 160({base})\n",
$op,
" f21, 168({base})\n",
$op,
" f22, 176({base})\n",
$op,
" f23, 184({base})\n",
$op,
" f24, 192({base})\n",
$op,
" f25, 200({base})\n",
$op,
" f26, 208({base})\n",
$op,
" f27, 216({base})\n",
$op,
" f28, 224({base})\n",
$op,
" f29, 232({base})\n",
$op,
" f30, 240({base})\n",
$op,
" f31, 248({base})\n",
)
};
}
/// The FPU's registers into `state` (the FPU must be on).
fn save(state: &mut FpState) {
let base = state.registers.as_mut_ptr();
let control: u64;
// SAFETY: `base` points at 32 u64s, written in place; frcsr only reads.
unsafe {
asm!(fp_registers!("fsd"), base = in(reg) base, options(nostack));
asm!("frcsr {0}", out(reg) control, options(nostack));
}
state.control = control;
}
/// `state` into the FPU's registers (the FPU must be on).
fn load(state: &FpState) {
let base = state.registers.as_ptr();
// SAFETY: `base` points at 32 u64s, only read; fscsr writes fcsr.
unsafe {
asm!(fp_registers!("fld"), base = in(reg) base, options(nostack, readonly));
asm!("fscsr {0}", in(reg) state.control, options(nostack));
}
}

View File

@@ -1,4 +1,5 @@
mod fast; mod fast;
mod fp;
mod sched; mod sched;
mod vector; mod vector;
@@ -17,6 +18,7 @@ const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
const CAUSE_S_SOFTWARE: usize = 1; const CAUSE_S_SOFTWARE: usize = 1;
const CAUSE_S_TIMER: usize = 5; const CAUSE_S_TIMER: usize = 5;
const CAUSE_S_EXTERNAL: usize = 9; const CAUSE_S_EXTERNAL: usize = 9;
const CAUSE_ILLEGAL_INSTRUCTION: usize = 2;
const CAUSE_ECALL_U: usize = 8; const CAUSE_ECALL_U: usize = 8;
/// Set in `sstatus` when the trap came from supervisor mode. /// Set in `sstatus` when the trap came from supervisor mode.
const SSTATUS_SPP: usize = 1 << 8; const SSTATUS_SPP: usize = 1 << 8;
@@ -29,6 +31,8 @@ pub fn init() {
let addr = trap_vector as *const () as usize; let addr = trap_vector as *const () as usize;
core::arch::asm!("csrw stvec, {0}", in(reg) addr); core::arch::asm!("csrw stvec, {0}", in(reg) addr);
core::arch::asm!("csrw sscratch, zero"); core::arch::asm!("csrw sscratch, zero");
// The kernel never uses the FPU; user threads get it lazily.
core::arch::asm!("csrc sstatus, {0}", in(reg) 3usize << 13);
// SIE stays clear; interrupts are enabled only by sret into userspace. // SIE stays clear; interrupts are enabled only by sret into userspace.
let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1); let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1);
core::arch::asm!("csrs sie, {0}", in(reg) sie_bits); core::arch::asm!("csrs sie, {0}", in(reg) sie_bits);
@@ -90,6 +94,14 @@ extern "C" fn trap_handler_rust(frame: &mut TrapFrame) {
} }
crate::smp::service_ipi(); crate::smp::service_ipi();
crate::smp::lock(); crate::smp::lock();
// The thread's FP registers are saved before anything can switch away.
if let Some(ctx) = kern()
.current_thread()
.and_then(|t| kern().thread_context_mut(t))
{
fp::save_if_dirty(frame, ctx);
}
fp::kernel_off();
if scause & CAUSE_INTERRUPT_BIT != 0 { if scause & CAUSE_INTERRUPT_BIT != 0 {
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT); handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
} else { } else {
@@ -104,6 +116,7 @@ fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
CAUSE_S_TIMER => { CAUSE_S_TIMER => {
crate::hw::timer::handle_interrupt(); crate::hw::timer::handle_interrupt();
let kern = kern(); let kern = kern();
kern.fire_timers();
if let Some(current_tid) = kern.current_thread() { if let Some(current_tid) = kern.current_thread() {
let decision = kern.handle_tick(this_core()); let decision = kern.handle_tick(this_core());
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision { if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {
@@ -162,19 +175,36 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
} }
} }
} }
CAUSE_ILLEGAL_INSTRUCTION
if kern()
.current_thread()
.and_then(|t| kern().thread_context_mut(t))
.is_some_and(|ctx| fp::first_use(frame, ctx)) => {}
_ => { _ => {
let uart = crate::hw::uart::Uart::new(); let uart = crate::hw::uart::Uart::new();
uart.write_str("FAULT: scause=");
uart.write_hex(scause);
let stval: usize; let stval: usize;
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) }; unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
uart.write_str(" stval="); let kern = kern();
uart.write_str("FAULT pid=");
let pid = kern.current_thread().and_then(|t| kern.thread_process(t));
uart.write_hex(pid.map_or(usize::MAX, |p| p.raw() as usize));
uart.write_str(": ");
uart.write_str(match scause {
2 => "illegal instruction",
5 | 13 => "load fault",
7 | 15 => "store fault",
12 => "instruction fetch fault",
_ => "fault",
});
uart.write_str(" at ");
uart.write_hex(stval); uart.write_hex(stval);
uart.write_str(" sepc="); if matches!(scause, 5 | 7 | 13 | 15) && types::layout::is_stack_overflow(stval) {
uart.write_str(" (stack overflow: give it more stack)");
}
uart.write_str(", pc ");
uart.write_hex(frame.sepc); uart.write_hex(frame.sepc);
uart.put_char(b'\n'); uart.put_char(b'\n');
let kern = kern();
if let Some(current_tid) = kern.current_thread() { if let Some(current_tid) = kern.current_thread() {
let fault = match scause { let fault = match scause {
2 => domain::fault::Fault::IllegalInstruction, 2 => domain::fault::Fault::IllegalInstruction,

View File

@@ -14,6 +14,7 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra
frame.sstatus = ctx.status(); frame.sstatus = ctx.status();
frame.sstatus &= !(1 << 8); // SPP: return to U-mode frame.sstatus &= !(1 << 8); // SPP: return to U-mode
frame.sstatus |= 1 << 5; // SPIE frame.sstatus |= 1 << 5; // SPIE
super::fp::load_for(frame, ctx);
} }
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) { fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
@@ -70,6 +71,7 @@ fn next_runnable() -> types::id::ThreadId {
if sip & (1 << CAUSE_S_TIMER) != 0 { if sip & (1 << CAUSE_S_TIMER) != 0 {
// No thread is running, so there is no time slice to charge. // No thread is running, so there is no time slice to charge.
crate::hw::timer::handle_interrupt(); crate::hw::timer::handle_interrupt();
kern().fire_timers();
} }
if sip & (1 << CAUSE_S_EXTERNAL) != 0 { if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
dispatch_external(); dispatch_external();

View File

@@ -0,0 +1,13 @@
[package]
name = "cat"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "cat"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }

View File

@@ -0,0 +1,6 @@
[app]
id = "kos.cat"
name = "cat"
version = "1.0.0"
binary = "cat"
author = "K-OS"

View File

@@ -0,0 +1,48 @@
//! Prints files: `cat <file>...`.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
use libkos::fs::{FsError, Storage};
use libkos::println;
/// Where storage's buffer is mapped.
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
fn cat(fs: &mut Storage, path: &str) -> Result<(), FsError> {
let h = fs.open(path.as_bytes())?;
let mut buf = [0u8; 4096];
loop {
let n = fs.read(h, &mut buf)?;
libkos::io::write_bytes(&buf[..n]);
if n < buf.len() {
break;
}
}
fs.close(h)
}
libkos::entry!(main);
fn main() {
let Some(mut fs) = libkos::start::storage().and_then(|ep| Storage::attach(ep, STORAGE_BUF_VA).ok()) else {
println!("cat: no storage");
return;
};
let mut any = false;
for path in libkos::start::args().skip(1) {
any = true;
match cat(&mut fs, path) {
Ok(()) => {}
Err(FsError::NotFound) => println!("cat: {}: no such file", path),
Err(FsError::Denied) => println!("cat: {}: outside what it was given", path),
Err(_) => println!("cat: {}: cannot read", path),
}
}
if !any {
println!("usage: cat <file>...");
}
}

View File

@@ -0,0 +1,14 @@
[package]
name = "counter"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "counter"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }

View File

@@ -0,0 +1,10 @@
# Remembers how often it ran, if the owner lets it keep a file.
[app]
id = "kos.counter"
name = "Counter"
version = "1.0.0"
binary = "counter"
author = "K-OS"
[capabilities]
optional = ["storage"]

View File

@@ -0,0 +1,59 @@
//! Counts its runs in its own data folder: the `storage` capability, which
//! it can do without (then it remembers nothing).
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
use libkos::fs::Storage;
use libkos::println;
use types::address::VirtualAddress;
const DATA_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0180_0000);
const COUNT: &[u8] = b"count";
/// The number in the file, 0 if there is none yet.
fn read_count(fs: &mut Storage) -> u64 {
let Ok(h) = fs.open(COUNT) else { return 0 };
let mut buf = [0u8; 20];
let n = fs.read(h, &mut buf).unwrap_or(0);
let _ = fs.close(h);
core::str::from_utf8(&buf[..n]).ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0)
}
fn write_count(fs: &mut Storage, count: u64) -> bool {
let mut digits = [0u8; 20];
let mut n = 0;
let mut v = count;
loop {
digits[n] = b'0' + (v % 10) as u8;
n += 1;
v /= 10;
if v == 0 {
break;
}
}
digits[..n].reverse();
let _ = fs.delete(COUNT);
let Ok(h) = fs.create(COUNT) else { return false };
let ok = fs.write(h, &digits[..n]) == Ok(n);
let _ = fs.close(h);
ok
}
fn main() {
let Some(mut fs) = libkos::start::data().and_then(|d| Storage::attach(d, DATA_BUF_VA).ok()) else {
println!("counter: not allowed to keep anything, so this is run 1 as far as it knows");
return;
};
let count = read_count(&mut fs) + 1;
if write_count(&mut fs, count) {
println!("counter: run {} (kept in its own data folder)", count);
} else {
println!("counter: run {}, but it could not save that", count);
}
}
libkos::entry!(main);

View File

@@ -1,4 +1,5 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#![no_main] #![no_main]
extern crate libkos; extern crate libkos;
@@ -6,8 +7,9 @@ extern crate libkos;
use types::bounded::CapSlot; use types::bounded::CapSlot;
use libkos::syscall; use libkos::syscall;
#[unsafe(no_mangle)] libkos::entry!(main);
extern "C" fn main() {
fn main() {
let ep = CapSlot::const_new(0); let ep = CapSlot::const_new(0);
if let Ok(syscall::Received::Call { words: [w0, w1, w2, w3], .. }) = syscall::receive_msg(ep) { if let Ok(syscall::Received::Call { words: [w0, w1, w2, w3], .. }) = syscall::receive_msg(ep) {

View File

@@ -0,0 +1,14 @@
[package]
name = "flaky"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "flaky"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }
server-framework = { workspace = true }

View File

@@ -0,0 +1,82 @@
//! A server that dies or hangs when asked: on the test image only, so the
//! self-test can watch init restart it (see init's supervise.rs).
#![no_std]
#![forbid(unsafe_code)]
use libkos::syscall;
use server_framework::{Decode, Encode, MALFORMED, Server};
use types::bounded::CapSlot;
/// Requests (word 0); a reply carries how many requests this instance has
/// served, this one included: a restarted server starts at 1.
pub mod op {
pub const WHO: usize = 1;
/// Panics: the process dies.
pub const CRASH: usize = 2;
/// Blocks for good inside the handler.
pub const HANG: usize = 3;
/// Recurses until its stack runs out: the kernel says it overflowed.
pub const OVERFLOW: usize = 4;
}
/// Ever deeper, a KiB of stack a call, until there is none.
#[allow(unconditional_recursion, reason = "running off the stack is what it is for")]
fn deeper(n: usize) -> usize {
let frame = core::hint::black_box([n as u8; 1024]);
// Used after the call, so the call is not a jump.
deeper(n + 1) + frame[n % 1024] as usize
}
/// Everything goes through `handle_call`.
struct Never;
impl Decode for Never {
fn decode(_: [usize; 4]) -> Option<Self> {
None
}
}
impl Encode for Never {
fn encode(&self) -> [usize; 4] {
[MALFORMED, 0, 0, 0]
}
}
struct Flaky {
served: usize,
}
impl Server for Flaky {
type Request = Never;
type Response = Never;
fn handle(&mut self, request: Never) -> Never {
request
}
fn handle_call(&mut self, _badge: usize, words: [usize; 4], _cap: Option<CapSlot>) -> ([usize; 4], Option<syscall::Give>) {
match words[0] {
op::WHO => {}
op::CRASH => panic!("asked to crash"),
op::OVERFLOW => {
core::hint::black_box(deeper(0));
}
op::HANG => {
// A notification nobody signals.
if let Ok(never) = syscall::create_notification() {
loop {
let _ = syscall::wait(never);
}
}
}
_ => return ([MALFORMED, 0, 0, 0], None),
}
self.served += 1;
([self.served, 0, 0, 0], None)
}
}
pub fn run() -> ! {
server_framework::serve(&mut Flaky { served: 0 }, CapSlot::const_new(0))
}

View File

@@ -0,0 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
libkos::entry!(main);
fn main() {
flaky::run();
}

View File

@@ -1,9 +1,11 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#![no_main] #![no_main]
extern crate libkos; extern crate libkos;
#[unsafe(no_mangle)] libkos::entry!(main);
extern "C" fn main() {
fn main() {
libkos::println!("Hello from k-os!"); libkos::println!("Hello from k-os!");
} }

View File

@@ -0,0 +1,15 @@
[package]
name = "home"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "home"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }
k-ui = { workspace = true }
manifest = { workspace = true }

View File

@@ -0,0 +1,10 @@
[app]
id = "kos.home"
name = "Home"
version = "1.0.0"
binary = "home"
author = "K-OS"
memory = "3 MiB"
[capabilities]
required = ["display", "launch"]

View File

@@ -0,0 +1,118 @@
//! Home: the session's desktop, which the permission manager starts with
//! it. It lists every app with a window and opens one when it is pressed,
//! as many as there is room for, each in its own window. A system app:
//! `launch` is only ever granted to apps on the system image.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate alloc;
extern crate libkos;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use k_ui::{App, Element, Spacing, TextColour, TextStyle, column, list_item, text};
use libkos::apps::Apps;
use manifest::{Capabilities, Capability};
use types::address::VirtualAddress;
use types::apps::reply;
/// Where the request page for the permission manager is mapped.
const REQUEST_PAGE_VA: VirtualAddress = VirtualAddress::from_raw(0x0190_0000);
const LISTING: usize = 4096;
const ME: &str = "kos.home";
struct Entry {
id: String,
name: String,
}
struct Home {
apps: Option<Apps>,
entries: Vec<Entry>,
/// What went wrong last, shown until the next press.
trouble: Option<String>,
}
#[derive(Clone, Copy)]
enum Event {
/// Open the `n`th app listed.
Open(usize),
}
impl Home {
/// Every installed app that has a window, but Home.
fn reload(&mut self) {
let Some(apps) = &self.apps else { return };
// On the heap: an app's stack is a few pages.
let mut buf = alloc::vec![0u8; LISTING];
let Ok(n) = apps.apps(&mut buf) else { return };
let text = core::str::from_utf8(&buf[..n]).unwrap_or("");
self.entries = libkos::apps::entries(text)
.filter(|e| e.id != ME)
.filter(|e| Capabilities::from_bits(e.required).union(Capabilities::from_bits(e.optional)).contains(Capability::Display))
.map(|e| Entry { id: e.id.to_string(), name: e.name.to_string() })
.collect();
}
fn open(&mut self, index: usize) {
let (Some(apps), Some(e)) = (&self.apps, self.entries.get(index)) else { return };
self.trouble = match apps.launch(&[e.id.as_str()], None, None) {
// Its window comes to the front; it runs on its own.
Ok((process, _)) => {
libkos::syscall::drop_cap(process);
None
}
Err(r) if r.code == reply::BUSY => Some("There is no room for another app: close one first (F4).".to_string()),
Err(r) => Some(format!("{} did not open (code {}).", e.name, r.code)),
};
self.reload();
}
}
impl App for Home {
type Event = Event;
fn init() -> Self {
let apps = libkos::start::permissions().and_then(|ep| Apps::connect(ep, REQUEST_PAGE_VA));
let mut home = Home { apps, entries: Vec::new(), trouble: None };
home.reload();
home
}
fn update(&mut self, event: Event) {
match event {
Event::Open(i) => self.open(i),
}
}
fn view(&self) -> Element<Event> {
let hint = "Enter opens an app. Alt+Tab or F1: the next window. F2: back here. F4: close the app in front.";
let mut view = column()
.padding(Spacing::Xl)
.gap(Spacing::Md)
.child(text("Home").style(TextStyle::Heading))
.child(text(hint).style(TextStyle::Caption).colour(TextColour::Secondary))
.children(self.entries.iter().enumerate().map(|(i, e)| list_item(e.name.clone(), Event::Open(i)).subtitle(e.id.clone())));
if self.entries.is_empty() {
view = view.child(text("No apps with a window are installed."));
}
if let Some(t) = &self.trouble {
view = view.child(text(t.clone()).colour(TextColour::Accent));
}
view.into()
}
/// Home is not left: Escape stays.
fn back(&mut self) -> bool {
true
}
}
libkos::entry!(main);
fn main() {
k_ui::run::<Home>();
}

13
crates/apps/ls/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "ls"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "ls"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }

View File

@@ -0,0 +1,6 @@
[app]
id = "kos.ls"
name = "ls"
version = "1.0.0"
binary = "ls"
author = "K-OS"

View File

@@ -0,0 +1,39 @@
//! Lists a directory: `ls [dir]`, the root by default.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
use libkos::fs::{FsError, Storage};
use libkos::println;
/// Where storage's buffer is mapped.
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
libkos::entry!(main);
fn main() {
let Some(mut fs) = libkos::start::storage().and_then(|ep| Storage::attach(ep, STORAGE_BUF_VA).ok()) else {
println!("ls: no storage");
return;
};
let dir = libkos::start::args().nth(1).unwrap_or("/");
let listed = fs.list_dir(dir.as_bytes(), |e| {
let name = core::str::from_utf8(e.name()).unwrap_or("?");
if name == "." || name == ".." {
return;
}
if e.is_dir {
println!("{}/", name);
} else {
println!("{:<24} {:>8}", name, e.size);
}
});
match listed {
Ok(_) => {}
Err(FsError::Denied) => println!("ls: {}: outside what it was given", dir),
Err(_) => println!("ls: {}: no such directory", dir),
}
}

View File

@@ -0,0 +1,15 @@
[package]
name = "settings"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "settings"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }
k-ui = { workspace = true }
manifest = { workspace = true }

View File

@@ -0,0 +1,10 @@
[app]
id = "kos.settings"
name = "Settings"
version = "1.0.0"
binary = "settings"
author = "K-OS"
memory = "3 MiB"
[capabilities]
required = ["display", "permissions", "session"]

View File

@@ -0,0 +1,285 @@
//! Settings: what each app may use (allow or revoke, through the
//! permission manager), the theme (the display's list, kept per user by
//! init), who is signed in, and signing out. A system app:
//! `permissions` and `session` are only ever granted to apps on the system
//! image.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate alloc;
extern crate libkos;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use k_ui::{App, ButtonVariant, Element, Spacing, TextColour, TextStyle, button, column, list_item, text, text_input, toggle};
use libkos::apps::Apps;
use libkos::session::Role;
use manifest::{Capabilities, Capability};
use types::address::VirtualAddress;
/// Where the request page for the permission manager is mapped.
const REQUEST_PAGE_VA: VirtualAddress = VirtualAddress::from_raw(0x0190_0000);
const LISTING: usize = 4096;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Screen {
Home,
Apps,
/// One app, by its place in the list.
App(usize),
Appearance,
About,
}
#[derive(Clone)]
enum Event {
Show(Screen),
/// Allow or revoke this capability of the app on screen.
Flip(Capability),
/// What the app list is narrowed to.
Filter(String),
/// Use the display's `n`th theme.
Theme(usize),
SignOut,
}
struct AppRow {
id: String,
name: String,
required: Capabilities,
requested: Capabilities,
granted: Capabilities,
}
struct Settings {
screen: Screen,
apps: Option<Apps>,
rows: Vec<AppRow>,
/// Only apps whose name or id has this in it are listed.
filter: String,
/// Who is signed in, and how.
who: Option<(String, Role)>,
/// The display's themes, and which is in use.
themes: Vec<String>,
theme: usize,
/// What went wrong last, shown until the next change.
trouble: Option<String>,
}
/// What a capability lets an app do, as the user reads it.
fn describe(c: Capability) -> &'static str {
match c {
Capability::Display => "A window, and the keyboard while it is in front",
Capability::Storage => "A folder of its own",
Capability::Permissions => "Change what other apps may use",
Capability::Session => "See who is signed in, and sign out",
Capability::Network => "The network",
_ => c.name(),
}
}
impl Settings {
fn reload(&mut self) {
self.rows.clear();
let Some(apps) = &self.apps else { return };
// On the heap: an app's stack is a few pages.
let mut buf = alloc::vec![0u8; LISTING];
let Ok(n) = apps.apps(&mut buf) else {
self.trouble = Some("The permission manager did not answer.".to_string());
return;
};
let text = core::str::from_utf8(&buf[..n]).unwrap_or("");
for e in libkos::apps::entries(text) {
let required = Capabilities::from_bits(e.required);
self.rows.push(AppRow {
id: e.id.to_string(),
name: e.name.to_string(),
required,
requested: required.union(Capabilities::from_bits(e.optional)),
granted: Capabilities::from_bits(e.granted),
});
}
}
fn flip(&mut self, index: usize, cap: Capability) {
let (Some(apps), Some(row)) = (&self.apps, self.rows.get(index)) else { return };
let change = if row.granted.contains(cap) { apps.revoke(&row.id, cap.index()) } else { apps.allow(&row.id, cap.index()) };
self.trouble = change.err().map(|r| format!("That did not change (code {}).", r.code));
self.reload();
}
fn appearance(&self) -> Element<Event> {
let rows = self.themes.iter().enumerate().map(|(i, name)| {
let item = list_item(name.clone(), Event::Theme(i));
if i == self.theme { item.subtitle("In use") } else { item }
});
let view = Self::hint(Self::heading("Appearance"), "Esc goes back").children(rows);
self.with_trouble(view).into()
}
fn choose_theme(&mut self, index: usize) {
let (Some(name), Some(session)) = (self.themes.get(index), libkos::start::session()) else { return };
match libkos::session::set_theme(session, name.as_bytes()) {
true => self.theme = index,
false => self.trouble = Some("That theme could not be kept.".to_string()),
}
}
fn heading(title: &str) -> k_ui::element::FlexBuilder<Event> {
column().padding(Spacing::Xl).gap(Spacing::Md).child(text(title).style(TextStyle::Heading))
}
fn hint(view: k_ui::element::FlexBuilder<Event>, s: &str) -> k_ui::element::FlexBuilder<Event> {
view.child(text(s).style(TextStyle::Caption).colour(TextColour::Secondary))
}
fn home(&self) -> Element<Event> {
Self::heading("Settings")
.child(list_item("Apps", Event::Show(Screen::Apps)).subtitle("What each app may use"))
.child(list_item("Appearance", Event::Show(Screen::Appearance)).subtitle("The theme everything is drawn in"))
.child(list_item("About", Event::Show(Screen::About)).subtitle("Who is signed in, and this device"))
.child(button("Sign out", Event::SignOut).variant(ButtonVariant::Destructive))
.into()
}
fn app_list(&self) -> Element<Event> {
let wanted = self.filter.to_ascii_lowercase();
let shown = |r: &&AppRow| r.name.to_ascii_lowercase().contains(&wanted) || r.id.contains(&wanted);
let rows = self.rows.iter().enumerate().filter(|(_, r)| shown(r)).map(|(i, r)| {
let allowed = r.granted.intersect(r.requested).iter().count();
let asked = r.requested.iter().count();
list_item(r.name.clone(), Event::Show(Screen::App(i))).subtitle(format!("{} · {} of {} allowed", r.id, allowed, asked))
});
let find = text_input("Find an app", self.filter.clone(), Event::Filter).placeholder("Name or id");
let view = Self::hint(Self::heading("Apps"), "Esc goes back, Down goes to the list").child(find).children(rows);
self.with_trouble(view).into()
}
fn app(&self, index: usize) -> Element<Event> {
let Some(r) = self.rows.get(index) else { return self.app_list() };
let mut view = Self::hint(Self::heading(&r.name), &r.id);
if r.requested.is_empty() {
view = Self::hint(view, "It asks for nothing.");
}
for cap in r.requested.iter() {
let label = match r.required.contains(cap) {
true => format!("{} (needed to run)", describe(cap)),
false => describe(cap).to_string(),
};
view = view.child(toggle(label, r.granted.contains(cap), Event::Flip(cap)));
}
if !r.requested.is_empty() {
view = Self::hint(view, "Taking something away from a running app stops it.");
}
self.with_trouble(view).into()
}
fn about(&self) -> Element<Event> {
let (name, role) = match &self.who {
Some((n, Role::Owner)) => (n.as_str(), "an owner of this device"),
Some((n, Role::Standard)) => (n.as_str(), "a user of this device"),
Some((_, Role::Guest)) => ("Guest", "a guest: nothing is kept"),
None => ("?", "unknown"),
};
Self::hint(Self::heading("About"), "Esc goes back")
.child(text(format!("Signed in as {name}")).style(TextStyle::Subheading))
.child(text(format!("You are {role}.")))
.child(text(format!("K-OS {}", env!("CARGO_PKG_VERSION"))))
.child(text("RISC-V, on QEMU's virt board").colour(TextColour::Secondary))
.into()
}
fn with_trouble(&self, view: k_ui::element::FlexBuilder<Event>) -> k_ui::element::FlexBuilder<Event> {
match &self.trouble {
Some(t) => view.child(text(t.clone()).colour(TextColour::Accent)),
None => view,
}
}
}
impl App for Settings {
type Event = Event;
fn init() -> Self {
let apps = libkos::start::permissions().and_then(|ep| Apps::connect(ep, REQUEST_PAGE_VA));
let mut name = [0u8; types::name::BYTES];
let who = libkos::start::session()
.and_then(|s| libkos::session::who(s, &mut name))
.map(|(len, role)| (String::from_utf8_lossy(&name[..len]).into_owned(), role));
let window = libkos::start::display();
let themes = window.map_or(Vec::new(), |w| {
(0..)
.map_while(|i| libkos::display::theme_name(w, i))
.map(|(n, len)| String::from_utf8_lossy(&n[..len]).into_owned())
.collect()
});
let theme = window.and_then(libkos::display::theme).map_or(0, |(i, _)| i);
let mut s = Settings { screen: Screen::Home, apps, rows: Vec::new(), filter: String::new(), who, themes, theme, trouble: None };
s.reload();
s
}
fn update(&mut self, event: Event) {
match event {
Event::Show(screen) => {
self.trouble = None;
if screen == Screen::Apps {
self.filter.clear();
self.reload();
}
self.screen = screen;
}
Event::Flip(cap) => {
if let Screen::App(i) = self.screen {
self.flip(i, cap);
}
}
Event::Filter(f) => self.filter = f,
Event::Theme(i) => self.choose_theme(i),
Event::SignOut => {
if let Some(s) = libkos::start::session() {
libkos::session::sign_out(s);
}
}
}
}
fn view(&self) -> Element<Event> {
match self.screen {
Screen::Home => self.home(),
Screen::Apps => self.app_list(),
Screen::App(i) => self.app(i),
Screen::Appearance => self.appearance(),
Screen::About => self.about(),
}
}
fn back(&mut self) -> bool {
self.trouble = None;
self.screen = match self.screen {
Screen::Home => return false,
Screen::App(_) => Screen::Apps,
Screen::Apps | Screen::Appearance | Screen::About => Screen::Home,
};
true
}
fn screen(&self) -> usize {
match self.screen {
Screen::Home => 0,
Screen::Apps => 1,
Screen::About => 2,
Screen::Appearance => 3,
Screen::App(i) => 4 + i,
}
}
}
libkos::entry!(main);
fn main() {
k_ui::run::<Settings>();
}

View File

@@ -0,0 +1,14 @@
[package]
name = "shell"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "shell"
test = false
bench = false
[dependencies]
types = { workspace = true }
manifest = { workspace = true }
libkos = { workspace = true }

View File

@@ -0,0 +1,337 @@
//! The shell: a few built-ins, and anything else is an app. `name args...`
//! asks the permission manager to run it, passing along the terminal as
//! its output and a read-only grant to the current folder, which is all
//! the shell itself has to give; the app gets the rest of what it may have
//! from the permission manager. Then it waits (Ctrl+C kills it) and takes
//! the grant back. The owner manages apps from here too.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
use libkos::apps::{Apps, Refused};
use libkos::fs::Storage;
use libkos::{print, println, syscall};
use manifest::{Capabilities, Capability};
use types::address::VirtualAddress;
use types::apps::reply;
use types::bounded::CapSlot;
/// Where init's manifest put them: `uses`, then storage, then signing out.
const TERMINAL: CapSlot = CapSlot::const_new(0);
const PERMISSION: CapSlot = CapSlot::const_new(1);
/// The user's files, their `home`: the shell's `/`.
const HOME: CapSlot = CapSlot::const_new(2);
const SESSION: CapSlot = CapSlot::const_new(3);
/// The terminal again, the copy that takes Ctrl+C; apps get the plain one.
const CONTROL: CapSlot = CapSlot::const_new(4);
/// Bits on the shell's notification: the program died (its death watch is
/// unbadged), or Ctrl+C was typed (the terminal's copy).
const DIED: usize = 1;
const INTERRUPTED: usize = 1 << 1;
const MAX_ARGS: usize = 16;
const STORAGE_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0180_0000);
const REQUEST_PAGE_VA: VirtualAddress = VirtualAddress::from_raw(0x0190_0000);
/// The current directory, as a path on disk: no leading or trailing `/`.
struct Cwd {
path: [u8; types::storage::MAX_PATH],
len: usize,
}
impl Cwd {
fn as_bytes(&self) -> &[u8] {
&self.path[..self.len]
}
/// Where `cd to` from here leads: `/` starts at the root, `..` goes up
/// (the shell holds all of its root, so that is its to resolve).
fn join(&self, to: &str) -> Option<Cwd> {
let mut next = match to.starts_with('/') {
true => Cwd {
path: [0; types::storage::MAX_PATH],
len: 0,
},
false => Cwd {
path: self.path,
len: self.len,
},
};
for part in to.split('/') {
match part {
"" | "." => {}
".." => {
next.len = next
.as_bytes()
.iter()
.rposition(|b| *b == b'/')
.unwrap_or(0)
}
name => {
let sep = usize::from(next.len > 0);
let end = next.len + sep + name.len();
let room = next.path.get_mut(next.len..end)?;
room[..sep].copy_from_slice(&b"/"[..sep]);
room[sep..].copy_from_slice(name.as_bytes());
next.len = end;
}
}
}
Some(next)
}
}
impl core::fmt::Display for Cwd {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"/{}",
core::str::from_utf8(self.as_bytes()).unwrap_or("?")
)
}
}
struct Shell {
fs: Storage,
apps: Apps,
cwd: Cwd,
done: CapSlot,
}
/// `display, storage`.
struct Names(Capabilities);
impl core::fmt::Display for Names {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for (i, c) in self.0.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(c.name())?;
}
Ok(())
}
}
fn names(bits: usize) -> Names {
Names(Capabilities::from_bits(bits as u32))
}
/// Says why the permission manager refused, about `what`.
fn refused(what: &str, r: Refused) {
match r.code {
reply::NOT_INSTALLED => println!("shell: {}: not found", what),
reply::MISSING => println!("shell: {}: cannot run without {}", what, names(r.detail)),
reply::ALREADY_INSTALLED => println!("shell: {}: already installed", what),
reply::UNAVAILABLE => println!("shell: {}: this device has no {}", what, names(r.detail)),
reply::DECLINED => println!("shell: {}: not installed", what),
reply::NO_MANIFEST => println!("shell: {}: no app here (no manifest.toml)", what),
reply::BAD_MANIFEST if r.detail > 0 => println!("shell: {}: bad manifest, line {}", what, r.detail),
reply::BAD_MANIFEST => println!("shell: {}: bad manifest", what),
reply::NOT_REQUESTED => println!("shell: {}: never asked for that", what),
reply::SYSTEM_ONLY => println!("shell: {}: asks for {}, which only system apps may have", what, names(r.detail)),
reply::BUSY => println!("shell: no room to run {}", what),
_ => println!("shell: {}: failed", what),
}
}
/// Runs app `args[0]` with `args` in the current folder, and waits for it
/// to finish or Ctrl+C; with a last argument `&`, does not wait.
fn run_program(sh: &mut Shell, args: &[&str]) {
if let Some((&"&", args)) = args.split_last() {
return start_program(sh, args);
}
let folder = sh.fs.grant(sh.cwd.as_bytes(), false).ok();
// A Ctrl+C typed since the last program is not for this one.
let _ = syscall::poll(sh.done);
match sh.apps.launch(args, Some(TERMINAL), folder) {
Ok((process, _granted)) => {
if syscall::watch_death(process, sh.done).is_ok() {
while syscall::wait(sh.done).is_ok_and(|bits| {
if bits & INTERRUPTED != 0 {
syscall::kill(process);
}
bits & DIED == 0
}) {}
} else {
let _ = syscall::kill(process);
}
syscall::drop_cap(process);
}
Err(r) => refused(args[0], r),
}
if let Some(g) = folder {
let _ = libkos::fs::release(g);
}
}
/// Starts app `args[0]` and lets it run on its own (its window, if it has
/// one, is closed with F4). It does not get the current folder: nobody
/// would be there to take the grant back when it is done.
fn start_program(sh: &mut Shell, args: &[&str]) {
let Some(name) = args.first() else { return println!("usage: <app> [args] &") };
match sh.apps.launch(args, Some(TERMINAL), None) {
Ok((process, _)) => {
// Not waited on: the handle is not needed.
syscall::drop_cap(process);
println!("{} started", name);
}
Err(r) => refused(name, r),
}
}
/// Installs the app in folder `from` (its manifest and binary); the owner
/// is asked on the terminal.
fn install(sh: &mut Shell, from: &str) {
let Some(path) = sh.cwd.join(from) else {
return println!("install: {}: no such folder", from);
};
let Ok(source) = sh.fs.grant(path.as_bytes(), false) else {
return println!("install: {}: no such folder", from);
};
match sh.apps.install(source) {
Ok(0) => println!("installed"),
Ok(bits) => println!("installed, may use: {}", names(bits as usize)),
Err(r) => refused(from, r),
}
let _ = libkos::fs::release(source);
}
fn list(sh: &mut Shell) {
let mut text = [0u8; types::apps::PAGE_SIZE];
match sh.apps.list(&mut text) {
Ok(n) => print!("{}", core::str::from_utf8(&text[..n]).unwrap_or("")),
Err(r) => refused("apps", r),
}
}
/// `allow <app id> <capability>` or `revoke ...`.
fn change(sh: &mut Shell, args: &[&str], allow: bool) {
let verb = if allow { "allow" } else { "revoke" };
let (Some(id), Some(name)) = (args.get(1), args.get(2)) else {
return println!("usage: {} <app id> <capability>", verb);
};
let Some(cap) = Capability::from_name(name) else {
return println!("{}: {}: no such capability", verb, name);
};
let done = match allow {
true => sh.apps.allow(id, cap.index()),
false => sh.apps.revoke(id, cap.index()),
};
if let Err(r) = done {
refused(id, r);
}
}
fn cd(sh: &mut Shell, to: &str) {
match sh.cwd.join(to) {
Some(next) if sh.fs.list_dir(next.as_bytes(), |_| {}).is_ok() => sh.cwd = next,
_ => println!("cd: {}: no such directory", to),
}
}
fn run(sh: &mut Shell, line: &str) {
let mut args = [""; MAX_ARGS];
let mut n = 0;
for w in line.split_ascii_whitespace().take(MAX_ARGS) {
args[n] = w;
n += 1;
}
let args = &args[..n];
match args.first().copied() {
None => {}
Some("help") => {
println!("built-ins: help, echo <text>, clear, cd <dir>, pwd, logout");
println!("apps: apps, install <folder>, allow|revoke <app id> <capability>");
println!("anything else runs an app: try ls, cat readme.txt, snake");
println!("apps see the current directory, read-only, and only what you allowed");
println!("Ctrl+C stops a running program; snake quits with q");
println!("end with & to leave it running (snake &); F4 closes the app in front");
}
Some("apps") => list(sh),
Some("install") => match args.get(1) {
Some(from) => install(sh, from),
None => println!("usage: install <folder>"),
},
Some("allow") => change(sh, args, true),
Some("revoke") => change(sh, args, false),
Some("cd") => cd(sh, args.get(1).copied().unwrap_or("/")),
Some("pwd") => println!("{}", sh.cwd),
Some("echo") => println!("{}", Joined(&args[1..])),
Some("clear") => libkos::terminal::clear(TERMINAL),
// Init stops this shell and everything it ran, and the lock
// screen comes back.
Some("logout") => {
libkos::terminal::clear(TERMINAL);
if syscall::send_msg(SESSION, [types::spawn::SIGN_OUT, 0, 0, 0], None).is_err() {
println!("logout: cannot sign out");
return;
}
// No prompt over the lock screen: wait to be stopped.
loop {
let _ = syscall::wait(sh.done);
}
}
Some(_) => run_program(sh, args),
}
}
/// Words with single spaces between them.
struct Joined<'a>(&'a [&'a str]);
impl core::fmt::Display for Joined<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for (i, w) in self.0.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
f.write_str(w)?;
}
Ok(())
}
}
libkos::entry!(main);
fn main() {
// Everything this shell prints goes to the terminal.
libkos::start::set_log(Some(TERMINAL));
let Ok(done) = syscall::create_notification() else {
println!("shell: no notification to wait on");
return;
};
let Ok(fs) = Storage::attach(HOME, STORAGE_BUF_VA) else {
println!("shell: no storage");
return;
};
let Some(apps) = Apps::connect(PERMISSION, REQUEST_PAGE_VA) else {
println!("shell: no permission manager");
return;
};
if !libkos::terminal::on_interrupt(CONTROL, done, types::badge::Badge::new(INTERRUPTED)) {
println!("shell: Ctrl+C will not work");
}
let mut sh = Shell {
fs,
apps,
cwd: Cwd {
path: [0; types::storage::MAX_PATH],
len: 0,
},
done,
};
println!("k-os shell. Type 'help'.");
let mut buf = [0u8; 256];
loop {
print!("$ ");
let n = libkos::terminal::read_line(TERMINAL, &mut buf);
match core::str::from_utf8(&buf[..n]) {
Ok(line) => run(&mut sh, line.trim_end_matches('\n')),
Err(_) => println!("shell: not text"),
}
}
}

View File

@@ -0,0 +1,14 @@
[package]
name = "sleep"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "sleep"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }

View File

@@ -0,0 +1,6 @@
[app]
id = "kos.sleep"
name = "sleep"
version = "1.0.0"
binary = "sleep"
author = "K-OS"

View File

@@ -0,0 +1,30 @@
//! Waits: `sleep <seconds>`. Mostly something to stop with Ctrl+C.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
use libkos::{println, syscall};
use types::time::Ticks;
/// QEMU virt's timebase.
const TICKS_PER_SECOND: u64 = 10_000_000;
libkos::entry!(main);
fn main() {
let Some(seconds) = libkos::start::args().nth(1).and_then(|s| s.parse::<u64>().ok()) else {
println!("usage: sleep <seconds>");
return;
};
let Ok(alarm) = syscall::create_notification() else {
println!("sleep: no notification");
return;
};
let at = syscall::get_time().raw().saturating_add(seconds.saturating_mul(TICKS_PER_SECOND));
if syscall::set_timer(alarm, Ticks::from_raw(at)).is_ok() {
let _ = syscall::wait(alarm);
}
}

View File

@@ -3,44 +3,43 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![forbid(unsafe_code)]
extern crate libkos; extern crate libkos;
use core::sync::atomic::{AtomicUsize, Ordering}; use core::sync::atomic::{AtomicUsize, Ordering};
use libkos::syscall; use libkos::syscall;
use types::address::{EntryPoint, StackPointer, VirtualAddress}; use types::address::VirtualAddress;
use types::bounded::{CapSlot, PageCount}; use types::bounded::CapSlot;
use types::mapping::PagePerms; use types::mapping::PagePerms;
/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters. /// Counter page layout (words): 0 next free counter index, 1..=THREADS counters.
const COUNTERS_VA: usize = 0x0100_0000; const COUNTERS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
const THREADS: usize = 4; const THREADS: usize = 4;
#[unsafe(no_mangle)] fn main() {
extern "C" fn main() {
let page = CapSlot::const_new(0); let page = CapSlot::const_new(0);
if syscall::map_region(page, VirtualAddress::from_raw(COUNTERS_VA), PagePerms::ReadWrite).is_err() { let Ok(window) = libkos::window::map(page, COUNTERS_VA, PagePerms::ReadWrite) else {
libkos::println!("smptest: cannot map the counter page"); libkos::println!("smptest: cannot map the counter page");
syscall::exit(); syscall::exit();
} };
let words = window.atomics(types::address::ByteOffset::ZERO, 1 + THREADS);
for _ in 1..THREADS { for _ in 1..THREADS {
let Some(stack) = PageCount::new(1).and_then(syscall::request_pages) else { if !libkos::thread::spawn(words, spin) {
break; break;
}; }
let top = StackPointer::from_raw(stack.raw() + 4096);
syscall::spawn_thread(EntryPoint::from_raw(spin as *const () as usize), top, 0);
} }
spin(); spin(words);
} }
extern "C" fn spin() -> ! { libkos::entry!(main);
// SAFETY: COUNTERS_VA maps the counter page; every access is atomic.
let word = |i: usize| unsafe { &*((COUNTERS_VA + i * 8) as *const AtomicUsize) }; fn spin(word: &'static [AtomicUsize]) -> ! {
let mine = 1 + word(0).fetch_add(1, Ordering::Relaxed) % THREADS; let mine = 1 + word[0].fetch_add(1, Ordering::Relaxed) % THREADS;
loop { loop {
// Yield so a spinner sharing init's hart cannot starve it. // Yield so a spinner sharing init's hart cannot starve it.
for _ in 0..1024 { for _ in 0..1024 {
word(mine).fetch_add(1, Ordering::Relaxed); word[mine].fetch_add(1, Ordering::Relaxed);
} }
syscall::yield_now(); syscall::yield_now();
} }

View File

@@ -12,5 +12,4 @@ bench = false
types = { workspace = true } types = { workspace = true }
libkos = { workspace = true } libkos = { workspace = true }
display-server = { workspace = true } port-framebuffer = { workspace = true }
server-framework = { workspace = true }

View File

@@ -0,0 +1,9 @@
[app]
id = "kos.snake"
name = "Snake"
version = "1.0.0"
binary = "snake"
author = "K-OS"
[capabilities]
required = ["display"]

View File

@@ -44,6 +44,12 @@ pub struct Snake {
rng_state: u32, rng_state: u32,
} }
impl Default for Snake {
fn default() -> Self {
Self::new()
}
}
impl Snake { impl Snake {
pub fn new() -> Self { pub fn new() -> Self {
let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 }; let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 };

View File

@@ -1,4 +1,5 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
pub mod game; pub mod game;
pub mod renderer; pub mod renderer;

View File

@@ -1,90 +1,139 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#![no_main] #![no_main]
extern crate libkos; extern crate libkos;
use libkos::display::Canvas;
use libkos::syscall;
use port_framebuffer::Framebuffer;
use snake::game::{Direction, GameState, Snake}; use snake::game::{Direction, GameState, Snake};
use snake::renderer::{self, RenderCommand}; use snake::renderer::{self, RenderCommand};
use types::bounded::CapSlot; use types::bounded::CapSlot;
use types::display::{Color, Dimension, ScreenCoord}; use types::display::{Color, Dimension, ScreenCoord};
use types::time::Ticks; use types::time::Ticks;
use libkos::syscall;
use server_framework::Encode;
use display_server::protocol::DisplayRequest;
const INPUT_EP: CapSlot = CapSlot::const_new(0); /// Steps a second; QEMU virt's timebase is 10 MHz.
const DISPLAY_EP: CapSlot = CapSlot::const_new(1); const STEPS_PER_SECOND: u64 = 8;
const TICK_DELAY: u64 = 5_000_000; const TICK_DELAY: u64 = 10_000_000 / STEPS_PER_SECOND;
const MAX_RENDER_CMDS: usize = 140; const MAX_RENDER_CMDS: usize = 140;
fn display_send(req: &DisplayRequest) { fn draw_rect(canvas: &mut Canvas, x: u16, y: u16, w: u16, h: u16, color: u32) {
let _ = syscall::request(DISPLAY_EP, req.encode()); canvas.fill_rect(ScreenCoord::new(x), ScreenCoord::new(y), Dimension::new(w), Dimension::new(h), Color::from_raw(color));
} }
fn draw_rect(x: u16, y: u16, w: u16, h: u16, color: u32) { libkos::entry!(main);
display_send(&DisplayRequest::DrawRect {
x: ScreenCoord::new(x),
y: ScreenCoord::new(y),
w: Dimension::new(w),
h: Dimension::new(h),
color: Color::from_raw(color),
});
}
fn flush() { fn main() {
display_send(&DisplayRequest::Flush); // Its window, which the permission manager opened because snake's
} // manifest asks for the display. Input comes through it too.
let Some(mut canvas) = libkos::start::display().and_then(Canvas::attach) else {
#[unsafe(no_mangle)] libkos::println!("snake: needs the display (see its manifest)");
extern "C" fn main() { return;
};
let Ok(alarm) = syscall::create_notification() else {
libkos::println!("snake: no timer");
return;
};
// Takes focus; keys are polled every tick, the signal only wakes it.
let window = canvas.window();
let _ = libkos::display::subscribe(window, alarm, types::badge::Badge::new(1 << 1));
let mut clock = Clock::new(alarm);
let mut snake = Snake::new(); let mut snake = Snake::new();
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS]; let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS];
loop { loop {
let count = renderer::render(&snake, &mut commands); let count = renderer::render(&snake, &mut commands);
for i in 0..count { for cmd in &commands[..count] {
let cmd = &commands[i]; draw_rect(&mut canvas, cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
draw_rect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
} }
flush(); canvas.flush();
if snake.state() == GameState::GameOver { if snake.state() == GameState::GameOver {
let gw = renderer::grid_pixel_width(); let gw = renderer::grid_pixel_width();
let gh = renderer::grid_pixel_height(); let gh = renderer::grid_pixel_height();
draw_rect(gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFFCC0000); draw_rect(&mut canvas, gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFFCC0000);
flush(); canvas.flush();
loop { syscall::yield_now(); } // Any key leaves.
while next_input(window) == Input::None {
clock.wait_tick();
}
return;
} }
wait_tick(); clock.wait_tick();
handle_input(&mut snake); if handle_input(window, &mut snake) == Input::Quit {
return;
}
snake.tick(); snake.tick();
} }
} }
fn handle_input(snake: &mut Snake) { #[derive(PartialEq)]
// Drain all pending events, apply the latest direction enum Input {
None,
Quit,
Turn(Direction),
Other,
}
/// The next key, if one is waiting: q or Escape quits, and so does the
/// window going.
fn next_input(window: CapSlot) -> Input {
use libkos::display::Event;
use libkos::input::{InputEvent, Key};
match libkos::display::poll(window) {
None => Input::None,
Some(Event::Closed) => Input::Quit,
Some(Event::Input(InputEvent::KeyDown(k))) => match k {
Key::Escape | Key::Char(b'q') => Input::Quit,
Key::Up | Key::Char(b'w') => Input::Turn(Direction::Up),
Key::Down | Key::Char(b's') => Input::Turn(Direction::Down),
Key::Left | Key::Char(b'a') => Input::Turn(Direction::Left),
Key::Right | Key::Char(b'd') => Input::Turn(Direction::Right),
_ => Input::Other,
},
Some(_) => Input::Other,
}
}
/// Drains the pending keys, applying each turn; reports a quit.
fn handle_input(window: CapSlot, snake: &mut Snake) -> Input {
loop { loop {
let Ok([tag, kind, _, _]) = syscall::request(INPUT_EP, [0; 4]) else { match next_input(window) {
break; Input::None => return Input::None,
}; Input::Quit => return Input::Quit,
if tag == 0 { break; } // NoEvent — queue drained Input::Turn(d) => snake.set_direction(d),
if tag == 1 { Input::Other => {}
match kind {
0 => snake.set_direction(Direction::Up),
1 => snake.set_direction(Direction::Down),
2 => snake.set_direction(Direction::Left),
3 => snake.set_direction(Direction::Right),
_ => {}
}
} }
} }
} }
fn wait_tick() { /// The game's clock: a step every `TICK_DELAY`, counted from the last
let start = syscall::get_time(); /// step's deadline, so the time drawing takes does not slow it down.
let deadline = Ticks::from_raw(start.raw() + TICK_DELAY); struct Clock {
while syscall::get_time().raw() < deadline.raw() { alarm: CapSlot,
syscall::yield_now(); next: u64,
}
impl Clock {
fn new(alarm: CapSlot) -> Self {
Self { alarm, next: syscall::get_time().raw() }
}
/// Sleeps until the next step is due, instead of spinning. Keys
/// signal `alarm` too (snake has one notification), so it waits again
/// until the step really is due.
fn wait_tick(&mut self) {
// After a stall, carry on from now rather than rushing to catch up.
self.next = (self.next + TICK_DELAY).max(syscall::get_time().raw());
if syscall::set_timer(self.alarm, Ticks::from_raw(self.next)).is_err() {
return;
}
while syscall::get_time().raw() < self.next {
if syscall::wait(self.alarm).is_err() {
return;
}
}
} }
} }

View File

@@ -0,0 +1,15 @@
[package]
name = "spawntest"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "spawntest"
test = false
bench = false
[dependencies]
types = { workspace = true }
manifest = { workspace = true }
flaky = { path = "../flaky" }
libkos = { workspace = true }

View File

@@ -0,0 +1,424 @@
//! The test image's last check, started by init with a spawn allowance:
//! programs run through init, the allowance holds, and it comes back when
//! a child dies. Reports the whole self-test and halts.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
use libkos::fs::{FsError, Storage};
use libkos::spawn::{SpawnError, Spawned, spawn};
use libkos::syscall;
use types::bounded::CapSlot;
use types::budget::Limits;
/// Where init put them (see init's manifest: `uses`, storage (the disk,
/// the user's home), the spawn endpoint, then control).
const PERMISSION: CapSlot = CapSlot::const_new(0);
const FLAKY: CapSlot = CapSlot::const_new(1);
const STORAGE: CapSlot = CapSlot::const_new(2);
const HOME: CapSlot = CapSlot::const_new(3);
const INIT: CapSlot = CapSlot::const_new(4);
const CONTROL: CapSlot = CapSlot::const_new(5);
/// Where storage's buffers are mapped: the whole disk's, a narrowed one's.
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
const NARROW_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0190_0000);
const REQUEST_PAGE_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x01C0_0000);
const HOME_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x01B0_0000);
const CROWD_PAGE_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x01E0_0000);
/// Apps the old tables held beside Home; the test wants more than this.
const OLD_APP_ROOM: usize = 7;
/// Read back from the disk image by scripts/test-qemu.sh.
const NOTE: &[u8] = b"written by k-os on ext4\n";
/// Over half the allowance (128 pages), so a second one fits only after
/// the first has died and its limits came back.
const HELLO: Limits = Limits { pages: 80, endpoints: 0, notifications: 0, regions: 0 };
/// Spawns the program at `path`, read through a grant made just for it.
fn spawn_file(fs: &mut Storage, path: &[u8], limits: Limits) -> Result<Spawned, SpawnError> {
let file = fs.grant(path, false).map_err(|_| SpawnError::Failed)?;
let child = spawn(INIT, file, limits);
let _ = libkos::fs::release(file);
child
}
/// Spawns hello, runs it, and waits for it to die; hands it `grant`
/// first, if there is one.
fn run_hello(fs: &mut Storage, died: CapSlot, grant: Option<CapSlot>) -> Result<(), &'static str> {
let child = spawn_file(fs, b"bin/hello", HELLO).map_err(|_| "init would not spawn hello")?;
let call = types::permission::PermissionSet::empty().with(types::permission::Permission::Execute);
if grant.is_some_and(|g| child.give(g, call).is_none()) {
return Err("a storage grant could not be passed on");
}
syscall::watch_death(child.process, died).map_err(|_| "cannot watch hello")?;
if !child.start() {
return Err("cannot start hello");
}
syscall::wait(died).map_err(|_| "wait failed")?;
Ok(())
}
/// A file written through storage, which the host then checks with e2fsck.
fn write_a_file(fs: &mut Storage) -> Result<(), &'static str> {
// Left by an earlier boot of the same disk.
let _ = fs.delete(b"selftest.txt");
let h = fs.create(b"selftest.txt").map_err(|_| "cannot create a file")?;
if fs.write(h, NOTE) != Ok(NOTE.len()) {
return Err("short write");
}
fs.close(h).map_err(|_| "close failed")?;
let h = fs.open(b"selftest.txt").map_err(|_| "cannot reopen the file")?;
let mut back = [0u8; 64];
let n = fs.read(h, &mut back).map_err(|_| "read failed")?;
fs.close(h).map_err(|_| "close failed")?;
if &back[..n] != NOTE {
return Err("read back something else");
}
Ok(())
}
/// A read-only grant to one directory reaches nothing else and changes
/// nothing, can be passed on, and is gone everywhere once released.
fn narrow_storage(fs: &mut Storage, died: CapSlot) -> Result<(), &'static str> {
let grant = fs.grant(b"lost+found", false).map_err(|_| "storage refused a grant")?;
let mut inner = Storage::attach(grant, NARROW_BUF_VA).map_err(|_| "cannot attach through the grant")?;
inner.list_dir(b"", |_| {}).map_err(|_| "cannot list its own directory")?;
if inner.open(b"../selftest.txt").err() != Some(FsError::Denied) {
return Err("a grant reached outside its directory");
}
if inner.create(b"x").err() != Some(FsError::Denied) {
return Err("a read-only grant created a file");
}
let widened = inner.grant(b"", true).and_then(|g| Storage::attach(g, types::address::VirtualAddress::from_raw(NARROW_BUF_VA.raw() + 0x10_0000)));
if widened.and_then(|mut w| w.create(b"x")).is_ok() {
return Err("a grant was widened to write");
}
run_hello(fs, died, Some(grant))?;
libkos::fs::release(grant).map_err(|_| "release failed")?;
if inner.list_dir(b"", |_| {}).is_ok() {
return Err("a released grant still works");
}
Ok(())
}
/// An app runs through the permission manager with what it was granted;
/// revoking that stops it, and it cannot run again until allowed.
fn revoke_stops_a_running_app(died: CapSlot) -> Result<(), &'static str> {
use manifest::Capability;
let apps = libkos::apps::Apps::connect(PERMISSION, REQUEST_PAGE_VA).ok_or("no permission manager")?;
let display = Capability::Display.index();
let (snake, granted) = apps.launch(&["snake"], None, None).map_err(|_| "snake would not launch")?;
if granted & manifest::Capabilities::NONE.with(Capability::Display).bits() == 0 {
return Err("snake runs without the display it was granted");
}
syscall::watch_death(snake, died).map_err(|_| "cannot watch snake")?;
apps.revoke("kos.snake", display).map_err(|_| "revoke failed")?;
syscall::wait(died).map_err(|_| "wait failed")?;
syscall::drop_cap(snake);
let refused = apps.launch(&["snake"], None, None);
if !matches!(refused, Err(r) if r.code == types::apps::reply::MISSING) {
return Err("snake ran without the display it requires");
}
apps.allow("kos.snake", display).map_err(|_| "allow failed")?;
let (snake, _) = apps.launch(&["snake"], None, None).map_err(|_| "snake did not run once allowed again")?;
syscall::watch_death(snake, died).map_err(|_| "cannot watch snake")?;
syscall::kill(snake);
syscall::wait(died).map_err(|_| "wait failed")?;
syscall::drop_cap(snake);
Ok(())
}
/// The user's home is sealed: seeded from the factory, and what is written
/// there one boot reads back the next. Theirs is the only folder that opens.
fn encrypted_home(fs: &mut Storage, home_fs: &mut Storage) -> Result<(), &'static str> {
let mut home = [0u8; 512];
let mut factory = [0u8; 512];
let read = |fs: &mut Storage, path: &[u8], out: &mut [u8]| -> Result<usize, &'static str> {
let h = fs.open(path).map_err(|_| "cannot open")?;
let n = fs.read(h, out).map_err(|_| "cannot read (a seal broken?)")?;
let _ = fs.close(h);
Ok(n)
};
let n = read(home_fs, b"readme.txt", &mut home)?;
let m = read(fs, b"factory/home/readme.txt", &mut factory)?;
if home[..n] != factory[..m] {
return Err("the encrypted home does not hold the factory's readme");
}
libkos::println!("spawntest: home seeded from the factory, reads back through its seal: ok");
const NOTE: &[u8] = b"sealed across a reboot";
let mut back = [0u8; 64];
match read(home_fs, b"persist.txt", &mut back) {
Ok(n) if &back[..n] == NOTE => libkos::println!("spawntest: an encrypted file from the last boot reads back: ok"),
Ok(_) => return Err("an encrypted file from the last boot reads back wrong"),
Err(_) => {
let h = home_fs.create(b"persist.txt").map_err(|_| "cannot create in home")?;
let wrote = home_fs.write(h, NOTE) == Ok(NOTE.len());
let _ = home_fs.close(h);
if !wrote {
return Err("cannot write in home");
}
libkos::println!("spawntest: wrote an encrypted file for the next boot");
}
}
// A made-up user's: nobody may reach it, the whole disk's holder neither.
if fs.create(b"users/0123456789abcdef/home/planted").is_ok() || fs.mkdir(b"users/0123456789abcdef").is_ok() {
return Err("someone else's folder could be written");
}
let mut folders = 0;
fs.list_dir(b"users", |e| folders += usize::from(e.name() != b"." && e.name() != b"..")).map_err(|_| "cannot list the users")?;
if folders != 1 {
return Err("not exactly one user's folder");
}
libkos::println!("spawntest: only the signed-in user's folder opens: ok");
Ok(())
}
/// Floating-point work that only comes out right if nobody else's
/// registers get in: a sum over `ROUNDS`, yielding (so switching threads)
/// all the way.
fn float_work(step: f64) -> u64 {
const ROUNDS: u32 = 2000;
let mut sum = 0.0f64;
let mut x = core::hint::black_box(step);
for _ in 0..ROUNDS {
sum += x * 0.5;
x += step;
syscall::yield_now();
}
sum.to_bits()
}
/// What `float_work(step)` gives when nothing interferes.
fn float_expected(step: f64) -> u64 {
let mut sum = 0.0f64;
let mut x = step;
for _ in 0..2000 {
sum += x * 0.5;
x += step;
}
sum.to_bits()
}
/// The other thread's answer, once it has one.
static FLOAT_RESULT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
fn float_thread(_: &'static core::sync::atomic::AtomicU64) -> ! {
FLOAT_RESULT.store(float_work(0.25), core::sync::atomic::Ordering::Release);
syscall::exit_thread()
}
/// Two threads computing with floats at once each get their own registers
/// (the kernel saves and restores them on every switch).
fn floating_point_is_kept_per_thread() -> Result<(), &'static str> {
if !libkos::thread::spawn(&FLOAT_RESULT, float_thread) {
return Err("cannot start a thread");
}
let mine = float_work(1.75);
while FLOAT_RESULT.load(core::sync::atomic::Ordering::Acquire) == 0 {
syscall::yield_now();
}
let theirs = FLOAT_RESULT.load(core::sync::atomic::Ordering::Acquire);
if mine != float_expected(1.75) || theirs != float_expected(0.25) {
return Err("floating-point registers leaked between threads");
}
Ok(())
}
/// How many requests the `flaky` answering now has served.
fn flaky() -> Result<usize, &'static str> {
syscall::request(FLAKY, [flaky::op::WHO, 0, 0, 0]).map(|r| r[0]).map_err(|_| "flaky does not answer")
}
/// A server that dies is started again on the same endpoint: the caller's
/// capability reaches the new one. Twice in a row, the second waits.
fn restarted_after_crashes() -> Result<(), &'static str> {
if flaky()? != 1 || flaky()? != 2 {
return Err("flaky counts wrong");
}
// Dies mid-call: the call comes back with an error.
let _ = syscall::request(FLAKY, [flaky::op::CRASH, 0, 0, 0]);
if flaky()? != 1 {
return Err("the crashed server still answers");
}
let crashed = syscall::get_time().raw();
let _ = syscall::request(FLAKY, [flaky::op::CRASH, 0, 0, 0]);
let fresh = flaky()? == 1;
// QEMU virt's timebase is 10 MHz.
let waited_ms = (syscall::get_time().raw() - crashed) / 10_000;
if !fresh || waited_ms < 900 {
return Err("a second crash in a row was not backed off");
}
Ok(())
}
/// A server stuck in a handler is noticed by the watchdog, killed (which
/// releases its caller) and restarted.
fn restarted_after_hanging() -> Result<(), &'static str> {
let asked = syscall::get_time().raw();
// Comes back, with an error, once the hung server is killed.
let _ = syscall::request(FLAKY, [flaky::op::HANG, 0, 0, 0]);
let hung_ms = (syscall::get_time().raw() - asked) / 10_000;
if hung_ms < 4_500 {
return Err("the hung server was stopped before the watchdog's deadline");
}
if flaky()? != 1 {
return Err("the hung server was not restarted");
}
Ok(())
}
/// Waits about `ms` on `alarm` (woken early by anything else it carries).
fn nap(alarm: CapSlot, ms: u64) {
let at = syscall::get_time().raw() + ms * types::time::TICKS_PER_MS;
if syscall::set_timer(alarm, types::time::Ticks::from_raw(at)).is_ok() {
let _ = syscall::wait(alarm);
}
}
/// Apps run until the permission manager says there is no room (busy, not
/// a failure), well past what the old tables held; once they are stopped,
/// the room comes back, all of it.
fn many_apps_and_their_room_comes_back(alarm: CapSlot) -> Result<usize, &'static str> {
use types::apps::reply;
let apps = libkos::apps::Apps::connect(PERMISSION, CROWD_PAGE_VA).ok_or("no permission manager")?;
let mut running = [None; 32];
let mut n = 0;
let refusal = loop {
match apps.launch(&["sleep", "300"], None, None) {
Ok((p, _)) if n < running.len() => {
running[n] = Some(p);
n += 1;
}
Ok(_) => return Err("more room than the test can hold"),
Err(r) => break r.code,
}
};
let stop = |running: &mut [Option<CapSlot>]| {
for p in running.iter_mut().filter_map(Option::take) {
let _ = syscall::kill(p);
syscall::drop_cap(p);
}
};
stop(&mut running);
if refusal != reply::BUSY {
return Err("a full permission manager did not say it was busy");
}
if n <= OLD_APP_ROOM {
return Err("no more apps ran than the old tables held");
}
// The deaths are its to hear; give it a moment, then all of it again.
let mut again = 0;
for _ in 0..100 {
match apps.launch(&["sleep", "300"], None, None) {
Ok((p, _)) => {
running[again] = Some(p);
again += 1;
if again == n {
break;
}
}
Err(r) if r.code == reply::BUSY => nap(alarm, 50),
Err(_) => {
stop(&mut running);
return Err("launching again failed");
}
}
}
stop(&mut running);
if again < n {
return Err("the room did not all come back");
}
Ok(n)
}
/// More files open at once than the old tables held (8 in ext4).
fn many_files_open_at_once(fs: &mut Storage) -> Result<(), &'static str> {
let mut open = [None; 30];
let mut result = Ok(());
for slot in open.iter_mut() {
match fs.open(b"selftest.txt") {
Ok(h) => *slot = Some(h),
Err(_) => {
result = Err("cannot hold 30 files open");
break;
}
}
}
for h in open.into_iter().flatten() {
let _ = fs.close(h);
}
result
}
/// A server that runs off its stack faults on the guard below it, and is
/// restarted like any other that died.
fn restarted_after_overflowing() -> Result<(), &'static str> {
let _ = syscall::request(FLAKY, [flaky::op::OVERFLOW, 0, 0, 0]);
if flaky()? != 1 {
return Err("the overflowed server was not restarted");
}
Ok(())
}
fn check() -> Result<(), &'static str> {
let mut fs = Storage::attach(STORAGE, STORAGE_BUF_VA).map_err(|_| "cannot attach to storage")?;
write_a_file(&mut fs)?;
libkos::println!("spawntest: a file written to ext4 reads back: ok");
let mut home = Storage::attach(HOME, HOME_BUF_VA).map_err(|_| "cannot attach to home")?;
encrypted_home(&mut fs, &mut home)?;
let died = syscall::create_notification().map_err(|_| "no notification")?;
narrow_storage(&mut fs, died)?;
libkos::println!("spawntest: storage grants stay in scope, pass on, release: ok");
run_hello(&mut fs, died, None)?;
libkos::println!("spawntest: spawned program runs and its death is heard: ok");
let greedy = Limits { pages: 200, ..HELLO };
if spawn_file(&mut fs, b"bin/hello", greedy).err() != Some(SpawnError::OverAllowance) {
return Err("init spawned past the allowance");
}
let tiny = Limits { pages: 1, ..HELLO };
if spawn_file(&mut fs, b"bin/nothere", tiny).err() != Some(SpawnError::NotFound) {
return Err("a missing program was not reported");
}
libkos::println!("spawntest: allowance and missing files refused: ok");
run_hello(&mut fs, died, None)?;
libkos::println!("spawntest: allowance comes back when a child dies: ok");
revoke_stops_a_running_app(died)?;
libkos::println!("spawntest: revoking stops a running app, allowing lets it run: ok");
floating_point_is_kept_per_thread()?;
libkos::println!("spawntest: floating point is kept per thread: ok");
restarted_after_crashes()?;
libkos::println!("spawntest: a crashed server is restarted, a crash loop backs off: ok");
restarted_after_hanging()?;
libkos::println!("spawntest: a hung server is noticed, stopped and restarted: ok");
many_files_open_at_once(&mut fs)?;
libkos::println!("spawntest: 30 files open at once: ok");
let crowd = many_apps_and_their_room_comes_back(died)?;
libkos::println!("spawntest: {} apps at once, then busy, and the room comes back: ok", crowd);
restarted_after_overflowing()?;
libkos::println!("spawntest: a server that overflows its stack is caught and restarted: ok");
Ok(())
}
libkos::entry!(main);
fn main() {
match check() {
Ok(()) => libkos::println!("SMPTEST PASS"),
Err(why) => libkos::println!("SMPTEST FAIL: {}", why),
}
syscall::halt(CONTROL);
}

View File

@@ -0,0 +1,13 @@
[package]
name = "tally"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "tally"
test = false
bench = false
[dependencies]
libkos = { workspace = true }
k-ui = { workspace = true }

View File

@@ -0,0 +1,13 @@
[app]
id = "kos.tally"
name = "Tally"
version = "1.0.0"
binary = "tally"
author = "K-OS"
memory = "3 MiB"
[capabilities]
required = ["display"]
[appearance]
accent = "#e91e63"

View File

@@ -0,0 +1,61 @@
//! A count, and buttons to change it: the first K-UI app. Tab and the
//! arrows move between the buttons, Enter presses, Escape leaves.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate alloc;
extern crate libkos;
use alloc::format;
use k_ui::{App, ButtonVariant, Element, Spacing, TextColour, TextStyle, button, column, row, text};
#[derive(Clone, Copy)]
enum Event {
Add,
Subtract,
Reset,
}
struct Tally(i64);
impl App for Tally {
type Event = Event;
fn init() -> Self {
Tally(0)
}
fn update(&mut self, event: Event) {
match event {
Event::Add => self.0 += 1,
Event::Subtract => self.0 -= 1,
Event::Reset => self.0 = 0,
}
}
fn view(&self) -> Element<Event> {
let mut buttons = row()
.gap(Spacing::Md)
.child(button("Subtract", Event::Subtract))
.child(button("Add", Event::Add).variant(ButtonVariant::Primary));
if self.0 != 0 {
buttons = buttons.child(button("Reset", Event::Reset).variant(ButtonVariant::Destructive));
}
column()
.padding(Spacing::Xl)
.gap(Spacing::Lg)
.child(text("Tally").style(TextStyle::Heading))
.child(text(format!("{}", self.0)).style(TextStyle::Subheading).colour(TextColour::Accent))
.child(buttons)
.child(text("Tab moves, Enter presses, Esc leaves").style(TextStyle::Caption).colour(TextColour::Secondary))
.into()
}
}
libkos::entry!(main);
fn main() {
k_ui::run::<Tally>();
}

View File

@@ -1,29 +1,96 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#![no_main] #![no_main]
extern crate libkos; extern crate libkos;
use types::bounded::CapSlot; use types::bounded::CapSlot;
#[unsafe(no_mangle)] /// Where init put them (see init's manifest: own endpoint, then `uses`).
extern "C" fn main() { const EP: CapSlot = CapSlot::const_new(0);
let ep = CapSlot::const_new(0); const INPUT: CapSlot = CapSlot::const_new(1);
/// `themes/`, read-only.
const THEMES: CapSlot = CapSlot::const_new(2);
/// Where storage's buffer is mapped while the themes are read.
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x1800_0000);
/// The longest theme file read.
const THEME_BYTES: usize = 4096;
libkos::entry!(main);
fn main() {
// Init hands over the GPU, or nothing if the machine has none. // Init hands over the GPU, or nothing if the machine has none.
let dev = libkos::device::receive_device(ep); let dev = libkos::device::receive_device(EP);
let input = subscribe_to_input();
if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) { if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) {
run_virtio(fb, ep); run_virtio(fb, input);
} }
libkos::println!("display: no GPU, running headless"); libkos::println!("display: no GPU, running headless");
run_headless(ep); run_headless(input);
}
/// Has the input server signal this thread whenever events arrive; the
/// server then passes them on to the app in focus.
fn subscribe_to_input() -> Option<CapSlot> {
use types::badge::Badge;
use types::permission::{Permission, PermissionSet};
let notify = libkos::syscall::create_notification().ok()?;
libkos::syscall::bind_notification(notify).ok()?;
let signal = PermissionSet::empty().with(Permission::Write).with(Permission::Delegate);
let copy = libkos::syscall::mint(notify, Badge::NONE, signal).ok()?;
let sent = libkos::syscall::call(INPUT, [types::input::SUBSCRIBE, 0, 0, 0], Some(copy.into()));
libkos::syscall::drop_cap(copy);
sent.ok().map(|_| INPUT)
}
fn server<F: port_framebuffer::Framebuffer>(fb: F, input: Option<CapSlot>) -> display_server::server::DisplayServer<F, display_server::host::KernelHost> {
display_server::server::DisplayServer::new(fb, display_server::host::KernelHost::new(EP, input), themes())
}
/// Every theme in `themes/` that reads and passes contrast; the built-in
/// one if none does.
fn themes() -> display_server::themes::Themes {
let mut themes = display_server::themes::Themes::new();
let Ok(mut fs) = libkos::fs::Storage::attach(THEMES, STORAGE_BUF_VA) else {
libkos::println!("display: no themes folder; the built-in theme only");
return themes;
};
let mut names = [([0u8; types::storage::DIR_NAME_SIZE], 0usize); display_server::themes::MAX_THEMES];
let mut found = 0;
let _ = fs.list_dir(b"", |e| {
if !e.is_dir && e.name().ends_with(b".toml") && found < names.len() {
names[found] = (e.name, e.name_len);
found += 1;
}
});
let mut text = [0u8; THEME_BYTES];
for (name, len) in &names[..found] {
let name = &name[..*len];
let read = fs.open(name).and_then(|h| {
let n = fs.read(h, &mut text);
let _ = fs.close(h);
n
});
let shown = core::str::from_utf8(name).unwrap_or("?");
match read.ok().and_then(|n| core::str::from_utf8(&text[..n]).ok()) {
Some(t) => {
if let Err(e) = themes.add(t) {
libkos::println!("display: theme {} refused: {:?}", shown, e);
}
}
None => libkos::println!("display: cannot read theme {}", shown),
}
}
fs.detach();
// Storage has few connections to give; this one is done with.
let _ = libkos::fs::release(THEMES);
themes
} }
#[inline(never)] #[inline(never)]
fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot) -> ! { fn run_virtio(fb: driver_framebuffer_virtio::VirtioFramebuffer, input: Option<CapSlot>) -> ! {
use port_framebuffer::Framebuffer; let mut srv = server(fb, input);
fb.clear(types::display::Color::rgb(30, 30, 60)); server_framework::serve(&mut srv, EP)
fb.flush();
let mut srv = display_server::server::DisplayServer::new(fb);
server_framework::serve(&mut srv, ep)
} }
/// No GPU: clients still get answers (and the dimensions they draw for); /// No GPU: clients still get answers (and the dimensions they draw for);
@@ -49,10 +116,18 @@ impl port_framebuffer::Framebuffer for Headless {
} }
fn clear(&mut self, _: types::display::Color) {} fn clear(&mut self, _: types::display::Color) {}
fn flush(&mut self) {} fn flush(&mut self) {}
fn scroll_up(
&mut self,
_: types::display::ScreenCoord,
_: types::display::Dimension,
_: types::display::Dimension,
_: types::display::Color,
) {
}
} }
#[inline(never)] #[inline(never)]
fn run_headless(ep: CapSlot) -> ! { fn run_headless(input: Option<CapSlot>) -> ! {
let mut srv = display_server::server::DisplayServer::new(Headless); let mut srv = server(Headless, input);
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, EP)
} }

View File

@@ -1,26 +1,37 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#![no_main] #![no_main]
extern crate libkos; extern crate libkos;
use driver_input_virtio::VirtioInputDevice;
use input_server::server::{InputServer, MAX_DEVICES, Pair};
use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO}; use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO};
use port_input_device::InputDevice; use port_input_device::InputDevice;
use types::bounded::CapSlot; use types::bounded::CapSlot;
#[unsafe(no_mangle)] libkos::entry!(main);
extern "C" fn main() {
fn main() {
let ep = CapSlot::new(0).unwrap(); let ep = CapSlot::new(0).unwrap();
// Init hands over the keyboard (virtio) or, failing that, the UART. // Init hands over the input devices, two (a keyboard, a tablet), each
let dev = device::receive_device(ep); // virtio or, failing a keyboard, the UART; either may be none.
match (dev.kind, dev.mmio) { let first = device::receive_device(ep);
let second = device::receive_device(ep);
let irqs = [first.irq, second.irq];
let tablet = match (second.kind, second.mmio) {
(KIND_VIRTIO, Some(mmio)) => VirtioInputDevice::try_new(mmio, 1),
_ => None,
};
match (first.kind, first.mmio) {
(KIND_VIRTIO, Some(mmio)) => { (KIND_VIRTIO, Some(mmio)) => {
if let Some(d) = driver_input_virtio::VirtioInputDevice::try_new(mmio) { if let Some(d) = VirtioInputDevice::try_new(mmio, 0) {
run(d, dev.irq, ep); run(Pair { first: d, second: tablet }, irqs, ep);
} }
} }
(KIND_UART_16550, Some(mmio)) => { (KIND_UART_16550, Some(mmio)) => {
if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) { if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) {
run(d, dev.irq, ep); run(Pair { first: d, second: tablet }, irqs, ep);
} }
} }
_ => {} _ => {}
@@ -29,12 +40,18 @@ extern "C" fn main() {
libkos::syscall::exit(); libkos::syscall::exit();
} }
/// Serves `device`, woken by its interrupt when it has one. /// Serves `device`, woken by its interrupts when it has them.
#[inline(never)] #[inline(never)]
fn run<D: InputDevice>(device: D, irq: Option<CapSlot>, ep: CapSlot) -> ! { fn run<D: InputDevice>(device: D, irqs: [Option<CapSlot>; MAX_DEVICES], ep: CapSlot) -> ! {
let mut srv = match irq.filter(|irq| device::bind_irq(*irq).is_ok()) { let mut lines = [CapSlot::const_new(0); MAX_DEVICES];
Some(irq) => input_server::server::InputServer::with_irq(device, irq), let mut n = 0;
None => input_server::server::InputServer::new(device), for irq in irqs.into_iter().flatten() {
lines[n] = irq;
n += 1;
}
let mut srv = match n > 0 && device::bind_irqs(&lines[..n]).is_ok() {
true => InputServer::with_irqs(device, irqs),
false => InputServer::new(device),
}; };
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, ep)
} }

View File

@@ -1,9 +1,11 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#![no_main] #![no_main]
extern crate libkos; extern crate libkos;
#[unsafe(no_mangle)] libkos::entry!(main);
extern "C" fn main() {
fn main() {
storage_server::run(); storage_server::run();
} }

View File

@@ -16,6 +16,16 @@ struct Node {
next: Option<u16>, next: Option<u16>,
} }
impl Node {
const EMPTY: Self = Self {
live: false,
parent: None,
first_child: None,
prev: None,
next: None,
};
}
pub struct CapTree<const N: usize> { pub struct CapTree<const N: usize> {
nodes: [Node; N], nodes: [Node; N],
} }
@@ -31,9 +41,9 @@ fn id(i: usize) -> Option<u16> {
} }
impl<const N: usize> CapTree<N> { impl<const N: usize> CapTree<N> {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
nodes: [Node::default(); N], nodes: [Node::EMPTY; N],
} }
} }

View File

@@ -93,14 +93,14 @@ impl Endpoint {
pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 }; pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 };
pub struct EndpointTable { pub struct EndpointTable {
endpoints: [Option<Endpoint>; MAX_ENDPOINTS], endpoints: [crate::slot::Slot<Endpoint>; MAX_ENDPOINTS],
generations: SlotGenerations<MAX_ENDPOINTS>, generations: SlotGenerations<MAX_ENDPOINTS>,
} }
impl EndpointTable { impl EndpointTable {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
endpoints: [const { None }; MAX_ENDPOINTS], endpoints: [const { crate::slot::Slot::Empty }; MAX_ENDPOINTS],
generations: SlotGenerations::new(), generations: SlotGenerations::new(),
} }
} }
@@ -111,9 +111,9 @@ impl EndpointTable {
.endpoints .endpoints
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?; .find(|(i, s)| !s.is_full() && generations.is_usable(*i))?;
let id = EndpointId::from_raw(idx as u16); let id = EndpointId::from_raw(idx as u16);
*slot = Some(Endpoint::new(id, owner)); slot.set(Endpoint::new(id, owner));
Some(id) Some(id)
} }
@@ -124,13 +124,13 @@ impl EndpointTable {
} }
pub fn iter(&self) -> impl Iterator<Item = &Endpoint> { pub fn iter(&self) -> impl Iterator<Item = &Endpoint> {
self.endpoints.iter().flatten() self.endpoints.iter().filter_map(crate::slot::Slot::as_ref)
} }
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ { pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
self.endpoints self.endpoints
.iter() .iter()
.flatten() .filter_map(crate::slot::Slot::as_ref)
.filter(move |ep| ep.owner == owner) .filter(move |ep| ep.owner == owner)
.map(|ep| ep.id) .map(|ep| ep.id)
} }

View File

@@ -10,7 +10,7 @@ pub struct WaitQueue<const N: usize = MAX_WAITERS> {
} }
impl<const N: usize> WaitQueue<N> { impl<const N: usize> WaitQueue<N> {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
entries: [None; N], entries: [None; N],
len: 0, len: 0,

View File

@@ -6,21 +6,44 @@ use types::generation::Generation;
#[path = "generations_tests.rs"] #[path = "generations_tests.rs"]
mod tests; mod tests;
/// One slot's counter. A fresh one is all zero bytes (not retired,
/// generation 0), so tables of them cost nothing in the kernel image.
#[derive(Clone, Copy)]
struct Counter {
retired: bool,
generation: Generation,
}
impl Counter {
const FRESH: Self = Self {
retired: false,
generation: Generation::initial(),
};
const RETIRED: Self = Self {
retired: true,
generation: Generation::initial(),
};
fn get(self) -> Option<Generation> {
(!self.retired).then_some(self.generation)
}
}
/// Bumped on every destroy or revoke so older capabilities stop matching; a slot whose counter runs out is retired. /// Bumped on every destroy or revoke so older capabilities stop matching; a slot whose counter runs out is retired.
pub struct SlotGenerations<const N: usize> { pub struct SlotGenerations<const N: usize> {
gens: [Option<Generation>; N], gens: [Counter; N],
} }
impl<const N: usize> SlotGenerations<N> { impl<const N: usize> SlotGenerations<N> {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
gens: [Some(Generation::initial()); N], gens: [Counter::FRESH; N],
} }
} }
/// Current generation of `idx`, `None` if retired or out of range. /// Current generation of `idx`, `None` if retired or out of range.
pub fn current(&self, idx: usize) -> Option<Generation> { pub fn current(&self, idx: usize) -> Option<Generation> {
*self.gens.get(idx)? self.gens.get(idx)?.get()
} }
pub fn is_usable(&self, idx: usize) -> bool { pub fn is_usable(&self, idx: usize) -> bool {
@@ -30,7 +53,13 @@ impl<const N: usize> SlotGenerations<N> {
/// Invalidates every capability minted for the current generation. /// Invalidates every capability minted for the current generation.
pub fn advance(&mut self, idx: usize) { pub fn advance(&mut self, idx: usize) {
if let Some(g) = self.gens.get_mut(idx) { if let Some(g) = self.gens.get_mut(idx) {
*g = g.and_then(|g| g.next()); *g = match g.get().and_then(|g| g.next()) {
Some(generation) => Counter {
retired: false,
generation,
},
None => Counter::RETIRED,
};
} }
} }
} }
@@ -43,7 +72,7 @@ impl<const N: usize> Default for SlotGenerations<N> {
#[cfg(kani)] #[cfg(kani)]
mod proofs { mod proofs {
use super::SlotGenerations; use super::{Counter, SlotGenerations};
use types::generation::Generation; use types::generation::Generation;
/// However many times a slot is advanced, a generation observed earlier /// However many times a slot is advanced, a generation observed earlier
@@ -53,9 +82,12 @@ mod proofs {
fn an_observed_generation_never_returns() { fn an_observed_generation_never_returns() {
let mut g: SlotGenerations<1> = SlotGenerations::new(); let mut g: SlotGenerations<1> = SlotGenerations::new();
g.gens[0] = if kani::any() { g.gens[0] = if kani::any() {
Some(Generation::from_raw(kani::any())) Counter {
retired: false,
generation: Generation::from_raw(kani::any()),
}
} else { } else {
None Counter::RETIRED
}; };
let observed = g.current(0); let observed = g.current(0);
let steps: u8 = kani::any(); let steps: u8 = kani::any();
@@ -73,7 +105,7 @@ mod proofs {
#[kani::unwind(5)] #[kani::unwind(5)]
fn retired_slots_stay_retired() { fn retired_slots_stay_retired() {
let mut g: SlotGenerations<1> = SlotGenerations::new(); let mut g: SlotGenerations<1> = SlotGenerations::new();
g.gens[0] = None; g.gens[0] = Counter::RETIRED;
let steps: u8 = kani::any(); let steps: u8 = kani::any();
kani::assume(steps <= 4); kani::assume(steps <= 4);
for _ in 0..steps { for _ in 0..steps {

View File

@@ -27,7 +27,10 @@ fn out_of_range_slots_are_never_usable() {
#[test] #[test]
fn exhausted_counter_retires_the_slot() { fn exhausted_counter_retires_the_slot() {
let mut g: SlotGenerations<1> = SlotGenerations::new(); let mut g: SlotGenerations<1> = SlotGenerations::new();
g.gens[0] = Some(Generation::from_raw(u32::MAX)); g.gens[0] = super::Counter {
retired: false,
generation: Generation::from_raw(u32::MAX),
};
g.advance(0); g.advance(0);
assert_eq!(g.current(0), None); assert_eq!(g.current(0), None);
assert!(!g.is_usable(0)); assert!(!g.is_usable(0));

View File

@@ -26,7 +26,7 @@ pub struct IrqRoutingTable {
} }
impl IrqRoutingTable { impl IrqRoutingTable {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
routes: [None; MAX_IRQS], routes: [None; MAX_IRQS],
} }

View File

@@ -1,4 +1,5 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
// A kernel panic takes the whole system down, so kernel code may not // A kernel panic takes the whole system down, so kernel code may not
// contain any way to panic. Tests and proofs are exempt. // contain any way to panic. Tests and proofs are exempt.
#![cfg_attr( #![cfg_attr(
@@ -30,5 +31,7 @@ pub mod notification;
pub mod process; pub mod process;
pub mod resource; pub mod resource;
pub mod scheduler; pub mod scheduler;
pub mod slot;
pub mod syscall; pub mod syscall;
pub mod thread; pub mod thread;
pub mod timer;

View File

@@ -1,6 +1,7 @@
//! Physical frame ownership. The kernel's single source of truth for what //! Physical frame ownership. The kernel's single source of truth for what
//! every frame of the user pool is used for and who is charged for it. //! every frame of the user pool is used for and who is charged for it.
use crate::slot::Slot;
use types::{ use types::{
address::{PhysicalAddress, VirtualAddress}, address::{PhysicalAddress, VirtualAddress},
id::{ProcessId, RegionId}, id::{ProcessId, RegionId},
@@ -18,7 +19,7 @@ mod proofs;
pub const PAGE_SIZE: usize = 4096; pub const PAGE_SIZE: usize = 4096;
/// Frames the kernel can track. The board's user pool must fit. /// Frames the kernel can track. The board's user pool must fit.
pub const MAX_FRAMES: usize = if cfg!(kani) { 6 } else { 4096 }; pub const MAX_FRAMES: usize = if cfg!(kani) { 6 } else { 114_688 };
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameUse { pub enum FrameUse {
@@ -48,24 +49,35 @@ impl FrameUse {
pub struct FrameTable<const N: usize = MAX_FRAMES> { pub struct FrameTable<const N: usize = MAX_FRAMES> {
base: PhysicalAddress, base: PhysicalAddress,
total: usize, total: usize,
frames: [Option<FrameUse>; N], frames: [Slot<FrameUse>; N],
free: usize, free: usize,
/// Where the next allocation starts looking: just past the last one. /// Where the next allocation starts looking: just past the last one.
cursor: usize, cursor: usize,
} }
impl<const N: usize> FrameTable<N> { impl<const N: usize> FrameTable<N> {
pub fn new(base: PhysicalAddress, total: usize) -> Self { pub const fn new(base: PhysicalAddress, total: usize) -> Self {
let total = total.min(N); let total = if total < N { total } else { N };
Self { Self {
base, base,
total, total,
frames: [None; N], frames: [Slot::Empty; N],
free: total, free: total,
cursor: 0, cursor: 0,
} }
} }
/// The pool, given at boot to a table made (in the kernel's static)
/// without one; only while nothing is allocated.
pub fn set_pool(&mut self, base: PhysicalAddress, total: usize) {
if self.free == self.total && self.live().iter().all(|f| !f.is_full()) {
self.base = base;
self.total = total.min(N);
self.free = self.total;
self.cursor = 0;
}
}
pub fn base(&self) -> PhysicalAddress { pub fn base(&self) -> PhysicalAddress {
self.base self.base
} }
@@ -92,11 +104,11 @@ impl<const N: usize> FrameTable<N> {
} }
/// The frames that exist (`total` is clamped to `N` at construction). /// The frames that exist (`total` is clamped to `N` at construction).
fn live(&self) -> &[Option<FrameUse>] { fn live(&self) -> &[Slot<FrameUse>] {
self.frames.get(..self.total).unwrap_or(&[]) self.frames.get(..self.total).unwrap_or(&[])
} }
fn live_mut(&mut self) -> &mut [Option<FrameUse>] { fn live_mut(&mut self) -> &mut [Slot<FrameUse>] {
self.frames.get_mut(..self.total).unwrap_or(&mut []) self.frames.get_mut(..self.total).unwrap_or(&mut [])
} }
@@ -113,8 +125,8 @@ impl<const N: usize> FrameTable<N> {
let start = self.cursor.min(total); let start = self.cursor.min(total);
let idx = (start..total) let idx = (start..total)
.chain(0..start) .chain(0..start)
.find(|&i| self.frames.get(i).is_some_and(Option::is_none))?; .find(|&i| self.frames.get(i).is_some_and(|f| !f.is_full()))?;
*self.frames.get_mut(idx)? = Some(usage); self.frames.get_mut(idx)?.set(usage);
self.free -= 1; self.free -= 1;
self.cursor = idx + 1; self.cursor = idx + 1;
Some(self.address_of(idx)) Some(self.address_of(idx))
@@ -132,7 +144,7 @@ impl<const N: usize> FrameTable<N> {
let mut run = 0; let mut run = 0;
let mut start = None; let mut start = None;
for (idx, f) in self.live().iter().enumerate() { for (idx, f) in self.live().iter().enumerate() {
if f.is_some() { if f.is_full() {
run = 0; run = 0;
continue; continue;
} }
@@ -145,7 +157,7 @@ impl<const N: usize> FrameTable<N> {
let start = start?; let start = start?;
let run = self.live_mut().get_mut(start..start + count)?; let run = self.live_mut().get_mut(start..start + count)?;
for (i, f) in run.iter_mut().enumerate() { for (i, f) in run.iter_mut().enumerate() {
*f = Some(usage(i)); f.set(usage(i));
} }
self.free -= count; self.free -= count;
Some(self.address_of(start)) Some(self.address_of(start))
@@ -165,7 +177,7 @@ impl<const N: usize> FrameTable<N> {
match self match self
.index_of(pa) .index_of(pa)
.and_then(|i| self.frames.get_mut(i)) .and_then(|i| self.frames.get_mut(i))
.and_then(Option::as_mut) .and_then(Slot::as_mut)
{ {
Some(slot) => { Some(slot) => {
*slot = usage; *slot = usage;
@@ -205,14 +217,17 @@ impl<const N: usize> FrameTable<N> {
return None; return None;
} }
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?; let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
self.live().iter().enumerate().find_map(|(idx, f)| match f { self.live()
Some(FrameUse::User { .iter()
owner: o, .enumerate()
page: p, .find_map(|(idx, f)| match f.as_ref() {
perms, Some(FrameUse::User {
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)), owner: o,
_ => None, page: p,
}) perms,
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)),
_ => None,
})
} }
/// Every allocated frame and its use. /// Every allocated frame and its use.
@@ -231,7 +246,7 @@ impl<const N: usize> FrameTable<N> {
self.live() self.live()
.iter() .iter()
.enumerate() .enumerate()
.filter_map(move |(idx, f)| match f { .filter_map(move |(idx, f)| match f.as_ref() {
Some(FrameUse::User { owner: o, page, .. }) if *o == owner => Some(( Some(FrameUse::User { owner: o, page, .. }) if *o == owner => Some((
self.address_of(idx), self.address_of(idx),
VirtualAddress::from_raw(*page as usize * PAGE_SIZE), VirtualAddress::from_raw(*page as usize * PAGE_SIZE),
@@ -246,11 +261,11 @@ impl<const N: usize> FrameTable<N> {
let mut freed = 0; let mut freed = 0;
for slot in self.live_mut().iter_mut() { for slot in self.live_mut().iter_mut() {
let owned = matches!( let owned = matches!(
slot, slot.as_ref(),
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner
); );
if owned { if owned {
*slot = None; *slot = Slot::Empty;
freed += 1; freed += 1;
} }
} }

View File

@@ -35,13 +35,17 @@ fn any_use() -> FrameUse {
} }
} }
fn any_slot() -> Option<FrameUse> { fn any_slot() -> Slot<FrameUse> {
if kani::any() { Some(any_use()) } else { None } if kani::any() {
Slot::Full(any_use())
} else {
Slot::Empty
}
} }
fn any_table() -> FrameTable<N> { fn any_table() -> FrameTable<N> {
let frames = [any_slot(), any_slot(), any_slot(), any_slot()]; let frames = [any_slot(), any_slot(), any_slot(), any_slot()];
let free = frames.iter().filter(|f| f.is_none()).count(); let free = frames.iter().filter(|f| !f.is_full()).count();
FrameTable { FrameTable {
base: PhysicalAddress::from_raw(BASE), base: PhysicalAddress::from_raw(BASE),
total: N, total: N,
@@ -53,7 +57,7 @@ fn any_table() -> FrameTable<N> {
} }
fn well_formed(t: &FrameTable<N>) -> bool { fn well_formed(t: &FrameTable<N>) -> bool {
t.free == t.frames[..t.total].iter().filter(|f| f.is_none()).count() t.free == t.frames[..t.total].iter().filter(|f| !f.is_full()).count()
} }
fn any_pa() -> PhysicalAddress { fn any_pa() -> PhysicalAddress {
@@ -71,8 +75,8 @@ fn alloc_only_hands_out_free_frames() {
Some(pa) => { Some(pa) => {
let idx = (pa.raw() - BASE) / PAGE_SIZE; let idx = (pa.raw() - BASE) / PAGE_SIZE;
assert!(idx < N); assert!(idx < N);
assert!(before[idx].is_none()); assert!(!before[idx].is_full());
assert!(t.frames[idx] == Some(usage)); assert!(t.frames[idx] == Slot::Full(usage));
for i in 0..N { for i in 0..N {
if i != idx { if i != idx {
assert!(t.frames[i] == before[i]); assert!(t.frames[i] == before[i]);
@@ -80,7 +84,7 @@ fn alloc_only_hands_out_free_frames() {
} }
} }
None => { None => {
assert!(before.iter().all(Option::is_some)); assert!(before.iter().all(Slot::is_full));
assert!(t.frames == before); assert!(t.frames == before);
} }
} }
@@ -97,8 +101,8 @@ fn free_releases_exactly_one_allocated_frame_or_nothing() {
Some(usage) => { Some(usage) => {
let idx = (pa.raw() - BASE) / PAGE_SIZE; let idx = (pa.raw() - BASE) / PAGE_SIZE;
assert!(pa.raw() % PAGE_SIZE == 0); assert!(pa.raw() % PAGE_SIZE == 0);
assert!(before[idx] == Some(usage)); assert!(before[idx] == Slot::Full(usage));
assert!(t.frames[idx].is_none()); assert!(!t.frames[idx].is_full());
for i in 0..N { for i in 0..N {
if i != idx { if i != idx {
assert!(t.frames[i] == before[i]); assert!(t.frames[i] == before[i]);
@@ -119,8 +123,8 @@ fn retag_never_allocates() {
let usage = any_use(); let usage = any_use();
let changed = t.retag(pa, usage); let changed = t.retag(pa, usage);
for i in 0..N { for i in 0..N {
if before[i].is_none() { if !before[i].is_full() {
assert!(t.frames[i].is_none()); assert!(!t.frames[i].is_full());
} }
} }
if !changed { if !changed {
@@ -138,11 +142,11 @@ fn free_owned_by_spares_everyone_else() {
t.free_owned_by(owner); t.free_owned_by(owner);
for i in 0..N { for i in 0..N {
let was_owned = matches!( let was_owned = matches!(
before[i], before[i].as_ref(),
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if o == owner Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner
); );
if was_owned { if was_owned {
assert!(t.frames[i].is_none()); assert!(!t.frames[i].is_full());
} else { } else {
assert!(t.frames[i] == before[i]); assert!(t.frames[i] == before[i]);
} }
@@ -162,7 +166,7 @@ fn find_user_only_reports_the_owners_mapping() {
assert!(va.raw() % PAGE_SIZE == 0); assert!(va.raw() % PAGE_SIZE == 0);
assert!( assert!(
t.frames[idx] t.frames[idx]
== Some(FrameUse::User { == Slot::Full(FrameUse::User {
owner, owner,
page: (va.raw() / PAGE_SIZE) as u32, page: (va.raw() / PAGE_SIZE) as u32,
perms, perms,
@@ -184,8 +188,8 @@ fn alloc_contiguous_only_claims_a_free_run() {
assert!(count > 0 && start + count <= N); assert!(count > 0 && start + count <= N);
for i in 0..N { for i in 0..N {
if i >= start && i < start + count { if i >= start && i < start + count {
assert!(before[i].is_none()); assert!(!before[i].is_full());
assert!(t.frames[i] == Some(FrameUse::Region { region })); assert!(t.frames[i] == Slot::Full(FrameUse::Region { region }));
} else { } else {
assert!(t.frames[i] == before[i]); assert!(t.frames[i] == before[i]);
} }

View File

@@ -18,7 +18,8 @@ mod quota_tests;
#[path = "tests.rs"] #[path = "tests.rs"]
mod tests; mod tests;
pub const MAX_REGIONS: usize = if cfg!(kani) { 1 } else { 64 }; /// System-wide: the display and storage hold a dozen or more each.
pub const MAX_REGIONS: usize = if cfg!(kani) { 1 } else { 256 };
pub const MAX_REGION_MAPPINGS: usize = if cfg!(kani) { 2 } else { 4 }; pub const MAX_REGION_MAPPINGS: usize = if cfg!(kani) { 2 } else { 4 };
/// Where a region's frames come from. /// Where a region's frames come from.
@@ -117,14 +118,14 @@ impl Region {
} }
pub struct RegionTable { pub struct RegionTable {
regions: [Option<Region>; MAX_REGIONS], regions: [crate::slot::Slot<Region>; MAX_REGIONS],
generations: SlotGenerations<MAX_REGIONS>, generations: SlotGenerations<MAX_REGIONS>,
} }
impl RegionTable { impl RegionTable {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
regions: [const { None }; MAX_REGIONS], regions: [const { crate::slot::Slot::Empty }; MAX_REGIONS],
generations: SlotGenerations::new(), generations: SlotGenerations::new(),
} }
} }
@@ -139,7 +140,7 @@ impl RegionTable {
self.regions self.regions
.iter() .iter()
.enumerate() .enumerate()
.position(|(i, r)| r.is_none() && self.generations.is_usable(i)) .position(|(i, r)| !r.is_full() && self.generations.is_usable(i))
} }
/// The id the next `insert` will return. /// The id the next `insert` will return.
@@ -150,7 +151,7 @@ impl RegionTable {
pub fn insert(&mut self, region: Region) -> Option<RegionId> { pub fn insert(&mut self, region: Region) -> Option<RegionId> {
let idx = self.free_slot()?; let idx = self.free_slot()?;
*self.regions.get_mut(idx)? = Some(region); self.regions.get_mut(idx)?.set(region);
Some(RegionId::from_raw(idx as u16)) Some(RegionId::from_raw(idx as u16))
} }

View File

@@ -11,6 +11,8 @@ pub const MAX_MESSAGE_WORDS: usize = 4;
pub struct Message { pub struct Message {
words: [usize; MAX_MESSAGE_WORDS], words: [usize; MAX_MESSAGE_WORDS],
cap: Option<CapSlot>, cap: Option<CapSlot>,
/// The receiver may pass `cap` on (see `syscall_abi::KEEP_DELEGATE`).
keep_delegate: bool,
} }
impl Message { impl Message {
@@ -19,7 +21,11 @@ impl Message {
} }
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self { pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
Self { words, cap: None } Self {
words,
cap: None,
keep_delegate: false,
}
} }
pub fn with_cap(mut self, slot: CapSlot) -> Self { pub fn with_cap(mut self, slot: CapSlot) -> Self {
@@ -39,4 +45,13 @@ impl Message {
pub fn cap(&self) -> Option<CapSlot> { pub fn cap(&self) -> Option<CapSlot> {
self.cap self.cap
} }
pub fn keeping_delegate(mut self) -> Self {
self.keep_delegate = true;
self
}
pub fn keeps_delegate(&self) -> bool {
self.keep_delegate
}
} }

View File

@@ -128,14 +128,14 @@ impl Notification {
} }
pub struct NotificationTable { pub struct NotificationTable {
slots: [Option<Notification>; MAX_NOTIFICATIONS], slots: [crate::slot::Slot<Notification>; MAX_NOTIFICATIONS],
generations: SlotGenerations<MAX_NOTIFICATIONS>, generations: SlotGenerations<MAX_NOTIFICATIONS>,
} }
impl NotificationTable { impl NotificationTable {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
slots: [const { None }; MAX_NOTIFICATIONS], slots: [const { crate::slot::Slot::Empty }; MAX_NOTIFICATIONS],
generations: SlotGenerations::new(), generations: SlotGenerations::new(),
} }
} }
@@ -146,9 +146,9 @@ impl NotificationTable {
.slots .slots
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?; .find(|(i, s)| !s.is_full() && generations.is_usable(*i))?;
let id = NotificationId::from_raw(idx as u16); let id = NotificationId::from_raw(idx as u16);
*slot = Some(Notification::new(id, owner)); slot.set(Notification::new(id, owner));
Some(id) Some(id)
} }
@@ -172,13 +172,13 @@ impl NotificationTable {
} }
pub fn iter(&self) -> impl Iterator<Item = &Notification> { pub fn iter(&self) -> impl Iterator<Item = &Notification> {
self.slots.iter().flatten() self.slots.iter().filter_map(crate::slot::Slot::as_ref)
} }
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ { pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
self.slots self.slots
.iter() .iter()
.flatten() .filter_map(crate::slot::Slot::as_ref)
.filter(move |n| n.owner == owner) .filter(move |n| n.owner == owner)
.map(|n| n.id) .map(|n| n.id)
} }

View File

@@ -75,14 +75,14 @@ impl Process {
} }
pub struct ProcessTable { pub struct ProcessTable {
processes: [Option<Process>; MAX_PROCESSES], processes: [crate::slot::Slot<Process>; MAX_PROCESSES],
generations: SlotGenerations<MAX_PROCESSES>, generations: SlotGenerations<MAX_PROCESSES>,
} }
impl ProcessTable { impl ProcessTable {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
processes: [const { None }; MAX_PROCESSES], processes: [const { crate::slot::Slot::Empty }; MAX_PROCESSES],
generations: SlotGenerations::new(), generations: SlotGenerations::new(),
} }
} }
@@ -103,11 +103,11 @@ impl ProcessTable {
.processes .processes
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.find(|(i, s)| s.is_none() && generations.is_usable(*i)) .find(|(i, s)| !s.is_full() && generations.is_usable(*i))
.ok_or(KernelError::TooManyProcesses)?; .ok_or(KernelError::TooManyProcesses)?;
let id = ProcessId::from_raw(idx as u16); let id = ProcessId::from_raw(idx as u16);
*slot = Some(Process::new(id, capabilities, fault_handler)); *slot = crate::slot::Slot::Full(Process::new(id, capabilities, fault_handler));
Ok(id) Ok(id)
} }

View File

@@ -21,11 +21,15 @@ pub struct MultiCoreScheduler {
} }
impl MultiCoreScheduler { impl MultiCoreScheduler {
pub fn new(num_cores: u8) -> Self { pub const fn new(num_cores: u8) -> Self {
let cores = core::array::from_fn(|i| PerCoreScheduler::new(CoreId::from_raw(i as u8))); // A core is numbered by its place in `cores`.
Self { Self {
cores, cores: [const { PerCoreScheduler::new() }; MAX_CORES],
num_cores: num_cores.min(MAX_CORES as u8), num_cores: if num_cores < MAX_CORES as u8 {
num_cores
} else {
MAX_CORES as u8
},
placements: [const { None }; MAX_SCHEDULED_THREADS], placements: [const { None }; MAX_SCHEDULED_THREADS],
} }
} }
@@ -47,6 +51,12 @@ impl MultiCoreScheduler {
self.placements.get(thread.raw() as usize)?.as_ref() self.placements.get(thread.raw() as usize)?.as_ref()
} }
/// How many cores to schedule on, once the platform knows; before any
/// thread is placed.
pub fn set_num_cores(&mut self, num_cores: u8) {
self.num_cores = num_cores.min(MAX_CORES as u8);
}
pub fn num_cores(&self) -> u8 { pub fn num_cores(&self) -> u8 {
self.num_cores self.num_cores
} }
@@ -60,8 +70,8 @@ impl MultiCoreScheduler {
pub fn queued_on(&self, thread: ThreadId) -> Option<CoreId> { pub fn queued_on(&self, thread: ThreadId) -> Option<CoreId> {
self.active() self.active()
.iter() .iter()
.find(|c| c.is_queued(thread)) .position(|c| c.is_queued(thread))
.map(|c| c.core()) .map(|i| CoreId::from_raw(i as u8))
} }
/// Whether `thread` waits in some core's run queue. /// Whether `thread` waits in some core's run queue.

View File

@@ -1,26 +1,26 @@
use super::priority_queue::PriorityQueue; use super::priority_queue::PriorityQueue;
use types::bounded::Priority; use types::bounded::Priority;
use types::id::{CoreId, ThreadId}; use types::id::ThreadId;
pub struct PerCoreScheduler { pub struct PerCoreScheduler {
core: CoreId,
queue: PriorityQueue, queue: PriorityQueue,
current: Option<ThreadId>, current: Option<ThreadId>,
} }
impl Default for PerCoreScheduler {
fn default() -> Self {
Self::new()
}
}
impl PerCoreScheduler { impl PerCoreScheduler {
pub fn new(core: CoreId) -> Self { pub const fn new() -> Self {
Self { Self {
core,
queue: PriorityQueue::new(), queue: PriorityQueue::new(),
current: None, current: None,
} }
} }
pub fn core(&self) -> CoreId {
self.core
}
pub fn current(&self) -> Option<ThreadId> { pub fn current(&self) -> Option<ThreadId> {
self.current self.current
} }

View File

@@ -25,7 +25,7 @@ pub struct PriorityQueue {
} }
impl PriorityQueue { impl PriorityQueue {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
lists: [ListEnds { lists: [ListEnds {
head: None, head: None,

View File

@@ -99,13 +99,13 @@ fn drain_all_priorities() {
#[test] #[test]
fn new_core_has_no_current() { fn new_core_has_no_current() {
let core = PerCoreScheduler::new(CoreId::from_raw(0)); let core = PerCoreScheduler::new();
assert!(core.current().is_none()); assert!(core.current().is_none());
} }
#[test] #[test]
fn pick_next_sets_current() { fn pick_next_sets_current() {
let mut core = PerCoreScheduler::new(CoreId::from_raw(0)); let mut core = PerCoreScheduler::new();
core.add(thread(1), prio(10)); core.add(thread(1), prio(10));
let picked = core.pick_next().unwrap(); let picked = core.pick_next().unwrap();
@@ -115,13 +115,13 @@ fn pick_next_sets_current() {
#[test] #[test]
fn pick_next_on_empty_returns_none() { fn pick_next_on_empty_returns_none() {
let mut core = PerCoreScheduler::new(CoreId::from_raw(0)); let mut core = PerCoreScheduler::new();
assert!(core.pick_next().is_none()); assert!(core.pick_next().is_none());
} }
#[test] #[test]
fn clear_current() { fn clear_current() {
let mut core = PerCoreScheduler::new(CoreId::from_raw(0)); let mut core = PerCoreScheduler::new();
core.add(thread(1), prio(10)); core.add(thread(1), prio(10));
core.pick_next(); core.pick_next();

144
crates/domain/src/slot.rs Normal file
View File

@@ -0,0 +1,144 @@
//! A table entry that is empty or holds a value, like `Option`, but whose
//! empty state is guaranteed to be all zero bytes. The kernel's tables are
//! one static; if every empty entry is zero, the whole static starts zero
//! and costs nothing in the kernel image (`.bss`). `Option` makes no such
//! promise (its `None` is whatever the compiler picks).
/// `#[repr(u8)]` fixes the layout: a tag byte, `Empty` being 0, then the
/// value (left uninitialised when empty).
#[cfg(not(kani))]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Slot<T> {
#[default]
Empty = 0,
Full(T) = 1,
}
/// For the proofs, the same behaviour over `Option`: only the byte layout
/// differs, which no proof depends on, and CBMC is far faster on `Option`
/// than on an enum with an uninitialised payload.
#[cfg(kani)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Slot<T>(Option<T>);
#[cfg(kani)]
impl<T> Default for Slot<T> {
fn default() -> Self {
Self(None)
}
}
#[cfg(kani)]
#[allow(non_upper_case_globals, non_snake_case)]
impl<T> Slot<T> {
pub const Empty: Self = Self(None);
pub const fn Full(value: T) -> Self {
Self(Some(value))
}
pub const fn is_full(&self) -> bool {
self.0.is_some()
}
pub const fn as_ref(&self) -> Option<&T> {
self.0.as_ref()
}
pub fn as_mut(&mut self) -> Option<&mut T> {
self.0.as_mut()
}
pub fn take(&mut self) -> Option<T> {
self.0.take()
}
pub fn set(&mut self, value: T) {
self.0 = Some(value);
}
}
#[cfg(kani)]
impl<T: Copy> Slot<T> {
pub const fn get(&self) -> Option<T> {
self.0
}
}
#[cfg(kani)]
impl<T> From<Option<T>> for Slot<T> {
fn from(o: Option<T>) -> Self {
Self(o)
}
}
#[cfg(not(kani))]
impl<T> Slot<T> {
pub const fn is_full(&self) -> bool {
matches!(self, Slot::Full(_))
}
pub const fn as_ref(&self) -> Option<&T> {
match self {
Slot::Full(v) => Some(v),
Slot::Empty => None,
}
}
pub fn as_mut(&mut self) -> Option<&mut T> {
match self {
Slot::Full(v) => Some(v),
Slot::Empty => None,
}
}
/// The value, leaving the slot empty.
pub fn take(&mut self) -> Option<T> {
match core::mem::replace(self, Slot::Empty) {
Slot::Full(v) => Some(v),
Slot::Empty => None,
}
}
pub fn set(&mut self, value: T) {
*self = Slot::Full(value);
}
}
#[cfg(not(kani))]
impl<T: Copy> Slot<T> {
pub const fn get(&self) -> Option<T> {
match self {
Slot::Full(v) => Some(*v),
Slot::Empty => None,
}
}
}
#[cfg(not(kani))]
impl<T> From<Option<T>> for Slot<T> {
fn from(o: Option<T>) -> Self {
match o {
Some(v) => Slot::Full(v),
None => Slot::Empty,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_slot_behaves_like_an_option() {
let mut s: Slot<u32> = Slot::Empty;
assert!(!s.is_full() && s.get().is_none());
s.set(5);
*s.as_mut().unwrap() += 1;
assert_eq!(s.get(), Some(6));
assert_eq!(s.take(), Some(6));
assert_eq!(s, Slot::Empty);
assert_eq!(Slot::from(Some(1)), Slot::Full(1));
}
}

View File

@@ -1,9 +1,10 @@
use types::{ use types::{
address::{DataRef, EntryPoint, StackPointer, VirtualAddress}, address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
badge::Badge, badge::Badge,
bounded::{CapSlot, PageCount}, bounded::{CapSlot, PageCount, SavedReply},
mapping::PagePerms, mapping::PagePerms,
permission::PermissionSet, permission::PermissionSet,
time::Ticks,
}; };
use crate::message::Message; use crate::message::Message;
@@ -91,6 +92,26 @@ pub enum Syscall {
RegionAddress { RegionAddress {
region: CapSlot, region: CapSlot,
}, },
/// Signals `notification` when the process behind `process` dies, by
/// exit, fault or kill. Up to `timer::MAX_WATCHERS` per process.
WatchDeath {
process: CapSlot,
notification: CapSlot,
},
/// Signals `notification` once `GetTime` reaches `deadline`; replaces
/// the caller's timer, and a zero deadline cancels it.
SetTimer {
notification: CapSlot,
deadline: Ticks,
},
/// Keeps the caller this thread owes a reply to, to answer later
/// (`ReplySaved`) while it receives others; returns which it is.
SaveReply,
/// Answers a caller kept with `SaveReply`, once.
ReplySaved {
reply: SavedReply,
message: Message,
},
/// Removes a capability from the caller's own table. /// Removes a capability from the caller's own table.
DropCap { DropCap {
slot: CapSlot, slot: CapSlot,

View File

@@ -1,9 +1,27 @@
//! Saved register state of a thread. //! Saved register state of a thread.
/// A thread's floating-point registers and their control word. A thread
/// has none until its first floating-point instruction (see
/// `SavedContext::enable_fp`): until then it cannot see another thread's.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FpState {
pub registers: [u64; 32],
pub control: u64,
}
impl FpState {
pub const ZERO: Self = Self {
registers: [0; 32],
control: 0,
};
}
pub struct SavedContext { pub struct SavedContext {
registers: [usize; 32], registers: [usize; 32],
pc: usize, pc: usize,
status: usize, status: usize,
fp: FpState,
fp_enabled: bool,
} }
impl SavedContext { impl SavedContext {
@@ -12,6 +30,8 @@ impl SavedContext {
registers: [0; 32], registers: [0; 32],
pc: 0, pc: 0,
status: 0, status: 0,
fp: FpState::ZERO,
fp_enabled: false,
} }
} }
@@ -48,4 +68,39 @@ impl SavedContext {
pub fn registers_mut(&mut self) -> &mut [usize; 32] { pub fn registers_mut(&mut self) -> &mut [usize; 32] {
&mut self.registers &mut self.registers
} }
/// Its floating-point registers, if it has used any.
pub fn fp(&self) -> Option<&FpState> {
self.fp_enabled.then_some(&self.fp)
}
pub fn fp_mut(&mut self) -> Option<&mut FpState> {
self.fp_enabled.then_some(&mut self.fp)
}
/// Gives it floating-point registers, all zero.
pub fn enable_fp(&mut self) -> &mut FpState {
self.fp = FpState::ZERO;
self.fp_enabled = true;
&mut self.fp
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_thread_has_no_fp_until_it_is_given_zeroed_registers() {
let mut c = SavedContext::empty();
assert!(c.fp().is_none() && c.fp_mut().is_none());
c.enable_fp().registers[3] = 7;
assert_eq!(c.fp().unwrap().registers[3], 7);
c.enable_fp();
assert_eq!(
c.fp(),
Some(&FpState::ZERO),
"enabling again starts from zero"
);
}
} }

View File

@@ -9,7 +9,7 @@ use crate::error::KernelError;
mod context; mod context;
mod table; mod table;
pub use context::SavedContext; pub use context::{FpState, SavedContext};
pub use table::{MAX_THREADS, ThreadTable}; pub use table::{MAX_THREADS, ThreadTable};
#[cfg(test)] #[cfg(test)]

View File

@@ -13,14 +13,14 @@ use crate::{error::KernelError, generations::SlotGenerations};
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 }; pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
pub struct ThreadTable { pub struct ThreadTable {
threads: [Option<Thread>; MAX_THREADS], threads: [crate::slot::Slot<Thread>; MAX_THREADS],
generations: SlotGenerations<MAX_THREADS>, generations: SlotGenerations<MAX_THREADS>,
} }
impl ThreadTable { impl ThreadTable {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
threads: [const { None }; MAX_THREADS], threads: [const { crate::slot::Slot::Empty }; MAX_THREADS],
generations: SlotGenerations::new(), generations: SlotGenerations::new(),
} }
} }
@@ -36,7 +36,7 @@ impl ThreadTable {
self.threads self.threads
.iter() .iter()
.enumerate() .enumerate()
.position(|(i, t)| t.is_none() && self.generations.is_usable(i)) .position(|(i, t)| !t.is_full() && self.generations.is_usable(i))
} }
pub fn spawn( pub fn spawn(
@@ -52,7 +52,7 @@ impl ThreadTable {
*self *self
.threads .threads
.get_mut(idx) .get_mut(idx)
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new( .ok_or(KernelError::TooManyThreads)? = crate::slot::Slot::Full(Thread::new(
id, id,
process, process,
priority, priority,
@@ -76,7 +76,7 @@ impl ThreadTable {
*self *self
.threads .threads
.get_mut(idx) .get_mut(idx)
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context( .ok_or(KernelError::TooManyThreads)? = crate::slot::Slot::Full(Thread::with_context(
id, id,
process, process,
priority, priority,

View File

@@ -0,0 +1,70 @@
//! Events the kernel reports by signalling a notification: a process's
//! death, a deadline passing. One timer per process.
use types::{badge::Badge, generation::Generation, id::NotificationId, id::ProcessId, time::Ticks};
use crate::process::MAX_PROCESSES;
#[cfg(test)]
#[path = "timer_tests.rs"]
mod tests;
/// A notification as a capability named it; once the notification is
/// destroyed (its generation moves on) the target is dead.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Target {
pub notification: NotificationId,
pub generation: Generation,
pub badge: Badge,
}
/// How many may watch one process die: its parent and init, with room.
pub const MAX_WATCHERS: usize = 4;
pub struct Timers {
deadlines: [Option<(Ticks, Target)>; MAX_PROCESSES],
}
impl Timers {
pub const fn new() -> Self {
Self {
deadlines: [None; MAX_PROCESSES],
}
}
/// Replaces `owner`'s timer.
pub fn set(&mut self, owner: ProcessId, deadline: Ticks, target: Target) -> bool {
match self.deadlines.get_mut(owner.raw() as usize) {
Some(slot) => {
*slot = Some((deadline, target));
true
}
None => false,
}
}
pub fn clear(&mut self, owner: ProcessId) {
if let Some(slot) = self.deadlines.get_mut(owner.raw() as usize) {
*slot = None;
}
}
/// Removes and returns one timer whose deadline is at or before `now`.
pub fn take_expired(&mut self, now: Ticks) -> Option<Target> {
let slot = self
.deadlines
.iter_mut()
.find(|s| s.is_some_and(|(d, _)| d <= now))?;
slot.take().map(|(_, t)| t)
}
pub fn earliest(&self) -> Option<Ticks> {
self.deadlines.iter().flatten().map(|(d, _)| *d).min()
}
}
impl Default for Timers {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,37 @@
use super::{Target, Timers};
use types::{badge::Badge, generation::Generation, id::NotificationId, id::ProcessId, time::Ticks};
fn target(n: u16) -> Target {
Target {
notification: NotificationId::from_raw(n),
generation: Generation::initial(),
badge: Badge::NONE,
}
}
fn pid(n: u16) -> ProcessId {
ProcessId::from_raw(n)
}
#[test]
fn only_expired_timers_fire_and_each_fires_once() {
let mut t = Timers::new();
t.set(pid(0), Ticks::from_raw(10), target(0));
t.set(pid(1), Ticks::from_raw(30), target(1));
assert_eq!(t.earliest(), Some(Ticks::from_raw(10)));
assert_eq!(t.take_expired(Ticks::from_raw(9)), None);
assert_eq!(t.take_expired(Ticks::from_raw(10)), Some(target(0)));
assert_eq!(t.take_expired(Ticks::from_raw(29)), None);
assert_eq!(t.earliest(), Some(Ticks::from_raw(30)));
}
#[test]
fn setting_again_replaces_and_clear_cancels() {
let mut t = Timers::new();
t.set(pid(2), Ticks::from_raw(10), target(0));
t.set(pid(2), Ticks::from_raw(50), target(1));
assert_eq!(t.take_expired(Ticks::from_raw(20)), None);
t.clear(pid(2));
assert_eq!(t.earliest(), None);
assert!(!t.set(pid(500), Ticks::from_raw(1), target(0)));
}

View File

@@ -1,71 +1,68 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
use hw_virtio::mmio::MmioAccess;
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
use hw_virtio::block::{self, DmaAddrs}; use hw_virtio::block::{self, DmaAddrs};
use hw_virtio::mmio::{MmioAccess, Registers};
use hw_virtio::queue::VirtioQueue;
use port_block_device::{BlockDevice, SectorId}; use port_block_device::{BlockDevice, SectorId};
use types::address::{PhysicalAddress, VirtualAddress}; use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
const MMIO_BASE: usize = 0x20000; /// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
const VQUEUE_VA: usize = 0x21000; /// each), clear of program images and heaps.
const REQ_HDR_VA: usize = 0x23000; const MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D0_0000);
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D0_1000);
const REQ_HDR_VA: VirtualAddress = VirtualAddress::from_raw(0x01D0_3000);
const SECTOR_SIZE: usize = 512; const SECTOR_SIZE: usize = 512;
struct UserMmio;
impl MmioAccess for UserMmio {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((MMIO_BASE + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((MMIO_BASE + offset) as *mut u32, val) }
}
}
pub struct VirtioBlockDevice { pub struct VirtioBlockDevice {
mmio: UserMmio, mmio: Registers,
queue: VirtioQueue, queue: VirtioQueue,
dma: DmaAddrs, dma: DmaAddrs,
sectors: usize,
} }
impl VirtioBlockDevice { impl VirtioBlockDevice {
/// Takes over the virtio block device behind `mmio`. `None` if it is /// Takes over the virtio block device behind `mmio`. `None` if it is
/// not one or any step fails. /// not one or any step fails.
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> { pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, MMIO_BASE).ok()?; let regs = Registers::new(libkos::device::map_mmio(mmio, MMIO_VA).ok()?);
let regs = UserMmio;
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK || regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK
{ {
return None; return None;
} }
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?; let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let hdr_pa = libkos::device::dma(1, REQ_HDR_VA).ok()?; let req = libkos::device::dma(PageCount::new(1)?, REQ_HDR_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32); hw_virtio::mmio::init_legacy_device(&regs, rings.physical, hw_virtio::queue::QUEUE_SIZE as u32);
// Header, status byte and one sector share the request page. // Header, status byte and one sector share the request page.
let at = |offset: usize| PhysicalAddress::from_raw(req.physical.raw() + offset);
let dma = DmaAddrs { let dma = DmaAddrs {
header_pa: PhysicalAddress::from_raw(hdr_pa), header_pa: at(0),
header_va: VirtualAddress::from_raw(REQ_HDR_VA), header: req.window.sub(ByteOffset::new(0), 16),
status_pa: PhysicalAddress::from_raw(hdr_pa + 16), status_pa: at(16),
status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16), status: req.window.sub(ByteOffset::new(16), 1),
data_pa: PhysicalAddress::from_raw(hdr_pa + 32), data_pa: at(32),
data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32), data: req.window.sub(ByteOffset::new(32), SECTOR_SIZE),
}; };
let capacity = regs.read(block::CONFIG_CAPACITY) as u64
| (regs.read(block::CONFIG_CAPACITY + 4) as u64) << 32;
Some(Self { Some(Self {
mmio: UserMmio, mmio: regs,
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)), queue: VirtioQueue::new(rings.window),
dma, dma,
sectors: usize::try_from(capacity).ok()?,
}) })
} }
} }
impl BlockDevice for VirtioBlockDevice { impl BlockDevice for VirtioBlockDevice {
fn sector_size(&self) -> usize { SECTOR_SIZE } fn sector_size(&self) -> usize { SECTOR_SIZE }
fn sector_count(&self) -> usize { 0xFFFF } fn sector_count(&self) -> usize { self.sectors }
fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool { fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool {
if buf.len() < SECTOR_SIZE { return false; } if buf.len() < SECTOR_SIZE || sector.raw() as usize >= self.sectors { return false; }
let mut sector_buf = [0u8; SECTOR_SIZE]; let mut sector_buf = [0u8; SECTOR_SIZE];
if !block::read_sector(&self.mmio, &self.queue, &self.dma, sector.raw(), &mut sector_buf) { if !block::read_sector(&self.mmio, &self.queue, &self.dma, sector.raw(), &mut sector_buf) {
return false; return false;
@@ -74,8 +71,8 @@ impl BlockDevice for VirtioBlockDevice {
true true
} }
fn write_sector(&mut self, _sector: SectorId, _buf: &[u8]) -> bool { false } fn write_sector(&mut self, sector: SectorId, buf: &[u8]) -> bool {
if sector.raw() as usize >= self.sectors { return false; }
/// Writing is not implemented yet. block::write_sector(&self.mmio, &self.queue, &self.dma, sector.raw(), buf)
fn writable(&self) -> bool { false } }
} }

View File

@@ -1,4 +1,5 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
use port_framebuffer::Framebuffer; use port_framebuffer::Framebuffer;
use types::display::{Color, Dimension, ScreenCoord}; use types::display::{Color, Dimension, ScreenCoord};
@@ -9,6 +10,7 @@ pub struct MockFramebuffer {
pixels: [(ScreenCoord, ScreenCoord, Color); 4096], pixels: [(ScreenCoord, ScreenCoord, Color); 4096],
pixel_count: usize, pixel_count: usize,
flushed: bool, flushed: bool,
scrolls: usize,
} }
impl MockFramebuffer { impl MockFramebuffer {
@@ -19,6 +21,7 @@ impl MockFramebuffer {
pixels: [(ScreenCoord::new(0), ScreenCoord::new(0), Color::from_raw(0)); 4096], pixels: [(ScreenCoord::new(0), ScreenCoord::new(0), Color::from_raw(0)); 4096],
pixel_count: 0, pixel_count: 0,
flushed: false, flushed: false,
scrolls: 0,
} }
} }
@@ -26,6 +29,11 @@ impl MockFramebuffer {
self.pixel_count self.pixel_count
} }
/// How many times the screen was scrolled.
pub fn scrolls(&self) -> usize {
self.scrolls
}
pub fn was_flushed(&self) -> bool { pub fn was_flushed(&self) -> bool {
self.flushed self.flushed
} }
@@ -61,4 +69,72 @@ impl Framebuffer for MockFramebuffer {
fn flush(&mut self) { fn flush(&mut self) {
self.flushed = true; self.flushed = true;
} }
fn scroll_up(&mut self, _top: ScreenCoord, _height: Dimension, _by: Dimension, _fill: Color) {
self.scrolls += 1;
}
}
/// A screen of `W` by `H` pixels that keeps what is drawn, for tests that
/// look at the picture rather than the calls.
pub struct GridFramebuffer<const W: usize, const H: usize> {
pixels: [[Color; W]; H],
pub flushes: usize,
}
impl<const W: usize, const H: usize> GridFramebuffer<W, H> {
pub fn new() -> Self {
Self { pixels: [[Color::from_raw(0); W]; H], flushes: 0 }
}
/// The pixel at (x, y), `None` off the screen.
pub fn pixel(&self, x: usize, y: usize) -> Option<Color> {
self.pixels.get(y).and_then(|row| row.get(x)).copied()
}
}
impl<const W: usize, const H: usize> Default for GridFramebuffer<W, H> {
fn default() -> Self {
Self::new()
}
}
impl<const W: usize, const H: usize> Framebuffer for GridFramebuffer<W, H> {
fn width(&self) -> Dimension {
Dimension::new(W as u16)
}
fn height(&self) -> Dimension {
Dimension::new(H as u16)
}
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
if let Some(px) = self.pixels.get_mut(y.raw() as usize).and_then(|r| r.get_mut(x.raw() as usize)) {
*px = color;
}
}
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
for row in y.raw()..y.raw().saturating_add(h.raw()) {
for col in x.raw()..x.raw().saturating_add(w.raw()) {
self.write_pixel(ScreenCoord::new(col), ScreenCoord::new(row), color);
}
}
}
fn clear(&mut self, color: Color) {
self.pixels = [[color; W]; H];
}
fn flush(&mut self) {
self.flushes += 1;
}
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
let (top, height, by) = (top.raw() as usize, height.raw() as usize, by.raw() as usize);
let end = (top + height).min(H);
for y in top..end {
self.pixels[y] = if y + by < end { self.pixels[y + by] } else { [fill; W] };
}
}
} }

View File

@@ -1,63 +1,68 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
use hw_virtio::mmio::MmioAccess;
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
use hw_virtio::gpu::{self, GpuDmaAddrs}; use hw_virtio::gpu::{self, GpuDmaAddrs};
use hw_virtio::mmio::{MmioAccess, Registers};
use hw_virtio::queue::VirtioQueue;
use libkos::Window;
use port_framebuffer::Framebuffer; use port_framebuffer::Framebuffer;
use types::address::{PhysicalAddress, VirtualAddress}; use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
use types::display::{Color, Dimension, ScreenCoord}; use types::display::{Color, Dimension, ScreenCoord};
const GPU_MMIO_VA: usize = 0x20000; /// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
const VQUEUE_VA: usize = 0x21000; /// each), clear of program images and heaps.
const CMDBUF_VA: usize = 0x23000; const GPU_MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D2_0000);
const FB_VA: usize = 0x100000; const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D2_1000);
const CMDBUF_VA: VirtualAddress = VirtualAddress::from_raw(0x01D2_3000);
const FB_VA: VirtualAddress = VirtualAddress::from_raw(0x100000);
const FB_BPP: usize = 4; const FB_BPP: usize = 4;
/// Pixels converted and stored per run.
const RUN: usize = 64;
const RESOURCE_ID: u32 = 1; const RESOURCE_ID: u32 = 1;
/// When the device will not say. /// When the device will not say.
const DEFAULT_SIZE: (u16, u16) = (640, 480); const DEFAULT_SIZE: (u16, u16) = (640, 480);
struct GpuMmio;
impl MmioAccess for GpuMmio {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((GPU_MMIO_VA + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((GPU_MMIO_VA + offset) as *mut u32, val) }
}
}
pub struct VirtioFramebuffer { pub struct VirtioFramebuffer {
mmio: GpuMmio, mmio: Registers,
queue: VirtioQueue, queue: VirtioQueue,
dma: GpuDmaAddrs, dma: GpuDmaAddrs,
/// The pixels the device scans out, `stride` bytes a row.
pixels: Window,
width: Dimension, width: Dimension,
height: Dimension, height: Dimension,
stride: usize, stride: usize,
/// What changed since the last flush, as corners: only that goes to
/// the host.
dirty: Option<(usize, usize, usize, usize)>,
}
/// `c` as the screen stores it: the bytes R, G, B, then one ignored.
fn word(c: Color) -> u32 {
u32::from_le_bytes(c.to_rgba())
} }
impl VirtioFramebuffer { impl VirtioFramebuffer {
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its /// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
/// scanout. `None` if it is not a GPU or any step fails. /// scanout. `None` if it is not a GPU or any step fails.
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> { pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?; let regs = Registers::new(libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?);
let regs = GpuMmio;
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU || regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU
{ {
return None; return None;
} }
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?; let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let cmd_pa = libkos::device::dma(1, CMDBUF_VA).ok()?; let cmd = libkos::device::dma(PageCount::new(1)?, CMDBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32); hw_virtio::mmio::init_legacy_device(&regs, rings.physical, hw_virtio::queue::QUEUE_SIZE as u32);
let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)); let queue = VirtioQueue::new(rings.window);
// Commands go out from the start of the page, responses come back // Commands go out from the start of the page, responses come back
// in its second half. // in its second half.
let dma = GpuDmaAddrs { let dma = GpuDmaAddrs {
cmd_pa: PhysicalAddress::from_raw(cmd_pa), cmd_pa: cmd.physical,
cmd_va: VirtualAddress::from_raw(CMDBUF_VA), cmd: cmd.window,
resp_pa: PhysicalAddress::from_raw(cmd_pa + 512), resp_pa: PhysicalAddress::from_raw(cmd.physical.raw() + gpu::RESPONSE.raw()),
}; };
let (w, h) = gpu::get_display_info(&regs, &queue, &dma) let (w, h) = gpu::get_display_info(&regs, &queue, &dma)
.and_then(|(w, h)| Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?))) .and_then(|(w, h)| Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?)))
@@ -65,55 +70,124 @@ impl VirtioFramebuffer {
.unwrap_or(DEFAULT_SIZE); .unwrap_or(DEFAULT_SIZE);
let (width, height) = (Dimension::new(w), Dimension::new(h)); let (width, height) = (Dimension::new(w), Dimension::new(h));
let fb_bytes = w as usize * h as usize * FB_BPP; let fb_bytes = w as usize * h as usize * FB_BPP;
let fb_pages = u16::try_from(fb_bytes.div_ceil(4096)).ok()?; let fb_pages = PageCount::new(u16::try_from(fb_bytes.div_ceil(4096)).ok()?)?;
let fb_pa = PhysicalAddress::from_raw(libkos::device::dma(fb_pages, FB_VA).ok()?); let fb = libkos::device::dma(fb_pages, FB_VA).ok()?;
let ready = gpu::create_2d(&regs, &queue, &dma, RESOURCE_ID, width, height) let ready = gpu::create_2d(&regs, &queue, &dma, RESOURCE_ID, width, height)
&& gpu::attach_backing(&regs, &queue, &dma, RESOURCE_ID, fb_pa, fb_bytes as u32) && gpu::attach_backing(&regs, &queue, &dma, RESOURCE_ID, fb.physical, fb_bytes as u32)
&& gpu::set_scanout(&regs, &queue, &dma, 0, RESOURCE_ID, width, height); && gpu::set_scanout(&regs, &queue, &dma, 0, RESOURCE_ID, width, height);
if !ready { if !ready {
return None; return None;
} }
Some(Self { Some(Self {
mmio: GpuMmio, mmio: regs,
queue, queue,
dma, dma,
pixels: fb.window,
width, width,
height, height,
stride: w as usize * FB_BPP, stride: w as usize * FB_BPP,
dirty: None,
}) })
} }
} }
impl Framebuffer for VirtioFramebuffer { impl VirtioFramebuffer {
fn width(&self) -> Dimension { self.width } fn pixel(&self, x: usize, y: usize) -> ByteOffset {
fn height(&self) -> Dimension { self.height } ByteOffset::new(y * self.stride + x * FB_BPP)
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
if x.raw() >= self.width.raw() || y.raw() >= self.height.raw() { return; }
let offset = (y.raw() as usize) * self.stride + (x.raw() as usize) * FB_BPP;
unsafe { core::ptr::write_volatile((FB_VA + offset) as *mut u32, color.raw()); }
} }
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) { /// Adds the rectangle from (x0, y0) up to (x1, y1) to what changed.
for dy in 0..h.raw() { fn touch(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
for dx in 0..w.raw() { if x0 >= x1 || y0 >= y1 {
self.write_pixel(ScreenCoord::new(x.raw() + dx), ScreenCoord::new(y.raw() + dy), color); return;
}
self.dirty = Some(match self.dirty {
None => (x0, y0, x1, y1),
Some((a, b, c, d)) => (a.min(x0), b.min(y0), c.max(x1), d.max(y1)),
});
}
}
impl Framebuffer for VirtioFramebuffer {
fn width(&self) -> Dimension {
self.width
}
fn height(&self) -> Dimension {
self.height
}
fn write_rgba_row(&mut self, x: ScreenCoord, y: ScreenCoord, bytes: &[u8]) {
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
let (x, y) = (x.raw() as usize, y.raw() as usize);
if x >= w || y >= h {
return;
}
let n = (bytes.len() / FB_BPP).min(w - x);
self.touch(x, y, x + n, y + 1);
self.pixels.copy_from(self.pixel(x, y), &bytes[..n * FB_BPP]);
}
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
self.write_row(x, y, &[color]);
}
fn write_row(&mut self, x: ScreenCoord, y: ScreenCoord, pixels: &[Color]) {
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
let (x, y) = (x.raw() as usize, y.raw() as usize);
if x >= w || y >= h {
return;
}
let n = pixels.len().min(w - x);
self.touch(x, y, x + n, y + 1);
let mut run = [0u32; RUN];
let mut at = self.pixel(x, y);
for chunk in pixels[..n].chunks(RUN) {
for (slot, c) in run.iter_mut().zip(chunk) {
*slot = word(*c);
} }
self.pixels.write_u32s(at, &run[..chunk.len()]);
at = at + chunk.len() * FB_BPP;
}
}
/// Callers clip to the screen (the display server does).
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
let (fw, fh) = (self.width.raw() as usize, self.height.raw() as usize);
let (x, y) = (x.raw() as usize, y.raw() as usize);
let w = (w.raw() as usize).min(fw.saturating_sub(x));
let bottom = (y + h.raw() as usize).min(fh);
self.touch(x, y, x + w, bottom);
for row in y..bottom {
self.pixels.fill_u32(self.pixel(x, row), w, word(color));
} }
} }
fn clear(&mut self, color: Color) { fn clear(&mut self, color: Color) {
let total = self.width.raw() as usize * self.height.raw() as usize; let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
for i in 0..total { self.touch(0, 0, w, h);
unsafe { core::ptr::write_volatile((FB_VA as *mut u32).add(i), color.raw()); } self.pixels.fill_u32(ByteOffset::ZERO, w * h, word(color));
}
} }
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
let fh = self.height.raw() as usize;
let top = top.raw() as usize;
let bottom = (top + height.raw() as usize).min(fh);
let by = (by.raw() as usize).min(bottom.saturating_sub(top));
let moved = bottom - top - by;
self.touch(0, top, self.width.raw() as usize, bottom);
self.pixels.copy_within(self.pixel(0, top + by), self.pixel(0, top), moved * self.stride);
let width = self.width.raw() as usize;
self.pixels.fill_u32(self.pixel(0, bottom - by), by * width, word(fill));
}
/// Sends the host only what changed since the last flush.
fn flush(&mut self) { fn flush(&mut self) {
let w = self.width.raw() as u32; let Some((x0, y0, x1, y1)) = self.dirty.take() else { return };
let h = self.height.raw() as u32; let r = gpu::Rect { x: x0 as u32, y: y0 as u32, w: (x1 - x0) as u32, h: (y1 - y0) as u32 };
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h); let offset = self.pixel(x0, y0).raw() as u64;
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h); gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, r, offset);
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, r);
} }
} }

View File

@@ -1,33 +1,36 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
use libkos::Window;
use port_input_device::{InputDevice, InputEvent, Key}; use port_input_device::{InputDevice, InputEvent, Key};
use types::address::{ByteOffset, VirtualAddress};
const UART_VA: usize = 0x20000; /// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
const UART_IER_OFFSET: usize = 1; /// each), clear of program images and heaps.
const UART_LSR_OFFSET: usize = 5; const UART_VA: VirtualAddress = VirtualAddress::from_raw(0x01D4_0000);
const UART_RBR: ByteOffset = ByteOffset::new(0);
const UART_IER_OFFSET: ByteOffset = ByteOffset::new(1);
const UART_LSR_OFFSET: ByteOffset = ByteOffset::new(5);
/// IER bit: interrupt when a received byte is waiting (cleared by reading /// IER bit: interrupt when a received byte is waiting (cleared by reading
/// it, so `acknowledge` has nothing to do). /// it, so `acknowledge` has nothing to do).
const IER_RX_READY: u8 = 1; const IER_RX_READY: u8 = 1;
pub struct UartInputDevice; /// The UART's registers, one byte each.
pub struct UartInputDevice {
regs: Window,
}
impl UartInputDevice { impl UartInputDevice {
/// Maps the 16550 UART whose region `mmio` names and enables its /// Maps the 16550 UART whose region `mmio` names and enables its
/// receive interrupt. /// receive interrupt.
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> { pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, UART_VA).ok()?; let regs = libkos::device::map_mmio(mmio, UART_VA).ok()?;
// SAFETY: UART_VA now maps the UART's registers. regs.write(UART_IER_OFFSET, IER_RX_READY);
unsafe { core::ptr::write_volatile((UART_VA + UART_IER_OFFSET) as *mut u8, IER_RX_READY) }; Some(Self { regs })
Some(Self)
} }
fn read_byte(&self) -> Option<u8> { fn read_byte(&self) -> Option<u8> {
let lsr = unsafe { core::ptr::read_volatile((UART_VA + UART_LSR_OFFSET) as *const u8) }; self.has_event().then(|| self.regs.read(UART_RBR))
if lsr & 1 != 0 {
Some(unsafe { core::ptr::read_volatile(UART_VA as *const u8) })
} else {
None
}
} }
fn byte_to_key(byte: u8) -> Key { fn byte_to_key(byte: u8) -> Key {
@@ -50,7 +53,6 @@ impl InputDevice for UartInputDevice {
} }
fn has_event(&self) -> bool { fn has_event(&self) -> bool {
let lsr = unsafe { core::ptr::read_volatile((UART_VA + UART_LSR_OFFSET) as *const u8) }; self.regs.read::<u8>(UART_LSR_OFFSET) & 1 != 0
lsr & 1 != 0
} }
} }

View File

@@ -0,0 +1,100 @@
//! Linux key codes (what virtio-input reports) to keys, US layout.
use port_input_device::Key;
pub const LEFT_SHIFT: u16 = 42;
pub const RIGHT_SHIFT: u16 = 54;
pub const LEFT_CTRL: u16 = 29;
pub const RIGHT_CTRL: u16 = 97;
pub const LEFT_ALT: u16 = 56;
pub const RIGHT_ALT: u16 = 100;
/// Alt+Tab is the display's window switch, the same key as F1
/// (`types::display::window::SWITCH_KEY`).
const SWITCH: Key = Key::Function(1);
/// What a key is with Alt held: only Alt+Tab means anything.
pub fn with_alt(key: Key) -> Option<Key> {
(key == Key::Tab).then_some(SWITCH)
}
/// A letter typed with Ctrl held is its control character, as on a
/// terminal: Ctrl+C is 3. Anything else is itself.
pub fn with_ctrl(key: Key) -> Key {
match key {
Key::Char(c) if c.is_ascii_alphabetic() => Key::Char(c.to_ascii_lowercase() & 0x1f),
k => k,
}
}
/// Rows of the main block: the first key's code, then each key unshifted
/// and shifted.
const ROWS: [(u16, &[u8], &[u8]); 4] = [
(2, b"1234567890-=", b"!@#$%^&*()_+"),
(16, b"qwertyuiop[]", b"QWERTYUIOP{}"),
(30, b"asdfghjkl;'`", b"ASDFGHJKL:\"~"),
(44, b"zxcvbnm,./", b"ZXCVBNM<>?"),
];
pub fn key(code: u16, shift: bool) -> Option<Key> {
let named = match code {
1 => Some(Key::Escape),
14 => Some(Key::Backspace),
15 => Some(Key::Tab),
28 => Some(Key::Enter),
57 => Some(Key::Space),
103 => Some(Key::Up),
108 => Some(Key::Down),
105 => Some(Key::Left),
106 => Some(Key::Right),
43 => Some(Key::Char(if shift { b'|' } else { b'\\' })),
59..=68 => Some(Key::Function((code - 58) as u8)),
87 | 88 => Some(Key::Function((code - 76) as u8)),
_ => None,
};
named.or_else(|| {
ROWS.iter().find_map(|(first, plain, shifted)| {
let i = usize::from(code.checked_sub(*first)?);
let row = if shift { shifted } else { plain };
row.get(i).map(|c| Key::Char(*c))
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alt_tab_switches_windows_and_other_alt_keys_are_nothing() {
assert_eq!(with_alt(Key::Tab), Some(Key::Function(1)));
assert_eq!(with_alt(Key::Char(b'a')), None);
}
#[test]
fn function_keys_are_numbered() {
assert_eq!(key(59, false), Some(Key::Function(1)));
assert_eq!(key(68, false), Some(Key::Function(10)));
assert_eq!(key(88, true), Some(Key::Function(12)));
}
#[test]
fn ctrl_letters_are_control_characters() {
assert_eq!(with_ctrl(Key::Char(b'c')), Key::Char(3));
assert_eq!(with_ctrl(Key::Char(b'C')), Key::Char(3));
assert_eq!(with_ctrl(Key::Char(b'1')), Key::Char(b'1'));
assert_eq!(with_ctrl(Key::Enter), Key::Enter);
}
#[test]
fn letters_digits_and_shift() {
assert_eq!(key(35, false), Some(Key::Char(b'h')));
assert_eq!(key(35, true), Some(Key::Char(b'H')));
assert_eq!(key(11, false), Some(Key::Char(b'0')));
assert_eq!(key(12, true), Some(Key::Char(b'_')));
assert_eq!(key(53, false), Some(Key::Char(b'/')));
assert_eq!(key(41, true), Some(Key::Char(b'~')));
assert_eq!(key(28, false), Some(Key::Enter));
assert_eq!(key(103, false), Some(Key::Up));
assert_eq!(key(29, false), None, "left ctrl is not a character");
}
}

View File

@@ -1,139 +1,126 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
use hw_virtio::mmio::MmioAccess; mod keymap;
use port_input_device::{InputDevice, InputEvent, Key}; mod pointer;
const INPUT_MMIO_VA: usize = 0x25000; use core::sync::atomic::{Ordering, fence};
const VQUEUE_VA: usize = 0x26000; use hw_virtio::mmio::{MmioAccess, Registers};
const EVBUF_VA: usize = 0x28000; use libkos::Window;
use port_input_device::{InputDevice, InputEvent};
use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
/// each), clear of program images and heaps; an input device takes 16 KiB
/// of it, the `n`th from here (a keyboard, a tablet).
const INPUT_VA: usize = 0x01D1_0000;
const INPUT_SPAN: usize = 0x4000;
/// Its registers, its queue's rings (two pages), its event buffers.
const MMIO_AT: usize = 0;
const VQUEUE_AT: usize = 0x1000;
const EVBUF_AT: usize = 0x3000;
/// How many may be driven at once in one program.
pub const MAX_INPUTS: usize = 4;
const QUEUE_SIZE: usize = 32; const QUEUE_SIZE: usize = 32;
const EVENT_SIZE: usize = 8; const EVENT_SIZE: usize = 8;
const DESC_SIZE: usize = 16;
/// Where the legacy layout puts the available and used rings.
const AVAIL: ByteOffset = ByteOffset::new(QUEUE_SIZE * DESC_SIZE);
const USED: ByteOffset = ByteOffset::new(4096);
const EV_KEY: u16 = 1; const EV_KEY: u16 = 1;
const KEY_PRESS: u32 = 1; const KEY_PRESS: u32 = 1;
struct MmioInput;
impl hw_virtio::mmio::MmioAccess for MmioInput {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((INPUT_MMIO_VA + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((INPUT_MMIO_VA + offset) as *mut u32, val) }
}
}
pub struct VirtioInputDevice { pub struct VirtioInputDevice {
regs: Registers,
/// The queue's rings, and the buffers the device writes events into.
rings: Window,
events: Window,
events_pa: PhysicalAddress,
last_used: u16, last_used: u16,
next_avail: u16, next_avail: u16,
evbuf_pa: usize, shift: bool,
ctrl: bool,
alt: bool,
/// Where a tablet's pointer is, between its reports.
pointer: pointer::Pointer,
} }
impl VirtioInputDevice { impl VirtioInputDevice {
/// Takes over the virtio input device behind `mmio`. `None` if it is /// Takes over the virtio input device behind `mmio`, as this program's
/// not one or any step fails. /// `n`th (each has addresses of its own). `None` if it is not one or
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> { /// any step fails.
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?; pub fn try_new(mmio: types::bounded::CapSlot, n: usize) -> Option<Self> {
let regs = MmioInput; if n >= MAX_INPUTS {
return None;
}
let at = |offset: usize| VirtualAddress::from_raw(INPUT_VA + n * INPUT_SPAN + offset);
let regs = Registers::new(libkos::device::map_mmio(mmio, at(MMIO_AT)).ok()?);
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT || regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
{ {
return None; return None;
} }
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?; let rings = libkos::device::dma(PageCount::new(2)?, at(VQUEUE_AT)).ok()?;
let evbuf_pa = libkos::device::dma(1, EVBUF_VA).ok()?; let events = libkos::device::dma(PageCount::new(1)?, at(EVBUF_AT)).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, QUEUE_SIZE as u32); hw_virtio::mmio::init_legacy_device(&regs, rings.physical, QUEUE_SIZE as u32);
let mut dev = Self { last_used: 0, next_avail: 0, evbuf_pa }; let mut dev = Self {
regs,
rings: rings.window,
events: events.window,
events_pa: events.physical,
last_used: 0,
next_avail: 0,
shift: false,
ctrl: false,
alt: false,
pointer: pointer::Pointer::default(),
};
dev.fill_queue(); dev.fill_queue();
Some(dev) Some(dev)
} }
/// Every buffer goes to the device, one event each.
fn fill_queue(&mut self) { fn fill_queue(&mut self) {
let evbuf_phys = self.evbuf_pa;
let desc_base = VQUEUE_VA;
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
for i in 0..QUEUE_SIZE { for i in 0..QUEUE_SIZE {
let desc = desc_base + i * 16; let desc = ByteOffset::new(i * DESC_SIZE);
let buf_pa = evbuf_phys + i * EVENT_SIZE; self.rings.write(desc, (self.events_pa.raw() + i * EVENT_SIZE) as u64);
unsafe { self.rings.write(desc + 8, EVENT_SIZE as u32);
core::ptr::write_volatile(desc as *mut u64, buf_pa as u64); self.rings.write(desc + 12, 2u16); // WRITE flag
core::ptr::write_volatile((desc + 8) as *mut u32, EVENT_SIZE as u32); self.rings.write(desc + 14, 0u16); // no next
core::ptr::write_volatile((desc + 12) as *mut u16, 2); // WRITE flag self.rings.write(AVAIL + 4 + i * 2, i as u16);
core::ptr::write_volatile((desc + 14) as *mut u16, 0); // no next
}
unsafe {
let ring_entry = avail_base + 4 + (i % QUEUE_SIZE) * 2;
core::ptr::write_volatile(ring_entry as *mut u16, i as u16);
}
} }
fence(Ordering::SeqCst);
unsafe { self.rings.write(AVAIL + 2, QUEUE_SIZE as u16);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); fence(Ordering::SeqCst);
core::ptr::write_volatile((avail_base + 2) as *mut u16, QUEUE_SIZE as u16); self.regs.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
let mmio = MmioInput;
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
self.next_avail = QUEUE_SIZE as u16; self.next_avail = QUEUE_SIZE as u16;
} }
fn resubmit(&mut self, desc_idx: u16) { fn resubmit(&mut self, desc_idx: u16) {
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
let slot = self.next_avail as usize % QUEUE_SIZE; let slot = self.next_avail as usize % QUEUE_SIZE;
unsafe { self.rings.write(AVAIL + 4 + slot * 2, desc_idx);
core::ptr::write_volatile((avail_base + 4 + slot * 2) as *mut u16, desc_idx); fence(Ordering::SeqCst);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); self.next_avail = self.next_avail.wrapping_add(1);
self.next_avail = self.next_avail.wrapping_add(1); self.rings.write(AVAIL + 2, self.next_avail);
core::ptr::write_volatile((avail_base + 2) as *mut u16, self.next_avail); fence(Ordering::SeqCst);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); self.regs.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
}
let mmio = MmioInput;
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
} }
fn read_event(&self, desc_idx: u16) -> (u16, u16, u32) { fn read_event(&self, desc_idx: u16) -> (u16, u16, u32) {
let buf_va = EVBUF_VA + (desc_idx as usize) * EVENT_SIZE; let at = ByteOffset::new(desc_idx as usize % QUEUE_SIZE * EVENT_SIZE);
unsafe { (self.events.read(at), self.events.read(at + 2), self.events.read(at + 4))
let ev_type = core::ptr::read_volatile(buf_va as *const u16);
let ev_code = core::ptr::read_volatile((buf_va + 2) as *const u16);
let ev_value = core::ptr::read_volatile((buf_va + 4) as *const u32);
(ev_type, ev_code, ev_value)
}
} }
fn used_idx(&self) -> u16 { fn used_idx(&self) -> u16 {
let used_base = VQUEUE_VA + 4096; self.rings.read(USED + 2)
unsafe { core::ptr::read_volatile((used_base + 2) as *const u16) }
} }
fn used_entry_id(&self, idx: u16) -> u16 { fn used_entry_id(&self, idx: u16) -> u16 {
let used_base = VQUEUE_VA + 4096; self.rings.read::<u32>(USED + 4 + (idx as usize % QUEUE_SIZE) * 8) as u16
let entry = used_base + 4 + (idx as usize % QUEUE_SIZE) * 8;
unsafe { core::ptr::read_volatile(entry as *const u32) as u16 }
}
}
fn linux_keycode_to_key(code: u16) -> Option<Key> {
match code {
17 => Some(Key::Up), // KEY_W
31 => Some(Key::Down), // KEY_S
30 => Some(Key::Left), // KEY_A
32 => Some(Key::Right), // KEY_D
103 => Some(Key::Up), // KEY_UP
108 => Some(Key::Down), // KEY_DOWN
105 => Some(Key::Left), // KEY_LEFT
106 => Some(Key::Right),// KEY_RIGHT
28 => Some(Key::Enter), // KEY_ENTER
1 => Some(Key::Escape), // KEY_ESC
57 => Some(Key::Space), // KEY_SPACE
14 => Some(Key::Backspace), // KEY_BACKSPACE
15 => Some(Key::Tab), // KEY_TAB
_ => None,
} }
} }
@@ -151,8 +138,25 @@ impl InputDevice for VirtioInputDevice {
let (ev_type, ev_code, ev_value) = self.read_event(desc_idx); let (ev_type, ev_code, ev_value) = self.read_event(desc_idx);
self.resubmit(desc_idx); self.resubmit(desc_idx);
if let Some(event) = self.pointer.event(ev_type, ev_code, ev_value) {
return Some(event);
}
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_SHIFT | keymap::RIGHT_SHIFT) {
self.shift = ev_value != 0;
continue;
}
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_CTRL | keymap::RIGHT_CTRL) {
self.ctrl = ev_value != 0;
continue;
}
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_ALT | keymap::RIGHT_ALT) {
self.alt = ev_value != 0;
continue;
}
if ev_type == EV_KEY { if ev_type == EV_KEY {
if let Some(key) = linux_keycode_to_key(ev_code) { let key = keymap::key(ev_code, self.shift).map(|k| if self.ctrl { keymap::with_ctrl(k) } else { k });
let key = if self.alt { key.and_then(keymap::with_alt) } else { key };
if let Some(key) = key {
if ev_value == KEY_PRESS { if ev_value == KEY_PRESS {
return Some(InputEvent::KeyDown(key)); return Some(InputEvent::KeyDown(key));
} else if ev_value == 0 { } else if ev_value == 0 {
@@ -169,8 +173,7 @@ impl InputDevice for VirtioInputDevice {
} }
fn acknowledge(&mut self) { fn acknowledge(&mut self) {
let regs = MmioInput; let status = self.regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
let status = regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS); self.regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
} }
} }

View File

@@ -0,0 +1,79 @@
//! A tablet's events (Linux codes, as virtio-input reports them) to
//! pointer events: absolute axes collect until the report's sync, then a
//! move; buttons press and let go where the pointer is. Positions are
//! fractions of the screen, 0 to 65535 each way.
use port_input_device::InputEvent;
pub const EV_SYN: u16 = 0;
pub const EV_ABS: u16 = 3;
const ABS_X: u16 = 0;
const ABS_Y: u16 = 1;
const BTN_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111;
/// QEMU's tablet reports 0 to this on each axis.
const ABS_MAX: u32 = 0x7fff;
/// Button ids in pointer events.
pub const LEFT: u8 = 0;
pub const RIGHT: u8 = 1;
#[derive(Default)]
pub struct Pointer {
x: u16,
y: u16,
/// An axis changed since the last sync.
moved: bool,
}
fn scaled(v: u32) -> u16 {
(v.min(ABS_MAX) * 0xffff / ABS_MAX) as u16
}
impl Pointer {
/// What one event of `kind`, `code`, `value` means, if anything yet.
pub fn event(&mut self, kind: u16, code: u16, value: u32) -> Option<InputEvent> {
let (x, y) = (self.x, self.y);
match (kind, code) {
(EV_ABS, ABS_X) => {
self.x = scaled(value);
self.moved = true;
None
}
(EV_ABS, ABS_Y) => {
self.y = scaled(value);
self.moved = true;
None
}
(EV_SYN, _) if self.moved => {
self.moved = false;
Some(InputEvent::PointerMove { id: LEFT, x, y })
}
(super::EV_KEY, BTN_LEFT | BTN_RIGHT) => {
let id = if code == BTN_LEFT { LEFT } else { RIGHT };
match value {
0 => Some(InputEvent::PointerUp { id, x, y }),
1 => Some(InputEvent::PointerDown { id, x, y }),
_ => None,
}
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn axes_move_on_sync_and_buttons_press_where_it_is() {
let mut p = Pointer::default();
assert_eq!(p.event(EV_ABS, ABS_X, 0x7fff), None);
assert_eq!(p.event(EV_ABS, ABS_Y, 0x4000), None);
assert_eq!(p.event(EV_SYN, 0, 0), Some(InputEvent::PointerMove { id: LEFT, x: 0xffff, y: 0x8000 }));
assert_eq!(p.event(EV_SYN, 0, 0), None, "nothing moved since");
assert_eq!(p.event(super::super::EV_KEY, BTN_LEFT, 1), Some(InputEvent::PointerDown { id: LEFT, x: 0xffff, y: 0x8000 }));
assert_eq!(p.event(super::super::EV_KEY, BTN_RIGHT, 0), Some(InputEvent::PointerUp { id: RIGHT, x: 0xffff, y: 0x8000 }));
}
}

View File

@@ -0,0 +1,10 @@
[package]
name = "driver-rng-virtio"
version = "0.1.0"
edition = "2024"
[dependencies]
types = { workspace = true }
libkos = { workspace = true }
hw-virtio = { workspace = true }
port-entropy = { workspace = true }

View File

@@ -0,0 +1,76 @@
//! virtio-rng (virtio spec 5.4): the device fills a buffer with entropy.
#![no_std]
#![forbid(unsafe_code)]
use hw_virtio::mmio::{MmioAccess, Registers};
use hw_virtio::queue::{Descriptor, VirtioQueue};
use libkos::device::Dma;
use port_entropy::Entropy;
use types::address::{ByteOffset, VirtualAddress};
use types::bounded::PageCount;
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
/// each), clear of program images and heaps.
const MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D3_0000);
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D3_1000);
const BUFFER_VA: VirtualAddress = VirtualAddress::from_raw(0x01D3_3000);
/// Asked for at most this much at a time.
const CHUNK: usize = 256;
pub struct VirtioRng {
regs: Registers,
queue: VirtioQueue,
buffer: Dma,
}
impl VirtioRng {
/// Takes over the virtio-rng device behind `mmio`; `None` if it is not
/// one or any step fails.
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
let regs = Registers::new(libkos::device::map_mmio(mmio, MMIO_VA).ok()?);
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_RNG
{
return None;
}
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let buffer = libkos::device::dma(PageCount::new(1)?, BUFFER_VA).ok()?;
hw_virtio::mmio::init_legacy_device(
&regs,
rings.physical,
hw_virtio::queue::QUEUE_SIZE as u32,
);
Some(Self {
regs,
queue: VirtioQueue::new(rings.window),
buffer,
})
}
}
impl Entropy for VirtioRng {
fn fill(&mut self, buf: &mut [u8]) -> bool {
let mut done = 0;
while done < buf.len() {
let want = (buf.len() - done).min(CHUNK);
let desc = [Descriptor {
addr: self.buffer.physical.raw() as u64,
len: want as u32,
writable: true,
}];
let got = match self.queue.submit_written(&self.regs, &desc) {
Some(n) if n > 0 => (n as usize).min(want),
// The device gave nothing (or never answered): no randomness.
_ => return false,
};
self.buffer
.window
.copy_to(ByteOffset::ZERO, &mut buf[done..done + got]);
done += got;
}
// What was handed out is not left lying in the buffer.
self.buffer.window.fill_u32(ByteOffset::ZERO, CHUNK / 4, 0);
true
}
}

View File

@@ -1,4 +1,5 @@
#![no_std] #![no_std]
#![forbid(unsafe_code)]
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@@ -0,0 +1,10 @@
[package]
name = "filecrypt"
version = "0.1.0"
edition = "2024"
[dependencies]
chacha20poly1305 = { workspace = true }
chacha20 = { workspace = true }
blake2 = { workspace = true }
zeroize = { workspace = true }

View File

@@ -0,0 +1,39 @@
//! Randomness from a seed: ChaCha20's keystream, re-keyed after every
//! request (fast key erasure), so what came out before cannot be worked
//! back from the state. The seed must be fresh each boot (the gatekeeper
//! draws it from the hardware), or nonces would repeat.
use chacha20::ChaCha20;
use chacha20::cipher::{KeyIvInit, StreamCipher};
use zeroize::Zeroize;
use crate::{KEY_SIZE, Random};
pub struct Drbg {
key: [u8; KEY_SIZE],
}
impl Drbg {
pub fn new(seed: [u8; KEY_SIZE]) -> Self {
Self { key: seed }
}
}
impl Random for Drbg {
fn fill(&mut self, buf: &mut [u8]) {
let mut stream = ChaCha20::new((&self.key).into(), &[0u8; 12].into());
// The next key first, then the output.
let mut next = [0u8; KEY_SIZE];
stream.apply_keystream(&mut next);
buf.fill(0);
stream.apply_keystream(buf);
self.key = next;
next.zeroize();
}
}
impl Drop for Drbg {
fn drop(&mut self) {
self.key.zeroize();
}
}

186
crates/filecrypt/src/lib.rs Normal file
View File

@@ -0,0 +1,186 @@
//! How a file in an encrypted area looks on disk (see the vision's
//! Encryption note): a header with the file's salt, then its contents in
//! chunks, each sealed with XChaCha20-Poly1305 under a fresh random nonce,
//! bound to its place in the file and to whether it is the last one.
//! Changing, swapping or cutting chunks off is detected; restoring an old
//! copy of a whole file is not. Pure: no I/O.
//!
//! Keys: the master key → an area's key (`area_key`, keyed BLAKE2s over
//! the area's name) → each file's key (over its salt).
#![no_std]
#![forbid(unsafe_code)]
mod drbg;
#[cfg(test)]
mod tests;
pub use drbg::Drbg;
use blake2::Blake2sMac256;
use blake2::digest::Mac;
use chacha20poly1305::aead::{AeadInPlace, KeyInit};
use chacha20poly1305::{Tag, XChaCha20Poly1305, XNonce};
use zeroize::Zeroize;
pub const KEY_SIZE: usize = 32;
const MAGIC: &[u8; 8] = b"KOSFILE1";
const SALT_SIZE: usize = 24;
/// The header: magic and salt.
pub const HEADER: usize = 8 + SALT_SIZE;
const NONCE: usize = 24;
const TAG: usize = 16;
/// What a chunk adds to what it holds.
pub const OVERHEAD: usize = NONCE + TAG;
/// A sealed chunk on disk, the last one possibly shorter.
pub const CHUNK: usize = 4096;
/// What a full chunk holds.
pub const PLAIN_CHUNK: usize = CHUNK - OVERHEAD;
/// A key, wiped from memory when dropped.
#[derive(Clone)]
pub struct Key(pub [u8; KEY_SIZE]);
impl Drop for Key {
fn drop(&mut self) {
self.0.zeroize();
}
}
fn mac(key: &[u8; KEY_SIZE], parts: &[&[u8]]) -> Key {
let mut m =
<Blake2sMac256 as KeyInit>::new_from_slice(key).expect("BLAKE2s takes a 32-byte key");
for p in parts {
m.update(p);
}
Key(m.finalize().into_bytes().into())
}
/// The key of the area named `area` (`home`, `data/kos.notes`, ...):
/// each app's data is noise under another app's key.
pub fn area_key(master: &Key, area: &[u8]) -> Key {
mac(&master.0, &[b"kos/storage/v1/area/", area])
}
/// Randomness for salts and nonces.
pub trait Random {
fn fill(&mut self, buf: &mut [u8]);
}
/// A file's header: its salt, which with its area's key makes its key.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Header {
salt: [u8; SALT_SIZE],
}
impl Header {
pub fn new(rng: &mut impl Random) -> Self {
let mut salt = [0u8; SALT_SIZE];
rng.fill(&mut salt);
Self { salt }
}
pub fn encode(&self) -> [u8; HEADER] {
let mut out = [0u8; HEADER];
out[..8].copy_from_slice(MAGIC);
out[8..].copy_from_slice(&self.salt);
out
}
/// `None` unless it begins an encrypted file (a plaintext file, say).
pub fn decode(bytes: &[u8]) -> Option<Self> {
let bytes = bytes.get(..HEADER)?;
if &bytes[..8] != MAGIC {
return None;
}
let mut salt = [0u8; SALT_SIZE];
salt.copy_from_slice(&bytes[8..]);
Some(Self { salt })
}
pub fn file_key(&self, area: &Key) -> Key {
mac(&area.0, &[b"kos/storage/v1/file/", &self.salt])
}
}
/// How many plaintext bytes a file holding `disk` bytes has; `None` if no
/// encrypted file is that long (a chunk cut short).
pub fn plain_len(disk: usize) -> Option<usize> {
let body = disk.checked_sub(HEADER)?;
let (full, rest) = (body / CHUNK, body % CHUNK);
match rest {
// The last chunk is full.
0 if full > 0 => Some(full * PLAIN_CHUNK),
r if r >= OVERHEAD => Some(full * PLAIN_CHUNK + r - OVERHEAD),
_ => None,
}
}
/// How many chunks hold `plain` bytes: always at least one, so even an
/// empty file has a last chunk to say it is complete.
pub fn chunks(plain: usize) -> usize {
plain.div_ceil(PLAIN_CHUNK).max(1)
}
/// What a file holding `plain` bytes takes on disk.
pub fn disk_len(plain: usize) -> usize {
HEADER + plain + chunks(plain) * OVERHEAD
}
/// Where chunk `index` starts on disk.
pub fn chunk_offset(index: usize) -> usize {
HEADER + index * CHUNK
}
/// Binds a chunk to its place, and to being the last or not.
fn associated(index: u64, last: bool) -> [u8; 9] {
let mut ad = [0u8; 9];
ad[..8].copy_from_slice(&index.to_le_bytes());
ad[8] = u8::from(last);
ad
}
/// Seals `plain` (at most `PLAIN_CHUNK` bytes) as chunk `index` into `out`
/// (room for `plain.len() + OVERHEAD`); its length.
pub fn seal(
key: &Key,
index: u64,
last: bool,
plain: &[u8],
out: &mut [u8],
rng: &mut impl Random,
) -> usize {
let n = plain.len().min(PLAIN_CHUNK);
let (nonce, rest) = out.split_at_mut(NONCE);
rng.fill(nonce);
let (body, tag) = rest.split_at_mut(n);
body.copy_from_slice(&plain[..n]);
let t = XChaCha20Poly1305::new((&key.0).into())
.encrypt_in_place_detached(XNonce::from_slice(nonce), &associated(index, last), body)
.expect("a chunk is far below XChaCha20-Poly1305's limits");
tag[..TAG].copy_from_slice(&t);
n + OVERHEAD
}
/// Opens chunk `index` (`sealed` as read from disk) into `out`; its length,
/// or `None` if it is not that chunk of that file, unaltered.
pub fn open(key: &Key, index: u64, last: bool, sealed: &[u8], out: &mut [u8]) -> Option<usize> {
let n = sealed.len().checked_sub(OVERHEAD)?;
let (nonce, rest) = sealed.split_at(NONCE);
let (body, tag) = rest.split_at(n);
let out = out.get_mut(..n)?;
out.copy_from_slice(body);
let ok = XChaCha20Poly1305::new((&key.0).into())
.decrypt_in_place_detached(
XNonce::from_slice(nonce),
&associated(index, last),
out,
Tag::from_slice(tag),
)
.is_ok();
if !ok {
out.zeroize();
}
ok.then_some(n)
}

View File

@@ -0,0 +1,130 @@
extern crate std;
use crate::*;
use std::vec;
fn master() -> Key {
Key([7; KEY_SIZE])
}
fn rng() -> Drbg {
Drbg::new([1; KEY_SIZE])
}
fn sealed(key: &Key, index: u64, last: bool, plain: &[u8]) -> std::vec::Vec<u8> {
let mut out = vec![0u8; plain.len() + OVERHEAD];
let n = seal(key, index, last, plain, &mut out, &mut rng());
out.truncate(n);
out
}
#[test]
fn a_chunk_opens_only_as_itself() {
let key = Header::new(&mut rng()).file_key(&area_key(&master(), b"home"));
let chunk = sealed(&key, 3, true, b"hello, k-os");
let mut out = [0u8; PLAIN_CHUNK];
assert_eq!(open(&key, 3, true, &chunk, &mut out), Some(11));
assert_eq!(&out[..11], b"hello, k-os");
assert_eq!(
open(&key, 4, true, &chunk, &mut out),
None,
"moved elsewhere in the file"
);
assert_eq!(
open(&key, 3, false, &chunk, &mut out),
None,
"no longer the last: something was cut off or added"
);
let other = Header::new(&mut Drbg::new([2; KEY_SIZE])).file_key(&area_key(&master(), b"home"));
assert_eq!(
open(&other, 3, true, &chunk, &mut out),
None,
"another file's key"
);
for at in [0, 23, 24, 30, chunk.len() - 1] {
let mut bent = chunk.clone();
bent[at] ^= 1;
assert_eq!(
open(&key, 3, true, &bent, &mut out),
None,
"byte {at} changed"
);
}
}
#[test]
fn one_apps_area_key_opens_nothing_of_anothers() {
let header = Header::new(&mut rng());
let notes = header.file_key(&area_key(&master(), b"data/kos.notes"));
let chat = header.file_key(&area_key(&master(), b"data/kos.chat"));
let chunk = sealed(&notes, 0, true, b"private");
let mut out = [0u8; 16];
assert_eq!(open(&chat, 0, true, &chunk, &mut out), None);
let other_master = header.file_key(&area_key(&Key([8; KEY_SIZE]), b"data/kos.notes"));
assert_eq!(
open(&other_master, 0, true, &chunk, &mut out),
None,
"another master key (a wiped device)"
);
}
#[test]
fn every_seal_takes_a_fresh_nonce() {
let key = Key([3; KEY_SIZE]);
let mut r = rng();
let (mut a, mut b) = ([0u8; 64], [0u8; 64]);
seal(&key, 0, true, b"same", &mut a, &mut r);
seal(&key, 0, true, b"same", &mut b, &mut r);
assert_ne!(a, b);
}
#[test]
fn headers_mark_encrypted_files() {
let h = Header::new(&mut rng());
assert_eq!(Header::decode(&h.encode()), Some(h));
assert_eq!(
Header::decode(b"Welcome to k-os. This is a plaintext file."),
None
);
assert_eq!(Header::decode(b"KOSFILE"), None, "too short");
}
#[test]
fn sizes_round_trip_across_chunk_boundaries() {
for plain in [
0,
1,
100,
PLAIN_CHUNK - 1,
PLAIN_CHUNK,
PLAIN_CHUNK + 1,
3 * PLAIN_CHUNK,
3 * PLAIN_CHUNK + 7,
] {
let disk = disk_len(plain);
assert_eq!(plain_len(disk), Some(plain), "{plain}");
}
assert_eq!(chunks(0), 1, "an empty file still has its last chunk");
for cut in [0, HEADER, HEADER + OVERHEAD - 1, HEADER + CHUNK + 5] {
assert_eq!(plain_len(cut), None, "{cut} bytes is no encrypted file");
}
}
#[test]
fn the_drbg_never_repeats_and_depends_on_its_seed() {
let mut a = Drbg::new([1; KEY_SIZE]);
let (mut x, mut y) = ([0u8; 32], [0u8; 32]);
a.fill(&mut x);
a.fill(&mut y);
assert_ne!(x, y);
let mut b = Drbg::new([1; KEY_SIZE]);
let mut z = [0u8; 32];
b.fill(&mut z);
assert_eq!(
x, z,
"the same seed, the same stream (so the seed must be fresh)"
);
let mut c = Drbg::new([2; KEY_SIZE]);
c.fill(&mut z);
assert_ne!(x, z);
}

View File

@@ -5,3 +5,4 @@ edition = "2024"
[dependencies] [dependencies]
types = { workspace = true } types = { workspace = true }
libkos = { workspace = true }

View File

@@ -1,23 +1,30 @@
use crate::mmio::MmioAccess; use crate::mmio::MmioAccess;
use crate::queue::{Descriptor, VirtioQueue}; use crate::queue::{Descriptor, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress}; use libkos::Window;
use types::address::{ByteOffset, PhysicalAddress};
const SECTOR_SIZE: usize = 512; const SECTOR_SIZE: usize = 512;
#[repr(C)] /// One request's memory: the header (type, reserved, sector: 16 bytes),
pub struct BlockReqHeader { /// the status byte the device writes back, and one sector of data.
pub req_type: u32,
pub reserved: u32,
pub sector: u64,
}
pub struct DmaAddrs { pub struct DmaAddrs {
pub header_pa: PhysicalAddress, pub header_pa: PhysicalAddress,
pub header_va: VirtualAddress, pub header: Window,
pub status_pa: PhysicalAddress, pub status_pa: PhysicalAddress,
pub status_va: VirtualAddress, pub status: Window,
pub data_pa: PhysicalAddress, pub data_pa: PhysicalAddress,
pub data_va: VirtualAddress, pub data: Window,
}
const HEADER_TYPE: ByteOffset = ByteOffset::new(0);
const HEADER_RESERVED: ByteOffset = ByteOffset::new(4);
const HEADER_SECTOR: ByteOffset = ByteOffset::new(8);
fn put_header(dma: &DmaAddrs, req_type: u32, sector: u32) {
dma.header.write(HEADER_TYPE, req_type);
dma.header.write(HEADER_RESERVED, 0u32);
dma.header.write(HEADER_SECTOR, sector as u64);
dma.status.write(ByteOffset::ZERO, 0xFFu8);
} }
pub fn read_sector( pub fn read_sector(
@@ -29,13 +36,7 @@ pub fn read_sector(
) -> bool { ) -> bool {
if buf.len() < SECTOR_SIZE { return false; } if buf.len() < SECTOR_SIZE { return false; }
unsafe { put_header(dma, 0, sector);
let hdr = dma.header_va.raw() as *mut BlockReqHeader;
core::ptr::write_volatile(&raw mut (*hdr).req_type, 0);
core::ptr::write_volatile(&raw mut (*hdr).reserved, 0);
core::ptr::write_volatile(&raw mut (*hdr).sector, sector as u64);
core::ptr::write_volatile(dma.status_va.raw() as *mut u8, 0xFF);
}
let descs = [ let descs = [
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false }, Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
@@ -45,18 +46,35 @@ pub fn read_sector(
if !queue.submit(mmio, &descs) { return false; } if !queue.submit(mmio, &descs) { return false; }
let status = unsafe { core::ptr::read_volatile(dma.status_va.raw() as *const u8) }; if dma.status.read::<u8>(ByteOffset::ZERO) != 0 { return false; }
if status != 0 { return false; } dma.data.copy_to(ByteOffset::ZERO, &mut buf[..SECTOR_SIZE]);
if dma.data_va.raw() != buf.as_ptr() as usize {
unsafe {
core::ptr::copy_nonoverlapping(
dma.data_va.raw() as *const u8,
buf.as_mut_ptr(),
SECTOR_SIZE,
);
}
}
true true
} }
/// Request types (virtio spec 5.2.6).
const T_OUT: u32 = 1;
/// Device configuration: the disk's size in 512-byte sectors (le64).
pub const CONFIG_CAPACITY: ByteOffset = ByteOffset::new(0x100);
/// Writes one sector from `buf`; true once the device reports success.
pub fn write_sector(
mmio: &impl MmioAccess,
queue: &VirtioQueue,
dma: &DmaAddrs,
sector: u32,
buf: &[u8],
) -> bool {
if buf.len() < SECTOR_SIZE { return false; }
dma.data.copy_from(ByteOffset::ZERO, &buf[..SECTOR_SIZE]);
put_header(dma, T_OUT, sector);
// The device reads the data this time; only the status is written back.
let descs = [
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
Descriptor { addr: dma.data_pa.raw() as u64, len: SECTOR_SIZE as u32, writable: false },
Descriptor { addr: dma.status_pa.raw() as u64, len: 1, writable: true },
];
if !queue.submit(mmio, &descs) { return false; }
dma.status.read::<u8>(ByteOffset::ZERO) == 0
}

Some files were not shown because too many files have changed in this diff Show More