Compare commits
79 Commits
master
...
kernel-har
| Author | SHA1 | Date | |
|---|---|---|---|
| 1401af3d82 | |||
| 0de13f124d | |||
| 1f2134fcec | |||
| 3b71f29f4c | |||
| 495bf03484 | |||
| 18e8b3ecb8 | |||
| c3eb70191b | |||
| 8c518b84aa | |||
| 7804934008 | |||
| 815a2f9fe2 | |||
| d26b265d38 | |||
| 3b71243dea | |||
| e952a149ec | |||
| 2b82f052ae | |||
| 6827e1455d | |||
| 29becf297c | |||
| a2d4a7d085 | |||
| 419b715a1f | |||
| fe73b89525 | |||
| c256f4ecf3 | |||
| 34a44b5928 | |||
| 07a0575908 | |||
| 864f039bc7 | |||
| d04dcfffa1 | |||
| 3d69c10079 | |||
| 28f1ace00a | |||
| c4871320a0 | |||
| 91f7d28834 | |||
| 29a25bd8c2 | |||
| 9dc751c466 | |||
| e8191be160 | |||
| eb9e3efaac | |||
| 24b0734c73 | |||
| 3b7d004285 | |||
| b7ef67ffd8 | |||
| 600140d518 | |||
| 6b13bf8bbf | |||
| 90e11eb2e8 | |||
| 9bc31a9bbe | |||
| ec3bc0d2a1 | |||
| c8056ddac6 | |||
| 7b8258b231 | |||
| 842d14987c | |||
| 83809bbe94 | |||
| 1fc5a54f06 | |||
| c603a578c0 | |||
| 0fd568cdad | |||
| 4030dd4315 | |||
| b80303f553 | |||
| 2845d190f0 | |||
| 64ec5df379 | |||
| 8587661684 | |||
| 817a38993b | |||
| a4f467f45d | |||
| 4049ba5007 | |||
| 7415bab7f5 | |||
| 33e1c849d1 | |||
| 37c482c36c | |||
| b4390b7b6d | |||
| 99273db6d3 | |||
| 95dbe7b9d5 | |||
| b814bd9570 | |||
| 909b3f0503 | |||
| 694c675a40 | |||
| 8bc5402a29 | |||
| e8a2963b24 | |||
| 2a8a5b0c27 | |||
| 19aeabd86d | |||
| 2e665fd66e | |||
| b87a62475f | |||
| 55758ae5b2 | |||
| bafc18a5f8 | |||
| 130a82f50d | |||
| 154599e3af | |||
| 76f10ff1f1 | |||
| d43421e33e | |||
| 8d2c40cb1e | |||
| e931485ca7 | |||
| 1c04ddf909 |
@@ -1,3 +1,7 @@
|
||||
[target.riscv64gc-unknown-none-elf]
|
||||
runner = "qemu-system-riscv64 -machine virt -nographic -bios default -kernel"
|
||||
rustflags = ["-C", "panic=abort"]
|
||||
|
||||
[env]
|
||||
# Kernel tables are large fixed arrays; tests build `Kernel` by value.
|
||||
RUST_MIN_STACK = "33554432"
|
||||
|
||||
525
Cargo.lock
generated
525
Cargo.lock
generated
@@ -12,16 +12,61 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"domain",
|
||||
"elf",
|
||||
"hw-virtio",
|
||||
"kernel",
|
||||
"platform",
|
||||
"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]]
|
||||
name = "alarm-server"
|
||||
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]]
|
||||
name = "audio-capture-server"
|
||||
version = "0.1.0"
|
||||
@@ -30,10 +75,40 @@ version = "0.1.0"
|
||||
name = "audio-playback-server"
|
||||
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]]
|
||||
name = "bluetooth-server"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "calendar-server"
|
||||
version = "0.1.0"
|
||||
@@ -42,6 +117,55 @@ version = "0.1.0"
|
||||
name = "camera-server"
|
||||
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]]
|
||||
name = "clipboard-server"
|
||||
version = "0.1.0"
|
||||
@@ -50,12 +174,58 @@ version = "0.1.0"
|
||||
name = "contacts-server"
|
||||
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]]
|
||||
name = "display-qemu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"display-server",
|
||||
"driver-framebuffer-mock",
|
||||
"driver-framebuffer-virtio",
|
||||
"libkos",
|
||||
"port-framebuffer",
|
||||
@@ -68,8 +238,11 @@ name = "display-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"driver-framebuffer-mock",
|
||||
"libkos",
|
||||
"port-framebuffer",
|
||||
"raster",
|
||||
"server-framework",
|
||||
"theme",
|
||||
"types",
|
||||
]
|
||||
|
||||
@@ -85,6 +258,7 @@ name = "driver-block-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hw-virtio",
|
||||
"libkos",
|
||||
"port-block-device",
|
||||
"types",
|
||||
]
|
||||
@@ -102,6 +276,7 @@ name = "driver-framebuffer-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hw-virtio",
|
||||
"libkos",
|
||||
"port-framebuffer",
|
||||
"types",
|
||||
]
|
||||
@@ -110,7 +285,9 @@ dependencies = [
|
||||
name = "driver-input-uart"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libkos",
|
||||
"port-input-device",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -118,10 +295,21 @@ name = "driver-input-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hw-virtio",
|
||||
"libkos",
|
||||
"port-input-device",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "driver-rng-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hw-virtio",
|
||||
"libkos",
|
||||
"port-entropy",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "echo"
|
||||
version = "0.1.0"
|
||||
@@ -134,14 +322,86 @@ dependencies = [
|
||||
name = "elf"
|
||||
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]]
|
||||
name = "gatekeeper"
|
||||
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]]
|
||||
name = "gps-server"
|
||||
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]]
|
||||
name = "hello"
|
||||
version = "0.1.0"
|
||||
@@ -149,10 +409,21 @@ dependencies = [
|
||||
"libkos",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"k-ui",
|
||||
"libkos",
|
||||
"manifest",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hw-virtio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libkos",
|
||||
"types",
|
||||
]
|
||||
|
||||
@@ -161,10 +432,21 @@ name = "init"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"elf",
|
||||
"hw-virtio",
|
||||
"libkos",
|
||||
"server-framework",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input-qemu"
|
||||
version = "0.1.0"
|
||||
@@ -173,6 +455,7 @@ dependencies = [
|
||||
"driver-input-virtio",
|
||||
"input-server",
|
||||
"libkos",
|
||||
"port-input-device",
|
||||
"server-framework",
|
||||
"types",
|
||||
]
|
||||
@@ -187,6 +470,19 @@ dependencies = [
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k-ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fontdue",
|
||||
"libkos",
|
||||
"libm",
|
||||
"port-framebuffer",
|
||||
"theme",
|
||||
"tiny-skia",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel"
|
||||
version = "0.1.0"
|
||||
@@ -200,13 +496,42 @@ dependencies = [
|
||||
name = "keychain-server"
|
||||
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]]
|
||||
name = "libkos"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"port-framebuffer",
|
||||
"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]]
|
||||
name = "log-server"
|
||||
version = "0.1.0"
|
||||
@@ -216,6 +541,18 @@ dependencies = [
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ls"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libkos",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manifest"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "media-index-server"
|
||||
version = "0.1.0"
|
||||
@@ -228,6 +565,12 @@ version = "0.1.0"
|
||||
name = "notification-server"
|
||||
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]]
|
||||
name = "package-manager"
|
||||
version = "0.1.0"
|
||||
@@ -237,6 +580,7 @@ name = "permission-manager"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libkos",
|
||||
"manifest",
|
||||
"server-framework",
|
||||
"types",
|
||||
]
|
||||
@@ -245,14 +589,28 @@ dependencies = [
|
||||
name = "platform"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"domain",
|
||||
"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]]
|
||||
name = "port-block-device"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "port-entropy"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "port-framebuffer"
|
||||
version = "0.1.0"
|
||||
@@ -272,6 +630,15 @@ version = "0.1.0"
|
||||
name = "print-server"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "raster"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"driver-framebuffer-mock",
|
||||
"port-framebuffer",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "server-framework"
|
||||
version = "0.1.0"
|
||||
@@ -289,13 +656,57 @@ dependencies = [
|
||||
"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]]
|
||||
name = "smptest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libkos",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snake"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"display-server",
|
||||
"libkos",
|
||||
"server-framework",
|
||||
"port-framebuffer",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spawntest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flaky",
|
||||
"libkos",
|
||||
"manifest",
|
||||
"types",
|
||||
]
|
||||
|
||||
@@ -312,24 +723,116 @@ name = "storage-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"driver-block-virtio",
|
||||
"filecrypt",
|
||||
"libkos",
|
||||
"port-block-device",
|
||||
"server-framework",
|
||||
"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]]
|
||||
name = "sync-server"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "tally"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"k-ui",
|
||||
"libkos",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telephony-server"
|
||||
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]]
|
||||
name = "types"
|
||||
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]]
|
||||
name = "usb-server"
|
||||
version = "0.1.0"
|
||||
@@ -337,3 +840,15 @@ version = "0.1.0"
|
||||
[[package]]
|
||||
name = "user-manager"
|
||||
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"
|
||||
|
||||
42
Cargo.toml
42
Cargo.toml
@@ -1,11 +1,31 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"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/shell",
|
||||
"crates/apps/cat",
|
||||
"crates/apps/counter",
|
||||
"crates/apps/ls",
|
||||
"crates/apps/echo",
|
||||
"crates/apps/sleep",
|
||||
"crates/apps/smptest",
|
||||
"crates/apps/spawntest",
|
||||
"crates/apps/snake",
|
||||
"crates/domain",
|
||||
"crates/elf",
|
||||
"crates/manifest",
|
||||
"crates/keystore",
|
||||
"crates/filecrypt",
|
||||
"crates/ports/entropy",
|
||||
"crates/drivers/rng/virtio",
|
||||
"crates/tools/app-image",
|
||||
"crates/init",
|
||||
"crates/kernel",
|
||||
"crates/platform",
|
||||
@@ -33,6 +53,7 @@ members = [
|
||||
"crates/servers/power-server",
|
||||
"crates/servers/print-server",
|
||||
"crates/servers/session-server",
|
||||
"crates/servers/terminal-server",
|
||||
"crates/servers/storage-server",
|
||||
"crates/servers/sync-server",
|
||||
"crates/servers/telephony-server",
|
||||
@@ -61,6 +82,18 @@ resolver = "2"
|
||||
types = { path = "crates/types" }
|
||||
domain = { path = "crates/domain" }
|
||||
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" }
|
||||
kernel = { path = "crates/kernel" }
|
||||
server-framework = { path = "crates/server-framework" }
|
||||
@@ -101,6 +134,8 @@ libkos = { path = "crates/libkos" }
|
||||
|
||||
# ports
|
||||
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-input-device = { path = "crates/ports/input-device" }
|
||||
|
||||
@@ -123,3 +158,10 @@ panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
# The randomized kernel invariant tests are too slow unoptimized.
|
||||
[profile.test.package.kernel]
|
||||
opt-level = 2
|
||||
|
||||
[profile.test.package.domain]
|
||||
opt-level = 2
|
||||
|
||||
76
Makefile
76
Makefile
@@ -1,57 +1,103 @@
|
||||
.PHONY: check fmt clippy test kani build all image run
|
||||
.PHONY: check fmt clippy test kani build all image run ci kernel-fmt kernel-clippy kernel-test test-qemu
|
||||
|
||||
RISCV_TARGET = riscv64gc-unknown-none-elf
|
||||
KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv
|
||||
# Harts QEMU boots; `make run-gui SMP=1` for a single core.
|
||||
SMP ?= 4
|
||||
|
||||
KERNEL_CRATES = -p types -p domain -p kernel -p platform
|
||||
|
||||
all: check fmt clippy test
|
||||
|
||||
# Gate for kernel work: formatting, lints, unit tests, Kani proofs, RISC-V build.
|
||||
ci: kernel-fmt kernel-clippy kernel-test kani build
|
||||
|
||||
kernel-fmt:
|
||||
cargo fmt $(KERNEL_CRATES) -p adapter-riscv -- --check
|
||||
|
||||
kernel-clippy:
|
||||
cargo clippy $(KERNEL_CRATES) --all-targets -- -D warnings
|
||||
cargo clippy -p adapter-riscv --target $(RISCV_TARGET) --no-deps -- -D warnings
|
||||
|
||||
kernel-test:
|
||||
cargo test $(KERNEL_CRATES)
|
||||
|
||||
check:
|
||||
cargo check --workspace
|
||||
|
||||
fmt:
|
||||
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:
|
||||
cargo clippy --workspace -- -D warnings
|
||||
cargo clippy --workspace --exclude adapter-riscv --lib -- -D warnings
|
||||
cargo clippy --target $(RISCV_TARGET) --release $(PROGRAMS) -- -D warnings
|
||||
|
||||
test:
|
||||
cargo test --workspace
|
||||
|
||||
# CBMC can eat all RAM and take the desktop down with it; cap each run.
|
||||
KANI = systemd-run --user --scope -q -p MemoryMax=12G -p MemorySwapMax=0 cargo kani
|
||||
|
||||
kani-types:
|
||||
cargo kani -p types
|
||||
$(KANI) -p types
|
||||
|
||||
kani-domain:
|
||||
cargo kani -p domain
|
||||
$(KANI) -p domain
|
||||
|
||||
kani-kernel:
|
||||
cargo kani -p kernel
|
||||
$(KANI) -p kernel
|
||||
|
||||
kani: kani-types kani-domain kani-kernel
|
||||
|
||||
build:
|
||||
cargo build --target $(RISCV_TARGET) --release \
|
||||
-p adapter-riscv -p init -p echo -p snake \
|
||||
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 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:
|
||||
cargo build --target $(RISCV_TARGET) --release $(PROGRAMS)
|
||||
cargo build --target $(RISCV_TARGET) --release -p adapter-riscv
|
||||
@# The kernel's tables are one static: if every empty entry is zero it
|
||||
@# 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
|
||||
scripts/make-disk-image.sh
|
||||
[ -f $(KEYS_IMG) ] || truncate -s 64K $(KEYS_IMG)
|
||||
|
||||
# Boots a test image (normal image + bin/smptest) on $(SMP) harts; init runs
|
||||
# the SMP self-test and halts. Passes only on "SMPTEST PASS".
|
||||
test-qemu: build
|
||||
IMG=test.img SMPTEST=1 scripts/make-disk-image.sh > /dev/null
|
||||
SMP=$(SMP) scripts/test-qemu.sh test.img
|
||||
|
||||
run: build
|
||||
qemu-system-riscv64 -machine virt -nographic -bios default \
|
||||
qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -nographic -bios default \
|
||||
-kernel $(KERNEL_BIN)
|
||||
|
||||
run-disk: image
|
||||
qemu-system-riscv64 -machine virt -nographic -bios default \
|
||||
qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -nographic -bios default \
|
||||
-kernel $(KERNEL_BIN) \
|
||||
-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
|
||||
qemu-system-riscv64 -machine virt -bios default \
|
||||
qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -bios default \
|
||||
-kernel $(KERNEL_BIN) \
|
||||
-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-keyboard-device \
|
||||
-device virtio-keyboard-device -device virtio-tablet-device \
|
||||
-serial stdio
|
||||
|
||||
@@ -14,7 +14,6 @@ domain = { workspace = true }
|
||||
elf = { workspace = true }
|
||||
platform = { workspace = true }
|
||||
kernel = { workspace = true }
|
||||
hw-virtio = { workspace = true }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let ld = std::path::Path::new("linker.ld").canonicalize().unwrap();
|
||||
println!("cargo:rustc-link-arg=-T{}", ld.display());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
OUTPUT_ARCH(riscv)
|
||||
ENTRY(_start)
|
||||
|
||||
/* The kernel runs in the upper half: linked at its physical load address
|
||||
plus PHYS_OFFSET (mmu::PHYS_OFFSET), loaded at the physical address.
|
||||
Firmware jumps to the physical entry with the MMU off; _start switches
|
||||
to the high mapping before any Rust code runs. */
|
||||
PHYS_OFFSET = 0xFFFFFFC000000000;
|
||||
LOAD_ADDRESS = 0x80200000;
|
||||
ENTRY(_start_phys)
|
||||
|
||||
/* Every boundary below is page aligned: the kernel maps each range with
|
||||
its own permissions (W^X), see mmu::init_kernel_page_table. */
|
||||
SECTIONS {
|
||||
. = 0x80200000;
|
||||
. = PHYS_OFFSET + LOAD_ADDRESS;
|
||||
_text_start = .;
|
||||
|
||||
.text : {
|
||||
.text : AT(ADDR(.text) - PHYS_OFFSET) {
|
||||
*(.text.entry)
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4096);
|
||||
.rodata : {
|
||||
_text_end = .;
|
||||
|
||||
.rodata : AT(ADDR(.rodata) - PHYS_OFFSET) {
|
||||
*(.rodata .rodata.*)
|
||||
*(.srodata .srodata.*)
|
||||
}
|
||||
.eh_frame : AT(ADDR(.eh_frame) - PHYS_OFFSET) {
|
||||
*(.eh_frame)
|
||||
}
|
||||
|
||||
. = ALIGN(4096);
|
||||
.data : {
|
||||
_rodata_end = .;
|
||||
|
||||
.data : AT(ADDR(.data) - PHYS_OFFSET) {
|
||||
*(.data .data.*)
|
||||
*(.sdata .sdata.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4096);
|
||||
.bss : {
|
||||
.bss : AT(ADDR(.bss) - PHYS_OFFSET) {
|
||||
_bss_start = .;
|
||||
*(.bss .bss.*)
|
||||
*(.sbss .sbss.*)
|
||||
_bss_end = .;
|
||||
}
|
||||
|
||||
/* One kernel stack per hart (smp::MAX_HARTS, smp::STACK_SLOT): each
|
||||
slot is an unmapped guard page, so overflowing a stack faults
|
||||
instead of silently overwriting what lies below, then the stack. */
|
||||
. = ALIGN(4096);
|
||||
_stack_bottom = .;
|
||||
. += 0x80000;
|
||||
_stack_top = .;
|
||||
_stacks_start = .;
|
||||
. += 8 * (4096 + 0x80000);
|
||||
_stacks_end = .;
|
||||
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
_start_phys = _start - PHYS_OFFSET;
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
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 VIRTIO_BASE: usize = 0x10001000;
|
||||
pub const VIRTIO_SIZE: usize = 0x8000;
|
||||
pub const PLIC_BASE: usize = 0x0C000000;
|
||||
pub const CLINT_BASE: usize = 0x02000000;
|
||||
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;
|
||||
|
||||
/// QEMU virt's PLIC lines.
|
||||
const UART_IRQ: u16 = 10;
|
||||
const VIRTIO_IRQ_BASE: u16 = 1;
|
||||
const VIRTIO_SLOT_SIZE: usize = 0x1000;
|
||||
const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE;
|
||||
|
||||
pub const BOARD_DEVICES: usize = 1 + VIRTIO_SLOTS;
|
||||
|
||||
/// What each entry of `devices()` is, for init's BootInfo.
|
||||
pub fn kinds() -> [u64; BOARD_DEVICES] {
|
||||
let mut kinds = [types::bootinfo::KIND_VIRTIO; BOARD_DEVICES];
|
||||
kinds[0] = types::bootinfo::KIND_UART_16550;
|
||||
kinds
|
||||
}
|
||||
|
||||
/// Every device userspace may drive: the UART and each VirtIO MMIO slot.
|
||||
/// The PLIC, CLINT and RAM stay the kernel's.
|
||||
pub fn devices() -> Option<[kernel::BoardDevice; BOARD_DEVICES]> {
|
||||
use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId};
|
||||
let one = PageCount::new(1)?;
|
||||
let device = |base: usize, irq: u16| kernel::BoardDevice {
|
||||
base: PhysicalAddress::from_raw(base),
|
||||
pages: one,
|
||||
irq: Some(IrqId::from_raw(irq)),
|
||||
};
|
||||
let mut all = [device(UART_BASE, UART_IRQ); BOARD_DEVICES];
|
||||
for (i, d) in all.iter_mut().skip(1).enumerate() {
|
||||
*d = device(
|
||||
VIRTIO_BASE + i * VIRTIO_SLOT_SIZE,
|
||||
VIRTIO_IRQ_BASE + i as u16,
|
||||
);
|
||||
}
|
||||
Some(all)
|
||||
}
|
||||
|
||||
@@ -5,54 +5,46 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
pub fn grant_all(
|
||||
uart: &Uart,
|
||||
init_pid: ProcessId,
|
||||
storage_pid: ProcessId,
|
||||
display_pid: ProcessId,
|
||||
input_pid: ProcessId,
|
||||
) {
|
||||
/// Init gets storage's process and endpoint (slots 1 and 2), storage its
|
||||
/// endpoint (slot 0); everything else init starts itself.
|
||||
pub fn grant_all(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId) {
|
||||
let all_perms = types::permission::PermissionSet::empty()
|
||||
.with(types::permission::Permission::Read)
|
||||
.with(types::permission::Permission::Write)
|
||||
.with(types::permission::Permission::Execute)
|
||||
.with(types::permission::Permission::Delegate);
|
||||
|
||||
// Delegate so storage can mint the badged copies it grants.
|
||||
let ipc_perms = types::permission::PermissionSet::empty()
|
||||
.with(types::permission::Permission::Read)
|
||||
.with(types::permission::Permission::Execute);
|
||||
.with(types::permission::Permission::Execute)
|
||||
.with(types::permission::Permission::Delegate);
|
||||
|
||||
let storage_ep = kern().create_endpoint()
|
||||
let storage_ep = kern()
|
||||
.create_endpoint(storage_pid)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "storage endpoint failed"));
|
||||
|
||||
kern().grant_capability(init_pid, domain::resource::Resource::Process(storage_pid), all_perms)
|
||||
kern()
|
||||
.grant_capability(
|
||||
init_pid,
|
||||
domain::resource::Resource::Process(storage_pid),
|
||||
all_perms,
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||
kern().grant_capability(init_pid, domain::resource::Resource::Endpoint(storage_ep), all_perms)
|
||||
kern()
|
||||
.grant_capability(
|
||||
init_pid,
|
||||
domain::resource::Resource::Endpoint(storage_ep),
|
||||
all_perms,
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||
|
||||
kern().grant_capability(storage_pid, domain::resource::Resource::Endpoint(storage_ep), ipc_perms)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||
|
||||
let display_ep = kern().create_endpoint()
|
||||
.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()
|
||||
.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)
|
||||
kern()
|
||||
.grant_capability(
|
||||
storage_pid,
|
||||
domain::resource::Resource::Endpoint(storage_ep),
|
||||
ipc_perms,
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||
|
||||
uart.write_str("k-os: capabilities granted\n");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::hw::uart::Uart;
|
||||
use crate::hw::virtio_blk::VirtioBlk;
|
||||
use crate::{board, kernel_cell::KernelCell, mmu, platform, trap, KERNEL};
|
||||
use crate::{board, mmu, platform, trap};
|
||||
|
||||
unsafe extern "C" {
|
||||
static _bss_start: u8;
|
||||
@@ -8,40 +7,64 @@ unsafe extern "C" {
|
||||
static _heap_start: u8;
|
||||
}
|
||||
|
||||
pub fn init(uart: &Uart) -> &'static mut mmu::PageTable {
|
||||
pub fn init(uart: &Uart, boot_hart: usize) -> &'static mut mmu::PageTable {
|
||||
zero_bss();
|
||||
|
||||
let heap_start = unsafe { &_heap_start as *const u8 as usize };
|
||||
mmu::init_allocator(heap_start);
|
||||
let kernel_pt = mmu::init_kernel_page_table();
|
||||
let harts = crate::smp::probe_harts();
|
||||
if boot_hart >= harts {
|
||||
crate::halt(uart, "boot hart outside the hart range");
|
||||
}
|
||||
crate::smp::init(
|
||||
boot_hart,
|
||||
harts,
|
||||
mmu::virt_to_phys(kernel_pt as *const mmu::PageTable as usize),
|
||||
);
|
||||
mmu::activate(kernel_pt);
|
||||
uart.write_str("k-os: MMU enabled (Sv39)\n");
|
||||
match mmu::check_wx(kernel_pt) {
|
||||
Ok(code_pages) => {
|
||||
uart.write_str("k-os: W^X verified, ");
|
||||
uart.write_dec(code_pages);
|
||||
uart.write_str(" code pages\n");
|
||||
}
|
||||
Err(v) => {
|
||||
let (what, va) = match v {
|
||||
mmu::page_table::WxViolation::WritableCode(va) => ("writable code", va),
|
||||
mmu::page_table::WxViolation::ExecutableData(va) => ("executable data", va),
|
||||
mmu::page_table::WxViolation::MappedHole(va) => ("mapped hole", va),
|
||||
mmu::page_table::WxViolation::LowerHalf(va) => ("lower-half mapping", va),
|
||||
};
|
||||
uart.write_str("k-os: W^X violation: ");
|
||||
uart.write_str(what);
|
||||
uart.write_str(" at ");
|
||||
uart.write_hex(va);
|
||||
uart.put_char(b'\n');
|
||||
crate::halt(uart, "kernel page table violates W^X");
|
||||
}
|
||||
}
|
||||
|
||||
trap::init();
|
||||
crate::hw::timer::init();
|
||||
crate::hw::plic::Plic::init();
|
||||
uart.write_str("k-os: traps and timer initialized\n");
|
||||
|
||||
let riscv_platform = platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize);
|
||||
let pool = domain::memory::page_pool::PagePool::new(
|
||||
if board::USER_POOL_PAGES > domain::memory::frame::MAX_FRAMES {
|
||||
crate::halt(uart, "user pool larger than the frame table");
|
||||
}
|
||||
let riscv_platform =
|
||||
platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8);
|
||||
crate::kern_mut().install_platform(riscv_platform);
|
||||
crate::kern_mut().install_pool(
|
||||
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
|
||||
board::USER_POOL_PAGES,
|
||||
);
|
||||
KERNEL.init(kernel::Kernel::new(riscv_platform, pool));
|
||||
|
||||
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() {
|
||||
unsafe {
|
||||
let start = &_bss_start as *const u8 as *mut u8;
|
||||
|
||||
@@ -1,84 +1,69 @@
|
||||
use crate::hw::uart::Uart;
|
||||
use crate::hw::virtio_blk::VirtioBlk;
|
||||
use crate::{boot_fs, mmu};
|
||||
use crate::mmu;
|
||||
use types::address::VirtualAddress;
|
||||
use types::budget::Limits;
|
||||
use types::id::{ProcessId, ThreadId};
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
const PAGE_SIZE: usize = mmu::PAGE_SIZE;
|
||||
const USER_VA_BASE: usize = 0x10000;
|
||||
const MAX_FILE_SIZE: usize = 64 * 1024;
|
||||
/// 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;
|
||||
|
||||
pub struct LoadedProcess {
|
||||
pub pid: ProcessId,
|
||||
pub entry: usize,
|
||||
}
|
||||
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
pub fn load_init(
|
||||
uart: &Uart,
|
||||
blk: &mut VirtioBlk,
|
||||
kernel_pt: &mmu::PageTable,
|
||||
) -> (LoadedProcess, ThreadId) {
|
||||
/// The two programs boot starts, packed into the kernel image.
|
||||
pub static INIT_ELF: &[u8] = include_bytes!(env!("KOS_INIT_ELF"));
|
||||
pub static STORAGE_ELF: &[u8] = include_bytes!(env!("KOS_STORAGE_ELF"));
|
||||
|
||||
pub fn load_init(uart: &Uart) -> LoadedProcess {
|
||||
let (pid, tid) = kern()
|
||||
.boot(types::bounded::Priority::DEFAULT, types::time::TimeSlice::DEFAULT)
|
||||
.boot(
|
||||
types::bounded::Priority::DEFAULT,
|
||||
types::time::TimeSlice::DEFAULT,
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
|
||||
|
||||
let entry = load_elf(blk, kernel_pt, pid, b"init.elf");
|
||||
if entry == 0 { crate::halt(uart, "init.elf not found"); }
|
||||
let entry = load_elf(uart, pid, INIT_ELF);
|
||||
setup_thread(tid, entry);
|
||||
uart.write_str("k-os: init loaded\n");
|
||||
|
||||
(LoadedProcess { pid, entry }, tid)
|
||||
LoadedProcess { pid }
|
||||
}
|
||||
|
||||
pub fn load_server(
|
||||
uart: &Uart,
|
||||
blk: &mut VirtioBlk,
|
||||
kernel_pt: &mmu::PageTable,
|
||||
filename: &[u8],
|
||||
label: &str,
|
||||
) -> LoadedProcess {
|
||||
let pid = kern().create_process_internal()
|
||||
/// Storage's budget, carved from init's.
|
||||
const STORAGE: Limits = {
|
||||
use types::storage::{BUFFER_PAGES, MAX_CLIENTS};
|
||||
Limits {
|
||||
// A buffer per client, and its image, heap and cache.
|
||||
pages: MAX_CLIENTS * BUFFER_PAGES + 256,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
// A buffer per client, and the disk's DMA.
|
||||
regions: MAX_CLIENTS + 4,
|
||||
}
|
||||
};
|
||||
|
||||
/// Storage, which init needs to read anything else, its budget carved
|
||||
/// from init's.
|
||||
pub fn load_storage(uart: &Uart, init: ProcessId) -> LoadedProcess {
|
||||
let pid = kern()
|
||||
.create_process_internal(init, STORAGE)
|
||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||
let entry = load_elf(blk, kernel_pt, pid, filename);
|
||||
if entry == 0 { crate::halt(uart, "elf not found"); }
|
||||
let tid = kern().spawn_thread_internal(pid)
|
||||
let entry = load_elf(uart, pid, STORAGE_ELF);
|
||||
let tid = kern()
|
||||
.spawn_thread_internal(pid)
|
||||
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
|
||||
setup_thread(tid, entry);
|
||||
uart.write_str("k-os: ");
|
||||
uart.write_str(label);
|
||||
uart.write_str(" loaded\n");
|
||||
uart.write_str("k-os: storage loaded\n");
|
||||
|
||||
LoadedProcess { pid, entry }
|
||||
}
|
||||
|
||||
pub fn enter_userspace(pid: ProcessId, entry: usize, shared_buf_va: usize) -> ! {
|
||||
let pt_addr = kern().platform_mut().page_table_for(pid);
|
||||
if pt_addr != 0 {
|
||||
mmu::activate(unsafe { &*(pt_addr as *const mmu::PageTable) });
|
||||
}
|
||||
|
||||
let kernel_sp: usize;
|
||||
unsafe { core::arch::asm!("mv {0}, sp", out(reg) kernel_sp) };
|
||||
crate::trap::set_kernel_stack(kernel_sp);
|
||||
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"csrc sstatus, {spp}",
|
||||
"csrs sstatus, {spie}",
|
||||
"csrw sepc, {entry}",
|
||||
"mv sp, {stack}",
|
||||
"mv a0, {arg}",
|
||||
"sret",
|
||||
spp = in(reg) 1usize << 8,
|
||||
spie = in(reg) 1usize << 5,
|
||||
entry = in(reg) entry,
|
||||
stack = in(reg) USER_VA_BASE,
|
||||
arg = in(reg) shared_buf_va,
|
||||
options(noreturn),
|
||||
);
|
||||
}
|
||||
LoadedProcess { pid }
|
||||
}
|
||||
|
||||
fn setup_thread(tid: ThreadId, entry: usize) {
|
||||
@@ -89,88 +74,86 @@ fn setup_thread(tid: ThreadId, entry: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_elf(
|
||||
blk: &mut VirtioBlk,
|
||||
kernel_pt: &mmu::PageTable,
|
||||
pid: ProcessId,
|
||||
filename: &[u8],
|
||||
) -> usize {
|
||||
let buf_base = mmu::allocator::alloc_zeroed_page();
|
||||
let file_pages = MAX_FILE_SIZE / 4096;
|
||||
for _ in 1..file_pages {
|
||||
mmu::allocator::alloc_zeroed_page();
|
||||
}
|
||||
let buf = unsafe { core::slice::from_raw_parts_mut(buf_base as *mut u8, MAX_FILE_SIZE) };
|
||||
|
||||
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0);
|
||||
if file_size == 0 { return 0; }
|
||||
|
||||
let elf_info = match elf::parse(&buf[..file_size]) {
|
||||
Ok(info) => info,
|
||||
Err(_) => return 0,
|
||||
/// Copies `data` into `pid`'s already mapped pages starting at `va`.
|
||||
fn copy_to_user(pid: ProcessId, va: usize, data: &[u8]) -> bool {
|
||||
let mut done = 0;
|
||||
while done < data.len() {
|
||||
let addr = va + done;
|
||||
let page = addr & !(PAGE_SIZE - 1);
|
||||
let offset = addr - page;
|
||||
let Some(pa) = kern().user_page_frame(pid, VirtualAddress::from_raw(page)) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let user_pt = mmu::create_user_page_table(
|
||||
kernel_pt,
|
||||
types::address::PhysicalAddress::from_raw(0),
|
||||
0,
|
||||
types::address::VirtualAddress::from_raw(USER_VA_BASE),
|
||||
);
|
||||
|
||||
for segment in elf_info.segments() {
|
||||
let page_start = segment.vaddr & !0xFFF;
|
||||
let offset_in_page = segment.vaddr & 0xFFF;
|
||||
let total_size = offset_in_page + segment.mem_size;
|
||||
let seg_pages = (total_size + 4095) / 4096;
|
||||
|
||||
for p in 0..seg_pages {
|
||||
let page = mmu::allocator::alloc_zeroed_page();
|
||||
let page_va = page_start + p * 4096;
|
||||
let seg_file_start = if p == 0 { 0 } else { p * 4096 - offset_in_page };
|
||||
let dest_off = if p == 0 { offset_in_page } else { 0 };
|
||||
let copy_len = if seg_file_start < segment.file_size {
|
||||
(segment.file_size - seg_file_start).min(4096 - dest_off)
|
||||
} else { 0 };
|
||||
|
||||
if copy_len > 0 {
|
||||
let src = segment.file_offset + seg_file_start;
|
||||
if src + copy_len <= file_size {
|
||||
let len = (PAGE_SIZE - offset).min(data.len() - done);
|
||||
// SAFETY: `pa` is a pool frame the kernel just mapped for `pid`;
|
||||
// pool frames are RAM, mapped at `phys_to_virt`.
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
buf.as_ptr().add(src),
|
||||
(page as *mut u8).add(dest_off),
|
||||
copy_len,
|
||||
data.as_ptr().add(done),
|
||||
(mmu::phys_to_virt(pa.raw()) as *mut u8).add(offset),
|
||||
len,
|
||||
);
|
||||
}
|
||||
done += len;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
|
||||
match (flags.write, flags.execute) {
|
||||
(true, true) => None,
|
||||
(false, true) => Some(PagePerms::ReadExecute),
|
||||
(true, false) => Some(PagePerms::ReadWrite),
|
||||
(false, false) => Some(PagePerms::ReadOnly),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// malformed or overlapping image: boot servers are trusted to be sane.
|
||||
fn load_elf(uart: &Uart, pid: ProcessId, file: &[u8]) -> usize {
|
||||
if file.is_empty() {
|
||||
crate::halt(uart, "program missing from the kernel image (make build)");
|
||||
}
|
||||
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
|
||||
|
||||
for segment in elf_info.segments() {
|
||||
let perms =
|
||||
segment_perms(segment.flags).unwrap_or_else(|| crate::halt(uart, "W+X segment"));
|
||||
let page_start = segment.vaddr & !(PAGE_SIZE - 1);
|
||||
let offset_in_page = segment.vaddr - page_start;
|
||||
let pages = offset_in_page
|
||||
.checked_add(segment.mem_size)
|
||||
.map(|total| total.div_ceil(PAGE_SIZE))
|
||||
.unwrap_or_else(|| crate::halt(uart, "segment too large"));
|
||||
if pages == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
kern()
|
||||
.map_fresh_range(pid, VirtualAddress::from_raw(page_start), pages, perms)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "cannot map segment"));
|
||||
|
||||
let copy_len = segment.file_size.min(segment.mem_size);
|
||||
let data = segment
|
||||
.file_offset
|
||||
.checked_add(copy_len)
|
||||
.and_then(|end| file.get(segment.file_offset..end))
|
||||
.unwrap_or_else(|| crate::halt(uart, "segment outside file"));
|
||||
if !copy_to_user(pid, segment.vaddr, data) {
|
||||
crate::halt(uart, "segment copy failed");
|
||||
}
|
||||
}
|
||||
|
||||
let flags = if segment.flags.execute {
|
||||
mmu::Sv39Flags::user_rx()
|
||||
} else if segment.flags.write {
|
||||
mmu::Sv39Flags::user_rw()
|
||||
} else {
|
||||
mmu::Sv39Flags::user_rx()
|
||||
};
|
||||
mmu::map(user_pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(page_va),
|
||||
pa: types::address::PhysicalAddress::from_raw(page),
|
||||
pages: 1,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
let stack_bottom = USER_VA_BASE - USER_STACK_PAGES * PAGE_SIZE;
|
||||
kern()
|
||||
.map_fresh_range(
|
||||
pid,
|
||||
VirtualAddress::from_raw(stack_bottom),
|
||||
USER_STACK_PAGES,
|
||||
PagePerms::ReadWrite,
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "cannot map stack"));
|
||||
|
||||
for i in 0..16usize {
|
||||
let sp = mmu::allocator::alloc_zeroed_page();
|
||||
mmu::map(user_pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(USER_VA_BASE - (16 - i) * 4096),
|
||||
pa: types::address::PhysicalAddress::from_raw(sp),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
}
|
||||
|
||||
kern().platform_mut().set_page_table(pid, user_pt as *const mmu::PageTable as usize);
|
||||
elf_info.entry_point
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
use crate::hw::virtio_blk::VirtioBlk;
|
||||
|
||||
const SECTOR_SIZE: usize = 512;
|
||||
const MAX_FILE_SIZE: usize = 64 * 1024; // 64K max for init binary
|
||||
|
||||
struct BootFat16 {
|
||||
reserved_sectors: u16,
|
||||
sectors_per_fat: 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 + 511) / 512;
|
||||
let data_start = root_dir_start + root_dir_sectors;
|
||||
|
||||
Some(Self {
|
||||
reserved_sectors,
|
||||
sectors_per_fat,
|
||||
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];
|
||||
|
||||
// Read boot sector
|
||||
if !blk.read_sector(0, &mut sector_buf) { return None; }
|
||||
let fs = BootFat16::parse(§or_buf)?;
|
||||
|
||||
let fat_name = to_fat_name(filename);
|
||||
|
||||
// Search root directory
|
||||
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) as u32, &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 && cluster >= 2 && cluster < 0xFFF8 {
|
||||
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(§or_buf[..chunk]);
|
||||
read += chunk;
|
||||
}
|
||||
|
||||
// Read next cluster from FAT
|
||||
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)
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
use crate::hw::uart::Uart;
|
||||
use crate::hw::virtio_gpu::VirtioGpu;
|
||||
use crate::mmu;
|
||||
use types::display::Dimension;
|
||||
use types::id::ProcessId;
|
||||
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
const GPU_VA: usize = 0x20000;
|
||||
const VQUEUE_VA: usize = 0x21000;
|
||||
const CMDBUF_VA: usize = 0x23000;
|
||||
const PHYSINFO_VA: usize = 0x24000;
|
||||
const FB_VA: usize = 0x100000;
|
||||
const DEFAULT_WIDTH: Dimension = Dimension::new(640);
|
||||
const DEFAULT_HEIGHT: Dimension = Dimension::new(480);
|
||||
|
||||
pub fn configure(uart: &Uart, display_pid: ProcessId) {
|
||||
let gpu = match VirtioGpu::probe() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
uart.write_str("k-os: no GPU (headless)\n");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let pt_addr = kern().platform_mut().page_table_for(display_pid);
|
||||
let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) };
|
||||
|
||||
map_page(pt, GPU_VA, gpu.base());
|
||||
|
||||
let vq_phys = alloc_contiguous(2);
|
||||
map_range(pt, VQUEUE_VA, vq_phys, 2);
|
||||
|
||||
let cmd_phys = mmu::allocator::alloc_zeroed_page();
|
||||
map_page(pt, CMDBUF_VA, cmd_phys);
|
||||
|
||||
gpu.init_device(vq_phys);
|
||||
|
||||
let queue = hw_virtio::queue::VirtioQueue::new(
|
||||
hw_virtio::queue::QueueAddrs::from_base(vq_phys),
|
||||
);
|
||||
let dma = hw_virtio::gpu::GpuDmaAddrs {
|
||||
cmd_pa: types::address::PhysicalAddress::from_raw(cmd_phys),
|
||||
cmd_va: types::address::VirtualAddress::from_raw(cmd_phys),
|
||||
resp_pa: types::address::PhysicalAddress::from_raw(cmd_phys + 512),
|
||||
};
|
||||
use hw_virtio::mmio::MmioAccess;
|
||||
|
||||
let (width, height) = match hw_virtio::gpu::get_display_info(gpu.mmio(), &queue, &dma) {
|
||||
Some((w, h)) => {
|
||||
uart.write_str("k-os: GPU display: ");
|
||||
uart.write_dec(w as usize);
|
||||
uart.put_char(b'x');
|
||||
uart.write_dec(h as usize);
|
||||
uart.put_char(b'\n');
|
||||
(Dimension::new(w as u16), Dimension::new(h as u16))
|
||||
}
|
||||
None => {
|
||||
uart.write_str("k-os: GPU display: using default 640x480\n");
|
||||
(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
||||
}
|
||||
};
|
||||
|
||||
let fb_pages = (width.raw() as usize * height.raw() as usize * 4 + 4095) / 4096;
|
||||
let fb_phys = alloc_contiguous(fb_pages);
|
||||
map_range(pt, FB_VA, fb_phys, fb_pages);
|
||||
|
||||
let info_phys = mmu::allocator::alloc_zeroed_page();
|
||||
map_page(pt, PHYSINFO_VA, info_phys);
|
||||
unsafe {
|
||||
let info = info_phys as *mut u64;
|
||||
core::ptr::write_volatile(info, cmd_phys as u64);
|
||||
core::ptr::write_volatile(info.add(1), (cmd_phys + 512) as u64);
|
||||
core::ptr::write_volatile(info.add(2), fb_phys as u64);
|
||||
core::ptr::write_volatile(info.add(3), width.raw() as u64);
|
||||
core::ptr::write_volatile(info.add(4), height.raw() as u64);
|
||||
}
|
||||
|
||||
if gpu.setup_framebuffer(cmd_phys, vq_phys, fb_phys, width, height) {
|
||||
uart.write_str("k-os: VirtIO GPU configured\n");
|
||||
} else {
|
||||
uart.write_str("k-os: VirtIO GPU setup failed (display will be headless)\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn map_page(pt: &mut mmu::PageTable, va: usize, pa: usize) {
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(va),
|
||||
pa: types::address::PhysicalAddress::from_raw(pa),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
}
|
||||
|
||||
fn map_range(pt: &mut mmu::PageTable, va_base: usize, pa_base: usize, count: usize) {
|
||||
for i in 0..count {
|
||||
map_page(pt, va_base + i * 4096, pa_base + i * 4096);
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_contiguous(pages: usize) -> usize {
|
||||
let first = mmu::allocator::alloc_zeroed_page();
|
||||
for _ in 1..pages {
|
||||
mmu::allocator::alloc_zeroed_page();
|
||||
}
|
||||
first
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
use crate::hw::uart::Uart;
|
||||
use crate::{board, mmu};
|
||||
use types::id::ProcessId;
|
||||
|
||||
const UART_VA: usize = 0x20000;
|
||||
const INPUT_MMIO_VA: usize = 0x25000;
|
||||
const INPUT_VQUEUE_VA: usize = 0x26000;
|
||||
const INPUT_EVBUF_VA: usize = 0x28000;
|
||||
const INPUT_PHYSINFO_VA: usize = 0x29000;
|
||||
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
pub fn configure(uart: &Uart, input_pid: ProcessId) {
|
||||
let pt_addr = kern().platform_mut().page_table_for(input_pid);
|
||||
let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) };
|
||||
|
||||
// Always map UART for fallback
|
||||
map_page(pt, UART_VA, board::UART_BASE);
|
||||
|
||||
// Try to find VirtIO input device
|
||||
if let Some(mmio) = hw_virtio::mmio::scan_for_device(hw_virtio::mmio::DEVICE_ID_INPUT) {
|
||||
let base = mmio.base();
|
||||
map_page(pt, INPUT_MMIO_VA, base);
|
||||
|
||||
let vq_phys = alloc_contiguous(2);
|
||||
map_range(pt, INPUT_VQUEUE_VA, vq_phys, 2);
|
||||
|
||||
let evbuf_phys = mmu::allocator::alloc_zeroed_page();
|
||||
map_page(pt, INPUT_EVBUF_VA, evbuf_phys);
|
||||
|
||||
let info_phys = mmu::allocator::alloc_zeroed_page();
|
||||
map_page(pt, INPUT_PHYSINFO_VA, info_phys);
|
||||
unsafe {
|
||||
core::ptr::write_volatile(info_phys as *mut u64, evbuf_phys as u64);
|
||||
core::ptr::write_volatile((info_phys as *mut u64).add(1), vq_phys as u64);
|
||||
}
|
||||
|
||||
hw_virtio::mmio::init_legacy_device(&mmio, vq_phys, 32);
|
||||
uart.write_str("k-os: VirtIO keyboard configured\n");
|
||||
} else {
|
||||
uart.write_str("k-os: UART mapped for input\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn map_page(pt: &mut mmu::PageTable, va: usize, pa: usize) {
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(va),
|
||||
pa: types::address::PhysicalAddress::from_raw(pa),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
}
|
||||
|
||||
fn map_range(pt: &mut mmu::PageTable, va_base: usize, pa_base: usize, count: usize) {
|
||||
for i in 0..count {
|
||||
map_page(pt, va_base + i * 4096, pa_base + i * 4096);
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_contiguous(pages: usize) -> usize {
|
||||
let first = mmu::allocator::alloc_zeroed_page();
|
||||
for _ in 1..pages {
|
||||
mmu::allocator::alloc_zeroed_page();
|
||||
}
|
||||
first
|
||||
}
|
||||
@@ -1,3 +1,80 @@
|
||||
pub mod gpu_setup;
|
||||
pub mod input_setup;
|
||||
pub mod storage_setup;
|
||||
// Boot-time userspace setup: init's BootInfo page.
|
||||
|
||||
use crate::hw::uart::Uart;
|
||||
use types::address::VirtualAddress;
|
||||
use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT};
|
||||
use types::id::ProcessId;
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
/// Tells init, in a read-only page at BOOT_INFO_VA, which of its slots hold
|
||||
/// each board device's capabilities.
|
||||
pub(crate) fn boot_info(
|
||||
uart: &Uart,
|
||||
init: ProcessId,
|
||||
board: &[kernel::BoardDevice],
|
||||
kinds: &[u64],
|
||||
placed: &[Option<kernel::BoardCaps>],
|
||||
boot_caps: kernel::BootCaps,
|
||||
) {
|
||||
let empty = BootDevice {
|
||||
kind: 0,
|
||||
phys_base: 0,
|
||||
pages: 0,
|
||||
irq: 0,
|
||||
region_slot: NO_SLOT,
|
||||
irq_slot: NO_SLOT,
|
||||
};
|
||||
let mut info = BootInfo {
|
||||
magic: MAGIC,
|
||||
device_count: 0,
|
||||
system_control_slot: boot_caps.system_control.value() as u64,
|
||||
console_slot: boot_caps.console.value() as u64,
|
||||
devices: [empty; MAX_BOOT_DEVICES],
|
||||
};
|
||||
for (((d, kind), caps), out) in board
|
||||
.iter()
|
||||
.zip(kinds)
|
||||
.zip(placed)
|
||||
.zip(info.devices.iter_mut())
|
||||
{
|
||||
let Some(caps) = caps else { continue };
|
||||
*out = BootDevice {
|
||||
kind: *kind,
|
||||
phys_base: d.base.raw() as u64,
|
||||
pages: d.pages.value() as u64,
|
||||
irq: d.irq.map_or(0, |i| i.raw() as u64),
|
||||
region_slot: caps.region.value() as u64,
|
||||
irq_slot: caps.irq.map_or(NO_SLOT, |s| s.value() as u64),
|
||||
};
|
||||
info.device_count += 1;
|
||||
}
|
||||
// SAFETY: BootInfo is repr(C) and made only of u64s.
|
||||
let words = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
&info as *const BootInfo as *const u64,
|
||||
core::mem::size_of::<BootInfo>() / 8,
|
||||
)
|
||||
};
|
||||
write_page(uart, init, BOOT_INFO_VA, words);
|
||||
}
|
||||
|
||||
/// Writes `words` into a fresh read-only page mapped at `va` in `pid`.
|
||||
fn write_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) {
|
||||
let va = VirtualAddress::from_raw(va);
|
||||
kern()
|
||||
.map_fresh_range(pid, va, 1, PagePerms::ReadOnly)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "boot info page failed"));
|
||||
let pa = kern()
|
||||
.user_page_frame(pid, va)
|
||||
.unwrap_or_else(|| crate::halt(uart, "boot info page vanished"));
|
||||
let page = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
|
||||
for (i, &w) in words.iter().take(crate::mmu::PAGE_SIZE / 8).enumerate() {
|
||||
// SAFETY: `pa` is a pool frame just mapped for `pid`, reachable at
|
||||
// `phys_to_virt`, and at most one page is written.
|
||||
unsafe { core::ptr::write_volatile(page.add(i), w) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
use crate::hw::uart::Uart;
|
||||
use crate::mmu;
|
||||
use types::id::ProcessId;
|
||||
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
const VIRTIO_VA: usize = 0x20000;
|
||||
const VQUEUE_VA: usize = 0x21000;
|
||||
const REQHDR_VA: usize = 0x23000;
|
||||
const PHYSINFO_VA: usize = 0x24000;
|
||||
const SHARED_BUF_VA: usize = 0x30000;
|
||||
const SHARED_BUF_PAGES: usize = 16;
|
||||
|
||||
pub fn configure(
|
||||
uart: &Uart,
|
||||
init_pid: ProcessId,
|
||||
storage_pid: ProcessId,
|
||||
virtio_base: usize,
|
||||
) {
|
||||
let storage_pt = get_pt(storage_pid);
|
||||
let init_pt = get_pt(init_pid);
|
||||
|
||||
map_virtio_mmio(storage_pt, virtio_base);
|
||||
let vqueue_phys = alloc_and_map_virtqueue(storage_pt);
|
||||
let reqhdr_phys = alloc_and_map_reqhdr(storage_pt);
|
||||
write_phys_info(storage_pt, reqhdr_phys);
|
||||
reinit_device(virtio_base, vqueue_phys);
|
||||
uart.write_str("k-os: VirtIO configured for storage\n");
|
||||
|
||||
map_shared_buffer(init_pt, storage_pt);
|
||||
uart.write_str("k-os: shared buffer mapped\n");
|
||||
}
|
||||
|
||||
fn get_pt(pid: ProcessId) -> &'static mut mmu::PageTable {
|
||||
let addr = kern().platform_mut().page_table_for(pid);
|
||||
unsafe { &mut *(addr as *mut mmu::PageTable) }
|
||||
}
|
||||
|
||||
fn map_virtio_mmio(pt: &mut mmu::PageTable, virtio_base: usize) {
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(VIRTIO_VA),
|
||||
pa: types::address::PhysicalAddress::from_raw(virtio_base),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
}
|
||||
|
||||
fn alloc_and_map_virtqueue(pt: &mut mmu::PageTable) -> usize {
|
||||
let phys = mmu::allocator::alloc_zeroed_page();
|
||||
mmu::allocator::alloc_zeroed_page();
|
||||
for i in 0..2usize {
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(VQUEUE_VA + i * 4096),
|
||||
pa: types::address::PhysicalAddress::from_raw(phys + i * 4096),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
}
|
||||
phys
|
||||
}
|
||||
|
||||
fn alloc_and_map_reqhdr(pt: &mut mmu::PageTable) -> usize {
|
||||
let phys = mmu::allocator::alloc_zeroed_page();
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(REQHDR_VA),
|
||||
pa: types::address::PhysicalAddress::from_raw(phys),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
phys
|
||||
}
|
||||
|
||||
fn write_phys_info(pt: &mut mmu::PageTable, reqhdr_phys: usize) {
|
||||
let phys = mmu::allocator::alloc_zeroed_page();
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(PHYSINFO_VA),
|
||||
pa: types::address::PhysicalAddress::from_raw(phys),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
unsafe {
|
||||
let info = phys as *mut u64;
|
||||
core::ptr::write_volatile(info, reqhdr_phys as u64);
|
||||
core::ptr::write_volatile(info.add(1), (reqhdr_phys + 16) as u64);
|
||||
core::ptr::write_volatile(info.add(2), (reqhdr_phys + 32) as u64);
|
||||
}
|
||||
}
|
||||
|
||||
fn reinit_device(base: usize, vqueue_phys: usize) {
|
||||
let mmio = hw_virtio::mmio::DirectMmio::new(base);
|
||||
hw_virtio::mmio::init_legacy_device(&mmio, vqueue_phys, hw_virtio::queue::QUEUE_SIZE as u32);
|
||||
}
|
||||
|
||||
fn map_shared_buffer(init_pt: &mut mmu::PageTable, storage_pt: &mut mmu::PageTable) {
|
||||
for i in 0..SHARED_BUF_PAGES {
|
||||
let page = mmu::allocator::alloc_zeroed_page();
|
||||
let va = SHARED_BUF_VA + i * 4096;
|
||||
let mapping = mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(va),
|
||||
pa: types::address::PhysicalAddress::from_raw(page),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
};
|
||||
mmu::map(init_pt, mapping);
|
||||
mmu::map(storage_pt, mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(va),
|
||||
pa: types::address::PhysicalAddress::from_raw(page),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
});
|
||||
}
|
||||
}
|
||||
73
crates/adapter-riscv/src/entry.rs
Normal file
73
crates/adapter-riscv/src/entry.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use core::arch::global_asm;
|
||||
|
||||
// Entered with the MMU off at the physical address; the kernel is linked high.
|
||||
// PC-relative code only: enable `boot_page_table` (RAM mapped low for the next
|
||||
// instruction and high, MMIO high), then jump high and take the hart's stack.
|
||||
global_asm!(
|
||||
r#"
|
||||
.section .text.entry
|
||||
.globl _start
|
||||
_start:
|
||||
lla t2, start_high_address
|
||||
j 1f
|
||||
.globl _secondary_start
|
||||
_secondary_start:
|
||||
lla t2, secondary_high_address
|
||||
1:
|
||||
li t0, 8 # smp::MAX_HARTS
|
||||
bgeu a0, t0, spin
|
||||
mv s0, a0
|
||||
lla t0, boot_page_table
|
||||
srli t0, t0, 12
|
||||
li t1, 8 << 60
|
||||
or t0, t0, t1
|
||||
csrw satp, t0
|
||||
sfence.vma
|
||||
ld t0, 0(t2)
|
||||
jr t0
|
||||
|
||||
# sp = _stacks_start + (hart + 1) * smp::STACK_SLOT
|
||||
start_high:
|
||||
call hart_stack
|
||||
mv a0, s0
|
||||
call kmain
|
||||
j spin
|
||||
secondary_high:
|
||||
call hart_stack
|
||||
mv a0, s0
|
||||
call kmain_secondary
|
||||
j spin
|
||||
hart_stack:
|
||||
lla sp, _stacks_start
|
||||
addi t0, s0, 1
|
||||
li t1, 0x81000
|
||||
mul t0, t0, t1
|
||||
add sp, sp, t0
|
||||
ret
|
||||
spin:
|
||||
wfi
|
||||
j spin
|
||||
|
||||
.pushsection .rodata
|
||||
.balign 8
|
||||
start_high_address:
|
||||
.quad start_high
|
||||
secondary_high_address:
|
||||
.quad secondary_high
|
||||
.popsection
|
||||
|
||||
# Sv39 root of 1 GiB leaves; flags V|R|W|A|D = 0xc7, plus X = 0xcf.
|
||||
.pushsection .data
|
||||
.balign 4096
|
||||
boot_page_table:
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0x200000cf # [2] 0x8000_0000 -> RAM, for the jump only
|
||||
.fill 253, 8, 0
|
||||
.quad 0x000000c7 # [256] PHYS_OFFSET + 0 -> MMIO
|
||||
.quad 0
|
||||
.quad 0x200000cf # [258] PHYS_OFFSET + 0x8000_0000 -> RAM
|
||||
.fill 253, 8, 0
|
||||
.popsection
|
||||
"#
|
||||
);
|
||||
@@ -1,5 +1,3 @@
|
||||
pub mod plic;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
pub mod virtio_blk;
|
||||
pub mod virtio_gpu;
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
use crate::board;
|
||||
|
||||
const PLIC_PRIORITY: usize = board::PLIC_BASE;
|
||||
const PLIC_S_ENABLE: usize = board::PLIC_BASE + 0x2080;
|
||||
const PLIC_S_THRESHOLD: usize = board::PLIC_BASE + 0x201000;
|
||||
const PLIC_S_CLAIM: usize = board::PLIC_BASE + 0x201004;
|
||||
const PLIC: usize = crate::mmu::phys_to_virt(board::PLIC_BASE);
|
||||
const PLIC_PRIORITY: usize = PLIC;
|
||||
|
||||
// External interrupts go to the boot hart only: its supervisor context is
|
||||
// 2 * hart + 1 on QEMU virt (even contexts are machine mode).
|
||||
fn context() -> usize {
|
||||
2 * crate::smp::boot_hart() + 1
|
||||
}
|
||||
|
||||
fn enable_base() -> usize {
|
||||
PLIC + 0x2000 + context() * 0x80
|
||||
}
|
||||
|
||||
fn threshold() -> usize {
|
||||
PLIC + 0x20_0000 + context() * 0x1000
|
||||
}
|
||||
|
||||
fn claim_reg() -> usize {
|
||||
threshold() + 4
|
||||
}
|
||||
|
||||
pub struct Plic;
|
||||
|
||||
impl Plic {
|
||||
pub fn init() {
|
||||
unsafe { core::ptr::write_volatile(PLIC_S_THRESHOLD as *mut u32, 0) };
|
||||
unsafe { core::ptr::write_volatile(threshold() as *mut u32, 0) };
|
||||
}
|
||||
|
||||
pub fn enable(irq: u32) {
|
||||
let word = (irq / 32) as usize;
|
||||
let bit = irq % 32;
|
||||
let addr = PLIC_S_ENABLE + word * 4;
|
||||
let addr = enable_base() + word * 4;
|
||||
unsafe {
|
||||
let current = core::ptr::read_volatile(addr as *const u32);
|
||||
core::ptr::write_volatile(addr as *mut u32, current | (1 << bit));
|
||||
@@ -27,7 +43,7 @@ impl Plic {
|
||||
pub fn disable(irq: u32) {
|
||||
let word = (irq / 32) as usize;
|
||||
let bit = irq % 32;
|
||||
let addr = PLIC_S_ENABLE + word * 4;
|
||||
let addr = enable_base() + word * 4;
|
||||
unsafe {
|
||||
let current = core::ptr::read_volatile(addr as *const u32);
|
||||
core::ptr::write_volatile(addr as *mut u32, current & !(1 << bit));
|
||||
@@ -35,10 +51,10 @@ impl Plic {
|
||||
}
|
||||
|
||||
pub fn claim() -> u32 {
|
||||
unsafe { core::ptr::read_volatile(PLIC_S_CLAIM as *const u32) }
|
||||
unsafe { core::ptr::read_volatile(claim_reg() as *const u32) }
|
||||
}
|
||||
|
||||
pub fn complete(irq: u32) {
|
||||
unsafe { core::ptr::write_volatile(PLIC_S_CLAIM as *mut u32, irq) };
|
||||
unsafe { core::ptr::write_volatile(claim_reg() as *mut u32, irq) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
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 {
|
||||
let time: u64;
|
||||
@@ -7,10 +13,13 @@ pub fn read_time() -> u64 {
|
||||
}
|
||||
|
||||
fn sbi_set_timer(stime: u64) {
|
||||
// SBI returns an error code in a0 and a value in a1; both are
|
||||
// clobbered even though we ignore them.
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
in("a0") stime,
|
||||
inlateout("a0") stime => _,
|
||||
lateout("a1") _,
|
||||
in("a6") 0usize,
|
||||
in("a7") 0x54494D45usize,
|
||||
);
|
||||
@@ -18,15 +27,27 @@ fn sbi_set_timer(stime: u64) {
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
let now = read_time();
|
||||
sbi_set_timer(now + board::TICK_INTERVAL);
|
||||
set_next(read_time() + board::TICK_INTERVAL);
|
||||
}
|
||||
|
||||
pub fn set_next(deadline: u64) {
|
||||
if let Some(next) = NEXT.get(hart_id()) {
|
||||
next.store(deadline, Ordering::Relaxed);
|
||||
}
|
||||
sbi_set_timer(deadline);
|
||||
}
|
||||
|
||||
pub fn handle_interrupt() {
|
||||
let now = read_time();
|
||||
sbi_set_timer(now + board::TICK_INTERVAL);
|
||||
/// Goes off at `deadline` too, if that is before the next tick.
|
||||
pub fn wake_at(deadline: u64) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ pub struct Uart {
|
||||
|
||||
impl Uart {
|
||||
pub fn new() -> Self {
|
||||
Self { base: board::UART_BASE }
|
||||
Self {
|
||||
base: crate::mmu::phys_to_virt(board::UART_BASE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_char(&self, byte: u8) {
|
||||
@@ -30,7 +32,11 @@ impl Uart {
|
||||
let mut i = 0;
|
||||
while value > 0 {
|
||||
let digit = (value & 0xF) as u8;
|
||||
buf[i] = if digit < 10 { b'0' + digit } else { b'a' + digit - 10 };
|
||||
buf[i] = if digit < 10 {
|
||||
b'0' + digit
|
||||
} else {
|
||||
b'a' + digit - 10
|
||||
};
|
||||
value >>= 4;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
use hw_virtio::mmio::{self, DirectMmio};
|
||||
use hw_virtio::queue::{QueueAddrs, VirtioQueue, QUEUE_SIZE};
|
||||
use hw_virtio::block::{self, DmaAddrs};
|
||||
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(mmio::DEVICE_ID_BLOCK)?;
|
||||
|
||||
let vq_phys = unsafe { core::ptr::addr_of!(VQUEUE) as usize };
|
||||
mmio::init_legacy_device(&mmio, vq_phys, QUEUE_SIZE as u32);
|
||||
|
||||
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_phys));
|
||||
|
||||
let dma = unsafe {
|
||||
DmaAddrs {
|
||||
header_pa: PhysicalAddress::from_raw(core::ptr::addr_of!(REQ_HEADER) as usize),
|
||||
header_va: VirtualAddress::from_raw(core::ptr::addr_of_mut!(REQ_HEADER) as usize),
|
||||
status_pa: PhysicalAddress::from_raw(core::ptr::addr_of!(REQ_STATUS) as usize),
|
||||
status_va: VirtualAddress::from_raw(core::ptr::addr_of_mut!(REQ_STATUS) as usize),
|
||||
data_pa: PhysicalAddress::from_raw(core::ptr::addr_of!(SECTOR_BUF) as usize),
|
||||
data_va: VirtualAddress::from_raw(core::ptr::addr_of_mut!(SECTOR_BUF) as usize),
|
||||
}
|
||||
};
|
||||
|
||||
Some(Self { mmio, queue, dma })
|
||||
}
|
||||
|
||||
pub fn base(&self) -> usize {
|
||||
self.mmio.base()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use hw_virtio::mmio::{self, DirectMmio};
|
||||
use hw_virtio::queue::{QueueAddrs, VirtioQueue, QUEUE_SIZE};
|
||||
use hw_virtio::gpu::{self, GpuDmaAddrs};
|
||||
use types::address::{PhysicalAddress, VirtualAddress};
|
||||
use types::display::Dimension;
|
||||
|
||||
pub struct VirtioGpu {
|
||||
mmio: DirectMmio,
|
||||
}
|
||||
|
||||
impl VirtioGpu {
|
||||
pub fn probe() -> Option<Self> {
|
||||
let mmio = mmio::scan_for_device(mmio::DEVICE_ID_GPU)?;
|
||||
Some(Self { mmio })
|
||||
}
|
||||
|
||||
pub fn base(&self) -> usize {
|
||||
self.mmio.base()
|
||||
}
|
||||
|
||||
pub fn mmio(&self) -> &DirectMmio {
|
||||
&self.mmio
|
||||
}
|
||||
|
||||
pub fn init_device(&self, vqueue_phys: usize) {
|
||||
mmio::init_legacy_device(&self.mmio, vqueue_phys, QUEUE_SIZE as u32);
|
||||
}
|
||||
|
||||
pub fn setup_framebuffer(&self, cmd_phys: usize, vq_phys: usize, fb_phys: usize, width: Dimension, height: Dimension) -> bool {
|
||||
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_phys));
|
||||
let dma = GpuDmaAddrs {
|
||||
cmd_pa: PhysicalAddress::from_raw(cmd_phys),
|
||||
cmd_va: VirtualAddress::from_raw(cmd_phys),
|
||||
resp_pa: PhysicalAddress::from_raw(cmd_phys + 512),
|
||||
};
|
||||
let ok1 = gpu::create_2d(&self.mmio, &queue, &dma, 1, width, height);
|
||||
let ok2 = gpu::attach_backing(&self.mmio, &queue, &dma, 1, PhysicalAddress::from_raw(fb_phys), width.raw() as u32 * height.raw() as u32 * 4);
|
||||
let ok3 = gpu::set_scanout(&self.mmio, &queue, &dma, 0, 1, width, height);
|
||||
ok1 && ok2 && ok3
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,24 @@
|
||||
use core::cell::UnsafeCell;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub struct KernelCell<T> {
|
||||
initialized: AtomicBool,
|
||||
inner: UnsafeCell<MaybeUninit<T>>,
|
||||
}
|
||||
|
||||
// Single-core, interrupts disabled during kernel access.
|
||||
// Access is serialized by the kernel lock.
|
||||
unsafe impl<T> Sync for 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 {
|
||||
initialized: AtomicBool::new(false),
|
||||
inner: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
inner: UnsafeCell::new(MaybeUninit::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self, value: T) {
|
||||
unsafe { (*self.inner.get()).write(value) };
|
||||
self.initialized.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Returns a raw pointer to the kernel. Callers must ensure
|
||||
/// no aliasing `&mut` references exist. Safe on single-core
|
||||
/// with interrupts disabled during kernel calls.
|
||||
/// Raw pointer to the kernel; callers must avoid aliasing `&mut`.
|
||||
pub fn as_ptr(&self) -> *mut T {
|
||||
debug_assert!(self.initialized.load(Ordering::Acquire));
|
||||
unsafe { (*self.inner.get()).as_mut_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,69 +3,101 @@
|
||||
|
||||
mod board;
|
||||
mod boot;
|
||||
mod boot_fs;
|
||||
mod devices;
|
||||
mod entry;
|
||||
mod hw;
|
||||
mod kernel_cell;
|
||||
mod mmu;
|
||||
mod platform;
|
||||
mod smp;
|
||||
mod trap;
|
||||
|
||||
use core::arch::global_asm;
|
||||
use hw::uart::Uart;
|
||||
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> {
|
||||
// SAFETY: boot runs with the kernel lock held and before any other
|
||||
// hart has started.
|
||||
unsafe { &mut *kernel_ptr() }
|
||||
}
|
||||
|
||||
pub fn kernel_ptr() -> *mut kernel::Kernel<platform::RiscVPlatform> {
|
||||
KERNEL.as_ptr()
|
||||
}
|
||||
|
||||
global_asm!(
|
||||
r#"
|
||||
.section .text.entry
|
||||
.globl _start
|
||||
_start:
|
||||
la sp, _stack_top
|
||||
call kmain
|
||||
spin:
|
||||
wfi
|
||||
j spin
|
||||
"#
|
||||
);
|
||||
|
||||
const SHARED_BUF_VA: usize = 0x30000;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn kmain() -> ! {
|
||||
extern "C" fn kmain(hart: usize) -> ! {
|
||||
let uart = Uart::new();
|
||||
uart.write_str("k-os: booted on RISC-V\n");
|
||||
|
||||
let kernel_pt = boot::hardware::init(&uart);
|
||||
let mut blk = boot::hardware::probe_block_device(&uart)
|
||||
.unwrap_or_else(|| halt(&uart, "no block device"));
|
||||
boot::hardware::init(&uart, hart);
|
||||
// Not before init: zeroing .bss resets the lock.
|
||||
smp::lock();
|
||||
// Both come from the kernel image; init starts everything else from disk.
|
||||
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, kernel_pt);
|
||||
let storage = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"storage.elf", "storage");
|
||||
let display = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"display.elf", "display");
|
||||
let input = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"input.elf", "input");
|
||||
let virtio_base = blk.base();
|
||||
drop(blk);
|
||||
|
||||
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base);
|
||||
devices::gpu_setup::configure(&uart, display.pid);
|
||||
devices::input_setup::configure(&uart, input.pid);
|
||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
||||
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||
kern_mut()
|
||||
.register_board(init.pid, &board)
|
||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||
boot::caps::grant_all(&uart, init.pid, storage.pid);
|
||||
// After grant_all: init already relies on those slots.
|
||||
let mut placed = [None; board::BOARD_DEVICES];
|
||||
let boot_caps = kern_mut()
|
||||
.grant_boot_authority(init.pid, &board, &mut placed)
|
||||
.unwrap_or_else(|_| halt(&uart, "boot authority failed"));
|
||||
devices::boot_info(&uart, init.pid, &board, &board::kinds(), &placed, boot_caps);
|
||||
|
||||
start_other_harts(&uart, hart);
|
||||
uart.write_str("k-os: entering userspace\n");
|
||||
boot::loader::enter_userspace(init.pid, init.entry, SHARED_BUF_VA);
|
||||
trap::enter_first();
|
||||
}
|
||||
|
||||
fn start_other_harts(uart: &Uart, boot: usize) {
|
||||
unsafe extern "C" {
|
||||
fn _secondary_start();
|
||||
}
|
||||
// Harts start with the MMU off, so at the entry's physical address.
|
||||
let entry = mmu::virt_to_phys(_secondary_start as *const () as usize);
|
||||
for hart in (0..smp::num_harts()).filter(|h| *h != boot) {
|
||||
if !smp::sbi_hart_start(hart, entry) {
|
||||
halt(uart, "could not start a hart");
|
||||
}
|
||||
}
|
||||
uart.write_str("k-os: ");
|
||||
uart.write_dec(smp::num_harts());
|
||||
uart.write_str(" harts\n");
|
||||
}
|
||||
|
||||
/// Where every hart but the boot hart starts, on its own stack.
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn kmain_secondary(_hart: usize) -> ! {
|
||||
trap::init();
|
||||
hw::timer::init();
|
||||
mmu::activate(mmu::page_table::table_at(smp::kernel_root()));
|
||||
smp::lock();
|
||||
trap::enter_first();
|
||||
}
|
||||
|
||||
pub fn halt(uart: &Uart, msg: &str) -> ! {
|
||||
uart.write_str("k-os: FATAL — ");
|
||||
uart.write_str(msg);
|
||||
uart.put_char(b'\n');
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
loop {
|
||||
unsafe { core::arch::asm!("wfi") };
|
||||
}
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
@@ -78,5 +110,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
uart.write_dec(location.line() as usize);
|
||||
}
|
||||
uart.put_char(b'\n');
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
loop {
|
||||
unsafe { core::arch::asm!("wfi") };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
use super::page_table::{PageTable, PAGE_SIZE};
|
||||
use super::page_table::{PAGE_SIZE, PageTable};
|
||||
use crate::board;
|
||||
|
||||
// Boot-time bump allocator for page tables.
|
||||
// Only used during init before the scheduler starts.
|
||||
// Boot bump allocator for kernel-only memory; must never reach the user pool.
|
||||
static mut NEXT_FREE: usize = 0;
|
||||
|
||||
pub fn init_allocator(start: usize) {
|
||||
unsafe { NEXT_FREE = start; }
|
||||
unsafe {
|
||||
NEXT_FREE = start;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc_zeroed_page() -> usize {
|
||||
unsafe {
|
||||
let addr = NEXT_FREE;
|
||||
if addr + PAGE_SIZE > super::phys_to_virt(board::USER_POOL_BASE) {
|
||||
crate::halt(&crate::hw::uart::Uart::new(), "boot allocator exhausted");
|
||||
}
|
||||
NEXT_FREE += PAGE_SIZE;
|
||||
core::ptr::write_bytes(addr as *mut u8, 0, PAGE_SIZE);
|
||||
addr
|
||||
@@ -18,9 +23,5 @@ pub fn alloc_zeroed_page() -> usize {
|
||||
}
|
||||
|
||||
pub fn alloc_page_table() -> &'static mut PageTable {
|
||||
unsafe {
|
||||
let addr = alloc_zeroed_page();
|
||||
let table = &mut *(addr as *mut PageTable);
|
||||
table
|
||||
}
|
||||
unsafe { &mut *(alloc_zeroed_page() as *mut PageTable) }
|
||||
}
|
||||
|
||||
@@ -2,4 +2,21 @@ pub mod allocator;
|
||||
pub mod page_table;
|
||||
|
||||
pub use allocator::init_allocator;
|
||||
pub use page_table::{activate, init_kernel_page_table, create_user_page_table, map, translate, Sv39Flags, Mapping, PageTable, PAGE_SIZE};
|
||||
pub use page_table::{
|
||||
PAGE_SIZE, PageTable, Sv39Flags, USER_VA_END, USER_VA_START, activate, active_root, check_wx,
|
||||
create_user_root, init_kernel_page_table, map_user_page, translate_user, unmap_user_page,
|
||||
};
|
||||
|
||||
/// Direct-map offset: RAM, MMIO and the kernel image sit at physical + this
|
||||
/// (start of Sv39's upper half). Must match linker.ld.
|
||||
pub const PHYS_OFFSET: usize = 0xFFFF_FFC0_0000_0000;
|
||||
|
||||
/// Where the kernel sees physical address `pa`.
|
||||
pub const fn phys_to_virt(pa: usize) -> usize {
|
||||
pa + PHYS_OFFSET
|
||||
}
|
||||
|
||||
/// The physical address behind a direct-map kernel address `va`.
|
||||
pub const fn virt_to_phys(va: usize) -> usize {
|
||||
va - PHYS_OFFSET
|
||||
}
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
use crate::board;
|
||||
use super::allocator::alloc_page_table;
|
||||
use types::address::{PhysicalAddress, VirtualAddress};
|
||||
|
||||
pub use types::mapping::Mapping;
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
const PTE_V: u64 = 1 << 0;
|
||||
const PTE_R: u64 = 1 << 1;
|
||||
const PTE_W: u64 = 1 << 2;
|
||||
const PTE_X: u64 = 1 << 3;
|
||||
const PTE_U: u64 = 1 << 4;
|
||||
const PTE_G: u64 = 1 << 5;
|
||||
const PTE_A: u64 = 1 << 6;
|
||||
const PTE_D: u64 = 1 << 7;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Sv39Flags {
|
||||
bits: u64,
|
||||
}
|
||||
|
||||
impl Sv39Flags {
|
||||
pub fn kernel_rwx() -> Self {
|
||||
Self { bits: PTE_R | PTE_W | PTE_X | PTE_G }
|
||||
}
|
||||
|
||||
pub fn kernel_rw() -> Self {
|
||||
Self { bits: PTE_R | PTE_W | PTE_G }
|
||||
}
|
||||
|
||||
pub fn user_rx() -> Self {
|
||||
Self { bits: PTE_R | PTE_X | PTE_U }
|
||||
}
|
||||
|
||||
pub fn user_rw() -> Self {
|
||||
Self { bits: PTE_R | PTE_W | PTE_U }
|
||||
}
|
||||
|
||||
pub fn user(read: bool, write: bool, exec: bool) -> Self {
|
||||
let mut bits = PTE_U;
|
||||
if read { bits |= PTE_R; }
|
||||
if write { bits |= PTE_W; }
|
||||
if exec { bits |= PTE_X; }
|
||||
Self { bits }
|
||||
}
|
||||
|
||||
fn to_pte_flags(self) -> u64 {
|
||||
PTE_V | PTE_A | PTE_D | self.bits
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct PageTableEntry(u64);
|
||||
|
||||
impl PageTableEntry {
|
||||
pub fn empty() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
fn new_leaf(ppn: u64, flags: Sv39Flags) -> Self {
|
||||
Self(flags.to_pte_flags() | (ppn << 10))
|
||||
}
|
||||
|
||||
fn new_branch(ppn: u64) -> Self {
|
||||
Self(PTE_V | (ppn << 10))
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.0 & PTE_V != 0
|
||||
}
|
||||
|
||||
pub fn ppn(&self) -> u64 {
|
||||
(self.0 >> 10) & 0xFFF_FFFF_FFFF
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, align(4096))]
|
||||
pub struct PageTable {
|
||||
pub entries: [PageTableEntry; 512],
|
||||
}
|
||||
|
||||
impl PageTable {
|
||||
pub fn zero(&mut self) {
|
||||
for entry in &mut self.entries {
|
||||
*entry = PageTableEntry::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vpn(va: usize, level: usize) -> usize {
|
||||
(va >> (12 + 9 * level)) & 0x1FF
|
||||
}
|
||||
|
||||
pub fn translate(root: &PageTable, va: usize) -> Option<usize> {
|
||||
let vpn2 = vpn(va, 2);
|
||||
if !root.entries[vpn2].is_valid() { return None; }
|
||||
let l1 = unsafe { &*((root.entries[vpn2].ppn() << 12) as *const PageTable) };
|
||||
|
||||
let vpn1 = vpn(va, 1);
|
||||
if !l1.entries[vpn1].is_valid() { return None; }
|
||||
let l0 = unsafe { &*((l1.entries[vpn1].ppn() << 12) as *const PageTable) };
|
||||
|
||||
let vpn0 = vpn(va, 0);
|
||||
if !l0.entries[vpn0].is_valid() { return None; }
|
||||
let pa = (l0.entries[vpn0].ppn() << 12) as usize | (va & 0xFFF);
|
||||
Some(pa)
|
||||
}
|
||||
|
||||
fn map_page(root: &mut PageTable, va: usize, pa: usize, flags: Sv39Flags) {
|
||||
let vpn2 = vpn(va, 2);
|
||||
let vpn1 = vpn(va, 1);
|
||||
let vpn0 = vpn(va, 0);
|
||||
|
||||
if !root.entries[vpn2].is_valid() {
|
||||
let table = alloc_page_table();
|
||||
let ppn = (table as *const PageTable as u64) >> 12;
|
||||
root.entries[vpn2] = PageTableEntry::new_branch(ppn);
|
||||
}
|
||||
|
||||
let l1_addr = (root.entries[vpn2].ppn() << 12) as *mut PageTable;
|
||||
let l1 = unsafe { &mut *l1_addr };
|
||||
|
||||
if !l1.entries[vpn1].is_valid() {
|
||||
let table = alloc_page_table();
|
||||
let ppn = (table as *const PageTable as u64) >> 12;
|
||||
l1.entries[vpn1] = PageTableEntry::new_branch(ppn);
|
||||
}
|
||||
|
||||
let l0_addr = (l1.entries[vpn1].ppn() << 12) as *mut PageTable;
|
||||
let l0 = unsafe { &mut *l0_addr };
|
||||
|
||||
let ppn = (pa as u64) >> 12;
|
||||
l0.entries[vpn0] = PageTableEntry::new_leaf(ppn, flags);
|
||||
}
|
||||
|
||||
pub fn map(root: &mut PageTable, mapping: Mapping<Sv39Flags>) {
|
||||
for i in 0..mapping.pages {
|
||||
let offset = i * PAGE_SIZE;
|
||||
map_page(root, mapping.va.raw() + offset, mapping.pa.raw() + offset, mapping.flags);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_kernel_page_table() -> &'static mut PageTable {
|
||||
let root = alloc_page_table();
|
||||
|
||||
let identity = |addr: usize| (VirtualAddress::from_raw(addr), PhysicalAddress::from_raw(addr));
|
||||
|
||||
let (va, pa) = identity(board::RAM_BASE);
|
||||
map(root, Mapping { va, pa, pages: board::RAM_SIZE / PAGE_SIZE, flags: Sv39Flags::kernel_rwx() });
|
||||
|
||||
let (va, pa) = identity(board::UART_BASE);
|
||||
map(root, Mapping { va, pa, pages: 1, flags: Sv39Flags::kernel_rw() });
|
||||
|
||||
let (va, pa) = identity(board::PLIC_BASE);
|
||||
map(root, Mapping { va, pa, pages: 0x400000 / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
||||
|
||||
let (va, pa) = identity(board::CLINT_BASE);
|
||||
map(root, Mapping { va, pa, pages: 0x10000 / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
||||
|
||||
let (va, pa) = identity(board::VIRTIO_BASE);
|
||||
map(root, Mapping { va, pa, pages: board::VIRTIO_SIZE / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
pub fn activate(root: &PageTable) {
|
||||
let ppn = (root as *const PageTable as u64) >> 12;
|
||||
let satp = (8u64 << 60) | ppn;
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"csrw satp, {0}",
|
||||
"sfence.vma",
|
||||
in(reg) satp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_user_page_table(
|
||||
kernel_root: &PageTable,
|
||||
_program_pa: PhysicalAddress,
|
||||
_program_pages: usize,
|
||||
_user_va: VirtualAddress,
|
||||
) -> &'static mut PageTable {
|
||||
let root = alloc_page_table();
|
||||
|
||||
for i in 0..512 {
|
||||
if !kernel_root.entries[i].is_valid() {
|
||||
continue;
|
||||
}
|
||||
if i == 0 {
|
||||
// User mappings land in VPN[2]=0 — each process needs its own L1
|
||||
let kernel_l1_addr = (kernel_root.entries[i].ppn() << 12) as *const PageTable;
|
||||
let kernel_l1 = unsafe { &*kernel_l1_addr };
|
||||
let new_l1 = alloc_page_table();
|
||||
for j in 0..512 {
|
||||
if kernel_l1.entries[j].is_valid() {
|
||||
new_l1.entries[j] = kernel_l1.entries[j];
|
||||
}
|
||||
}
|
||||
let ppn = (new_l1 as *const PageTable as u64) >> 12;
|
||||
root.entries[i] = PageTableEntry::new_branch(ppn);
|
||||
} else {
|
||||
root.entries[i] = kernel_root.entries[i];
|
||||
}
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
207
crates/adapter-riscv/src/mmu/page_table/kernel.rs
Normal file
207
crates/adapter-riscv/src/mmu/page_table/kernel.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use super::pte::{
|
||||
Mapping, PAGE_SIZE, PTE_W, PTE_X, PageTable, PageTableEntry, Sv39Flags, map, table_at,
|
||||
table_ppn,
|
||||
};
|
||||
use crate::board;
|
||||
use crate::mmu::allocator::alloc_page_table;
|
||||
use crate::mmu::{phys_to_virt, virt_to_phys};
|
||||
use crate::smp;
|
||||
use types::address::{PhysicalAddress, VirtualAddress};
|
||||
|
||||
pub fn init_kernel_page_table() -> &'static mut PageTable {
|
||||
let root = alloc_page_table();
|
||||
|
||||
// Physical `pa` at its place in the upper-half direct map.
|
||||
let direct = |pa: usize| {
|
||||
(
|
||||
VirtualAddress::from_raw(phys_to_virt(pa)),
|
||||
PhysicalAddress::from_raw(pa),
|
||||
)
|
||||
};
|
||||
|
||||
// The kernel image, then the rest of RAM, each with only the access it
|
||||
// needs: code is never writable, data never executable. RAM below the
|
||||
// image belongs to the firmware and stays unmapped, as does the guard
|
||||
// page under each hart's kernel stack. Nothing is mapped in the lower
|
||||
// half, which is userspace's alone.
|
||||
let layout = KernelLayout::from_linker();
|
||||
let ram_end = phys_to_virt(board::RAM_BASE + board::RAM_SIZE);
|
||||
let stacks = (0..smp::MAX_HARTS).map(|h| {
|
||||
let slot = layout.stacks_start + h * smp::STACK_SLOT;
|
||||
(
|
||||
slot + PAGE_SIZE,
|
||||
slot + smp::STACK_SLOT,
|
||||
Sv39Flags::kernel_rw(),
|
||||
)
|
||||
});
|
||||
for (start, end, flags) in [
|
||||
(layout.text_start, layout.text_end, Sv39Flags::kernel_rx()),
|
||||
(layout.text_end, layout.rodata_end, Sv39Flags::kernel_ro()),
|
||||
(
|
||||
layout.rodata_end,
|
||||
layout.stacks_start,
|
||||
Sv39Flags::kernel_rw(),
|
||||
),
|
||||
(layout.stacks_end, ram_end, Sv39Flags::kernel_rw()),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(stacks)
|
||||
{
|
||||
let (va, pa) = direct(virt_to_phys(start));
|
||||
map(
|
||||
root,
|
||||
Mapping {
|
||||
va,
|
||||
pa,
|
||||
pages: (end - start) / PAGE_SIZE,
|
||||
flags,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (base, size) in [
|
||||
(board::UART_BASE, PAGE_SIZE),
|
||||
(board::PLIC_BASE, 0x40_0000),
|
||||
(board::CLINT_BASE, 0x1_0000),
|
||||
(board::VIRTIO_BASE, board::VIRTIO_SIZE),
|
||||
] {
|
||||
let (va, pa) = direct(base);
|
||||
map(
|
||||
root,
|
||||
Mapping {
|
||||
va,
|
||||
pa,
|
||||
pages: size / PAGE_SIZE,
|
||||
flags: Sv39Flags::kernel_rw(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
/// Page-aligned boundaries of the kernel image, from linker.ld.
|
||||
pub struct KernelLayout {
|
||||
pub text_start: usize,
|
||||
pub text_end: usize,
|
||||
pub rodata_end: usize,
|
||||
pub stacks_start: usize,
|
||||
pub stacks_end: usize,
|
||||
}
|
||||
|
||||
impl KernelLayout {
|
||||
pub fn from_linker() -> Self {
|
||||
unsafe extern "C" {
|
||||
static _text_start: u8;
|
||||
static _text_end: u8;
|
||||
static _rodata_end: u8;
|
||||
static _stacks_start: u8;
|
||||
static _stacks_end: u8;
|
||||
}
|
||||
// SAFETY: only the symbols' addresses are taken, never their values.
|
||||
unsafe {
|
||||
Self {
|
||||
text_start: &_text_start as *const u8 as usize,
|
||||
text_end: &_text_end as *const u8 as usize,
|
||||
rodata_end: &_rodata_end as *const u8 as usize,
|
||||
stacks_start: &_stacks_start as *const u8 as usize,
|
||||
stacks_end: &_stacks_end as *const u8 as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What `check_wx` found wrong with a kernel page table, if anything.
|
||||
pub enum WxViolation {
|
||||
/// A page that is both writable and executable.
|
||||
WritableCode(usize),
|
||||
/// An executable page outside the kernel's text.
|
||||
ExecutableData(usize),
|
||||
/// A mapped page under the firmware, or the stack guard.
|
||||
MappedHole(usize),
|
||||
/// A kernel mapping in the lower half, which belongs to userspace.
|
||||
LowerHalf(usize),
|
||||
}
|
||||
|
||||
/// Walks every leaf of `root` and checks the kernel layout: nothing is
|
||||
/// both writable and executable, only kernel text is executable, the
|
||||
/// firmware and every stack guard are unmapped, and nothing at all is mapped
|
||||
/// in the lower half. Returns the number of executable pages.
|
||||
pub fn check_wx(root: &PageTable) -> Result<usize, WxViolation> {
|
||||
let layout = KernelLayout::from_linker();
|
||||
let text = layout.text_start..layout.text_end;
|
||||
let is_guard = |va: usize| {
|
||||
(layout.stacks_start..layout.stacks_end).contains(&va)
|
||||
&& (va - layout.stacks_start).is_multiple_of(smp::STACK_SLOT)
|
||||
};
|
||||
let firmware = phys_to_virt(board::RAM_BASE)..layout.text_start;
|
||||
let mut executable = 0;
|
||||
for (va, bits) in leaves(root) {
|
||||
if va < phys_to_virt(0) {
|
||||
return Err(WxViolation::LowerHalf(va));
|
||||
}
|
||||
if bits & PTE_W != 0 && bits & PTE_X != 0 {
|
||||
return Err(WxViolation::WritableCode(va));
|
||||
}
|
||||
if bits & PTE_X != 0 {
|
||||
if !text.contains(&va) {
|
||||
return Err(WxViolation::ExecutableData(va));
|
||||
}
|
||||
executable += 1;
|
||||
}
|
||||
if is_guard(va) || firmware.contains(&va) {
|
||||
return Err(WxViolation::MappedHole(va));
|
||||
}
|
||||
}
|
||||
Ok(executable)
|
||||
}
|
||||
|
||||
/// Every 4 KiB leaf in `root` as (virtual address, PTE flag bits).
|
||||
/// The kernel maps nothing larger.
|
||||
fn leaves(root: &PageTable) -> impl Iterator<Item = (usize, u64)> + '_ {
|
||||
let next = |e: &PageTableEntry| table_at((e.ppn() << 12) as usize) as &PageTable;
|
||||
root.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| e.is_valid())
|
||||
.flat_map(move |(i2, e2)| {
|
||||
next(e2)
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| e.is_valid())
|
||||
.flat_map(move |(i1, e1)| {
|
||||
next(e1)
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| e.is_valid())
|
||||
.map(move |(i0, e0)| {
|
||||
let va = (i2 << 30) | (i1 << 21) | (i0 << 12);
|
||||
// Sv39 addresses are sign extended from bit 38.
|
||||
let va = if i2 >= 256 { va | !((1 << 39) - 1) } else { va };
|
||||
(va, e0.0 & 0x3ff)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn activate(root: &PageTable) {
|
||||
smp::note_active_root(virt_to_phys(root as *const PageTable as usize));
|
||||
let satp = (8u64 << 60) | table_ppn(root);
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"csrw satp, {0}",
|
||||
"sfence.vma",
|
||||
in(reg) satp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Physical address of the root table the hart is translating with.
|
||||
pub fn active_root() -> usize {
|
||||
let satp: usize;
|
||||
// SAFETY: reading satp has no side effects.
|
||||
unsafe { core::arch::asm!("csrr {0}, satp", out(reg) satp) };
|
||||
(satp & ((1 << 44) - 1)) << 12
|
||||
}
|
||||
11
crates/adapter-riscv/src/mmu/page_table/mod.rs
Normal file
11
crates/adapter-riscv/src/mmu/page_table/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Sv39 page tables: entries and mapping, the kernel table, user address spaces.
|
||||
|
||||
mod kernel;
|
||||
mod pte;
|
||||
mod user;
|
||||
|
||||
pub use kernel::{WxViolation, activate, active_root, check_wx, init_kernel_page_table};
|
||||
pub use pte::{PAGE_SIZE, PageTable, Sv39Flags, table_at};
|
||||
pub use user::{
|
||||
USER_VA_END, USER_VA_START, create_user_root, map_user_page, translate_user, unmap_user_page,
|
||||
};
|
||||
139
crates/adapter-riscv/src/mmu/page_table/pte.rs
Normal file
139
crates/adapter-riscv/src/mmu/page_table/pte.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::mmu::allocator::alloc_page_table;
|
||||
use crate::mmu::{phys_to_virt, virt_to_phys};
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
pub use types::mapping::Mapping;
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
const PTE_V: u64 = 1 << 0;
|
||||
const PTE_R: u64 = 1 << 1;
|
||||
pub(super) const PTE_W: u64 = 1 << 2;
|
||||
pub(super) const PTE_X: u64 = 1 << 3;
|
||||
const PTE_U: u64 = 1 << 4;
|
||||
const PTE_G: u64 = 1 << 5;
|
||||
const PTE_A: u64 = 1 << 6;
|
||||
const PTE_D: u64 = 1 << 7;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Sv39Flags {
|
||||
bits: u64,
|
||||
}
|
||||
|
||||
impl Sv39Flags {
|
||||
pub fn kernel_rx() -> Self {
|
||||
Self {
|
||||
bits: PTE_R | PTE_X | PTE_G,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel_ro() -> Self {
|
||||
Self {
|
||||
bits: PTE_R | PTE_G,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel_rw() -> Self {
|
||||
Self {
|
||||
bits: PTE_R | PTE_W | PTE_G,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user(perms: PagePerms) -> Self {
|
||||
let bits = match perms {
|
||||
PagePerms::ReadOnly => PTE_R | PTE_U,
|
||||
PagePerms::ReadWrite => PTE_R | PTE_W | PTE_U,
|
||||
PagePerms::ReadExecute => PTE_R | PTE_X | PTE_U,
|
||||
};
|
||||
Self { bits }
|
||||
}
|
||||
|
||||
fn to_pte_flags(self) -> u64 {
|
||||
PTE_V | PTE_A | PTE_D | self.bits
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct PageTableEntry(pub(super) u64);
|
||||
|
||||
impl PageTableEntry {
|
||||
pub fn empty() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub(super) fn new_leaf(ppn: u64, flags: Sv39Flags) -> Self {
|
||||
Self(flags.to_pte_flags() | (ppn << 10))
|
||||
}
|
||||
|
||||
pub(super) fn new_branch(ppn: u64) -> Self {
|
||||
Self(PTE_V | (ppn << 10))
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.0 & PTE_V != 0
|
||||
}
|
||||
|
||||
pub fn ppn(&self) -> u64 {
|
||||
(self.0 >> 10) & 0xFFF_FFFF_FFFF
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, align(4096))]
|
||||
pub struct PageTable {
|
||||
pub entries: [PageTableEntry; 512],
|
||||
}
|
||||
|
||||
impl PageTable {
|
||||
pub fn zero(&mut self) {
|
||||
for entry in &mut self.entries {
|
||||
*entry = PageTableEntry::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn vpn(va: usize, level: usize) -> usize {
|
||||
(va >> (12 + 9 * level)) & 0x1FF
|
||||
}
|
||||
|
||||
fn map_page(root: &mut PageTable, va: usize, pa: usize, flags: Sv39Flags) {
|
||||
let vpn2 = vpn(va, 2);
|
||||
let vpn1 = vpn(va, 1);
|
||||
let vpn0 = vpn(va, 0);
|
||||
|
||||
if !root.entries[vpn2].is_valid() {
|
||||
root.entries[vpn2] = PageTableEntry::new_branch(table_ppn(alloc_page_table()));
|
||||
}
|
||||
let l1 = table_at((root.entries[vpn2].ppn() << 12) as usize);
|
||||
|
||||
if !l1.entries[vpn1].is_valid() {
|
||||
l1.entries[vpn1] = PageTableEntry::new_branch(table_ppn(alloc_page_table()));
|
||||
}
|
||||
let l0 = table_at((l1.entries[vpn1].ppn() << 12) as usize);
|
||||
|
||||
let ppn = (pa as u64) >> 12;
|
||||
l0.entries[vpn0] = PageTableEntry::new_leaf(ppn, flags);
|
||||
}
|
||||
|
||||
/// The physical page number of a page table the kernel holds a reference to.
|
||||
pub(super) fn table_ppn(table: &PageTable) -> u64 {
|
||||
(virt_to_phys(table as *const PageTable as usize) as u64) >> 12
|
||||
}
|
||||
|
||||
pub fn map(root: &mut PageTable, mapping: Mapping<Sv39Flags>) {
|
||||
for i in 0..mapping.pages {
|
||||
let offset = i * PAGE_SIZE;
|
||||
map_page(
|
||||
root,
|
||||
mapping.va.raw() + offset,
|
||||
mapping.pa.raw() + offset,
|
||||
mapping.flags,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The page table at physical address `pa`, through the direct map.
|
||||
pub fn table_at(pa: usize) -> &'static mut PageTable {
|
||||
// SAFETY: every page table lives in RAM, which the kernel maps at
|
||||
// `phys_to_virt`.
|
||||
unsafe { &mut *(phys_to_virt(pa) as *mut PageTable) }
|
||||
}
|
||||
94
crates/adapter-riscv/src/mmu/page_table/user.rs
Normal file
94
crates/adapter-riscv/src/mmu/page_table/user.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use super::pte::{PAGE_SIZE, PageTable, PageTableEntry, Sv39Flags, table_at, vpn};
|
||||
use platform::{FrameSource, MapError};
|
||||
|
||||
/// 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_END: usize = 0x4000_0000;
|
||||
|
||||
fn fresh_table(frames: &mut dyn FrameSource) -> Result<usize, MapError> {
|
||||
let pa = frames
|
||||
.alloc_table_frame()
|
||||
.ok_or(MapError::OutOfFrames)?
|
||||
.raw();
|
||||
table_at(pa).zero();
|
||||
Ok(pa)
|
||||
}
|
||||
|
||||
fn in_user_window(va: usize) -> bool {
|
||||
va.is_multiple_of(PAGE_SIZE) && (USER_VA_START..USER_VA_END).contains(&va)
|
||||
}
|
||||
|
||||
/// Builds a root table sharing the kernel's mappings, with a private L1 for
|
||||
/// the user window. Returns the root's physical address.
|
||||
pub fn create_user_root(
|
||||
kernel_root: &PageTable,
|
||||
frames: &mut dyn FrameSource,
|
||||
) -> Result<usize, MapError> {
|
||||
let root_pa = fresh_table(frames)?;
|
||||
let l1_pa = fresh_table(frames)?;
|
||||
let root = table_at(root_pa);
|
||||
|
||||
for (mine, kernels) in root.entries.iter_mut().zip(&kernel_root.entries).skip(256) {
|
||||
*mine = *kernels;
|
||||
}
|
||||
root.entries[0] = PageTableEntry::new_branch((l1_pa as u64) >> 12);
|
||||
Ok(root_pa)
|
||||
}
|
||||
|
||||
fn user_leaf_table(
|
||||
root_pa: usize,
|
||||
va: usize,
|
||||
frames: Option<&mut dyn FrameSource>,
|
||||
) -> Result<&'static mut PageTable, MapError> {
|
||||
let root = table_at(root_pa);
|
||||
let l1 = table_at((root.entries[0].ppn() << 12) as usize);
|
||||
let slot = &mut l1.entries[vpn(va, 1)];
|
||||
if !slot.is_valid() {
|
||||
let frames = frames.ok_or(MapError::NoAddressSpace)?;
|
||||
let l0_pa = fresh_table(frames)?;
|
||||
*slot = PageTableEntry::new_branch((l0_pa as u64) >> 12);
|
||||
}
|
||||
Ok(table_at((slot.ppn() << 12) as usize))
|
||||
}
|
||||
|
||||
pub fn map_user_page(
|
||||
root_pa: usize,
|
||||
va: usize,
|
||||
pa: usize,
|
||||
flags: Sv39Flags,
|
||||
frames: &mut dyn FrameSource,
|
||||
) -> Result<(), MapError> {
|
||||
if !in_user_window(va) || !pa.is_multiple_of(PAGE_SIZE) {
|
||||
return Err(MapError::InvalidAddress);
|
||||
}
|
||||
let l0 = user_leaf_table(root_pa, va, Some(frames))?;
|
||||
l0.entries[vpn(va, 0)] = PageTableEntry::new_leaf((pa as u64) >> 12, flags);
|
||||
flush_tlb(va);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The physical page `va` maps to in the address space at `root_pa`.
|
||||
pub fn translate_user(root_pa: usize, va: usize) -> Option<usize> {
|
||||
if !in_user_window(va) {
|
||||
return None;
|
||||
}
|
||||
let l0 = user_leaf_table(root_pa, va, None).ok()?;
|
||||
let entry = l0.entries[vpn(va, 0)];
|
||||
entry.is_valid().then(|| (entry.ppn() << 12) as usize)
|
||||
}
|
||||
|
||||
pub fn unmap_user_page(root_pa: usize, va: usize) {
|
||||
if !in_user_window(va) {
|
||||
return;
|
||||
}
|
||||
if let Ok(l0) = user_leaf_table(root_pa, va, None) {
|
||||
l0.entries[vpn(va, 0)] = PageTableEntry::empty();
|
||||
flush_tlb(va);
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_tlb(va: usize) {
|
||||
// SAFETY: sfence.vma only invalidates translation caches.
|
||||
unsafe { core::arch::asm!("sfence.vma {0}, zero", in(reg) va) };
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
use domain::memory::MemoryRegion;
|
||||
use domain::process::MAX_PROCESSES;
|
||||
use platform::Platform;
|
||||
use platform::{FrameSource, MapError, Platform};
|
||||
use types::{
|
||||
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||
bounded::PageCount,
|
||||
id::{CoreId, IrqId, ProcessId},
|
||||
permission::{Permission, PermissionSet},
|
||||
time::{Ticks, TimerDuration},
|
||||
mapping::PagePerms,
|
||||
time::Ticks,
|
||||
};
|
||||
|
||||
use crate::board;
|
||||
use crate::hw::{plic::Plic, timer};
|
||||
use crate::mmu;
|
||||
use crate::smp;
|
||||
use crate::trap;
|
||||
|
||||
pub struct RiscVPlatform {
|
||||
num_cores: u8,
|
||||
kernel_pt: usize,
|
||||
/// Root table of each process's address space, 0 if it has none.
|
||||
page_tables: [usize; MAX_PROCESSES],
|
||||
}
|
||||
|
||||
impl RiscVPlatform {
|
||||
pub fn new(kernel_pt_addr: usize) -> Self {
|
||||
pub const fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
|
||||
Self {
|
||||
num_cores: 1,
|
||||
num_cores,
|
||||
kernel_pt: kernel_pt_addr,
|
||||
page_tables: [0usize; MAX_PROCESSES],
|
||||
}
|
||||
@@ -33,37 +32,86 @@ impl RiscVPlatform {
|
||||
self.page_tables[pid.raw() as usize]
|
||||
}
|
||||
|
||||
pub fn set_page_table(&mut self, pid: ProcessId, addr: usize) {
|
||||
self.page_tables[pid.raw() as usize] = addr;
|
||||
fn root(&self, pid: ProcessId) -> Option<usize> {
|
||||
match self.page_tables.get(pid.raw() as usize) {
|
||||
Some(&0) | None => None,
|
||||
Some(&root) => Some(root),
|
||||
}
|
||||
|
||||
fn ensure_page_table(&mut self, pid: ProcessId) -> &mut mmu::PageTable {
|
||||
let idx = pid.raw() as usize;
|
||||
if self.page_tables[idx] == 0 {
|
||||
let kernel_pt = unsafe { &*(self.kernel_pt as *const mmu::PageTable) };
|
||||
let user_pt = mmu::create_user_page_table(
|
||||
kernel_pt,
|
||||
PhysicalAddress::from_raw(0),
|
||||
0,
|
||||
VirtualAddress::from_raw(0x10000),
|
||||
);
|
||||
self.page_tables[idx] = user_pt as *const mmu::PageTable as usize;
|
||||
}
|
||||
unsafe { &mut *(self.page_tables[idx] as *mut mmu::PageTable) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for RiscVPlatform {
|
||||
fn configure_protection(&mut self, process: ProcessId, regions: &[MemoryRegion]) {
|
||||
let pt = self.ensure_page_table(process);
|
||||
for region in regions {
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: VirtualAddress::from_raw(region.base().raw()),
|
||||
pa: region.base(),
|
||||
pages: region.size().value() as usize,
|
||||
flags: mmu::Sv39Flags::user(true, true, true),
|
||||
});
|
||||
const USER_VA_START: usize = mmu::USER_VA_START;
|
||||
const USER_VA_END: usize = mmu::USER_VA_END;
|
||||
|
||||
fn create_address_space(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
frames: &mut dyn FrameSource,
|
||||
) -> Result<(), MapError> {
|
||||
let slot = self
|
||||
.page_tables
|
||||
.get_mut(process.raw() as usize)
|
||||
.ok_or(MapError::NoAddressSpace)?;
|
||||
// SAFETY: the kernel page table lives in the boot heap forever.
|
||||
let kernel_root = unsafe { &*(self.kernel_pt as *const mmu::PageTable) };
|
||||
*slot = mmu::create_user_root(kernel_root, frames)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy_address_space(&mut self, process: ProcessId) {
|
||||
let Some(root) = self.root(process) else {
|
||||
return;
|
||||
};
|
||||
// No hart may translate through it once its frames are freed; that
|
||||
// includes this one, which may be running on it.
|
||||
smp::shoot_down(root);
|
||||
if mmu::active_root() == root {
|
||||
// SAFETY: the kernel page table lives in the boot heap forever.
|
||||
mmu::activate(unsafe { &*(self.kernel_pt as *const mmu::PageTable) });
|
||||
}
|
||||
self.page_tables[process.raw() as usize] = 0;
|
||||
// SAFETY: flushing translations has no other effect.
|
||||
unsafe { core::arch::asm!("sfence.vma zero, zero") };
|
||||
}
|
||||
|
||||
fn map_page(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
va: VirtualAddress,
|
||||
pa: PhysicalAddress,
|
||||
perms: PagePerms,
|
||||
frames: &mut dyn FrameSource,
|
||||
) -> Result<(), MapError> {
|
||||
let root = self.root(process).ok_or(MapError::NoAddressSpace)?;
|
||||
mmu::map_user_page(
|
||||
root,
|
||||
va.raw(),
|
||||
pa.raw(),
|
||||
mmu::Sv39Flags::user(perms),
|
||||
frames,
|
||||
)
|
||||
}
|
||||
|
||||
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress) {
|
||||
if let Some(root) = self.root(process) {
|
||||
mmu::unmap_user_page(root, va.raw());
|
||||
// Other harts running this process may still cache it.
|
||||
smp::shoot_down(root);
|
||||
}
|
||||
}
|
||||
|
||||
fn translate(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||
let root = self.root(process)?;
|
||||
mmu::translate_user(root, va.raw()).map(PhysicalAddress::from_raw)
|
||||
}
|
||||
|
||||
fn zero_frame(&mut self, pa: PhysicalAddress) {
|
||||
// SAFETY: pool frames are RAM, mapped at `phys_to_virt`, and the
|
||||
// kernel only zeroes frames it has just allocated.
|
||||
unsafe {
|
||||
core::ptr::write_bytes(mmu::phys_to_virt(pa.raw()) as *mut u8, 0, mmu::PAGE_SIZE)
|
||||
};
|
||||
}
|
||||
|
||||
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> ! {
|
||||
@@ -87,9 +135,8 @@ impl Platform for RiscVPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_timer(&mut self, _duration: TimerDuration) {
|
||||
let now = timer::read_time();
|
||||
timer::set_next(now + board::TICK_INTERVAL);
|
||||
fn wake_at(&mut self, deadline: Ticks) {
|
||||
timer::wake_at(deadline.raw());
|
||||
}
|
||||
|
||||
fn current_ticks(&self) -> Ticks {
|
||||
@@ -104,68 +151,34 @@ impl Platform for RiscVPlatform {
|
||||
Plic::enable(irq.raw() as u32);
|
||||
}
|
||||
|
||||
fn send_ipi(&mut self, _target: CoreId) {}
|
||||
fn send_ipi(&mut self, target: CoreId) {
|
||||
smp::kick(target.raw() as usize);
|
||||
}
|
||||
|
||||
fn core_id(&self) -> CoreId {
|
||||
CoreId::from_raw(0)
|
||||
CoreId::from_raw(smp::hart_id() as u8)
|
||||
}
|
||||
|
||||
fn num_cores(&self) -> u8 {
|
||||
self.num_cores
|
||||
}
|
||||
|
||||
fn map_shared(
|
||||
&mut self,
|
||||
process: ProcessId,
|
||||
base: PhysicalAddress,
|
||||
size: PageCount,
|
||||
permissions: PermissionSet,
|
||||
) {
|
||||
let pt = self.ensure_page_table(process);
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va: VirtualAddress::from_raw(base.raw()),
|
||||
pa: base,
|
||||
pages: size.value() as usize,
|
||||
flags: mmu::Sv39Flags::user(
|
||||
permissions.allows(Permission::Read),
|
||||
permissions.allows(Permission::Write),
|
||||
permissions.allows(Permission::Execute),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
fn debug_putchar(&self, byte: u8) {
|
||||
crate::hw::uart::Uart::new().put_char(byte);
|
||||
}
|
||||
|
||||
fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||
let pt_addr = self.page_tables[process.raw() as usize];
|
||||
if pt_addr == 0 { return None; }
|
||||
let pt = unsafe { &*(pt_addr as *const mmu::PageTable) };
|
||||
mmu::translate(pt, va.raw()).map(PhysicalAddress::from_raw)
|
||||
}
|
||||
|
||||
fn map_user_page(&mut self, process: ProcessId, va: VirtualAddress, pa: PhysicalAddress, writable: bool, executable: bool) {
|
||||
let pt = self.ensure_page_table(process);
|
||||
let flags = mmu::Sv39Flags::user(true, writable, executable);
|
||||
mmu::map(pt, mmu::Mapping {
|
||||
va,
|
||||
pa,
|
||||
pages: 1,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
|
||||
fn halt(&self) -> ! {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
in("a0") 0usize,
|
||||
in("a1") 0usize,
|
||||
inlateout("a0") 0usize => _,
|
||||
inlateout("a1") 0usize => _,
|
||||
in("a7") 0x53525354usize,
|
||||
in("a6") 0usize,
|
||||
);
|
||||
}
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
loop {
|
||||
unsafe { core::arch::asm!("wfi") };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
crates/adapter-riscv/src/smp/hints.rs
Normal file
42
crates/adapter-riscv/src/smp/hints.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
//! State published under the kernel lock for lock-free fast paths to read.
|
||||
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use super::{MAX_HARTS, hart_id};
|
||||
|
||||
/// Per core: highest waiting priority + 1, 0 if its queue is empty.
|
||||
static WAITING: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||
/// Per hart: the running thread's process id + 1 and priority + 1.
|
||||
static RUNNING_PID: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||
static RUNNING_PRIORITY: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||
|
||||
pub fn publish_waiting(core: usize, highest: Option<u8>) {
|
||||
if let Some(w) = WAITING.get(core) {
|
||||
w.store(highest.map_or(0, |p| p as usize + 1), Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn publish_running(pid: Option<u16>, priority: Option<u8>) {
|
||||
let me = hart_id();
|
||||
if let (Some(p), Some(q)) = (RUNNING_PID.get(me), RUNNING_PRIORITY.get(me)) {
|
||||
p.store(pid.map_or(0, |x| x as usize + 1), Ordering::Release);
|
||||
q.store(priority.map_or(0, |x| x as usize + 1), Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
/// The process the thread running on this hart belongs to.
|
||||
pub fn running_pid() -> Option<usize> {
|
||||
let v = RUNNING_PID.get(hart_id())?.load(Ordering::Acquire);
|
||||
v.checked_sub(1)
|
||||
}
|
||||
|
||||
/// Whether a Yield by the running thread would change nothing: no thread
|
||||
/// waits on this core at its priority or above.
|
||||
pub fn yield_is_noop() -> bool {
|
||||
let me = hart_id();
|
||||
let (Some(w), Some(p)) = (WAITING.get(me), RUNNING_PRIORITY.get(me)) else {
|
||||
return false;
|
||||
};
|
||||
let mine = p.load(Ordering::Acquire);
|
||||
mine != 0 && w.load(Ordering::Acquire) < mine
|
||||
}
|
||||
96
crates/adapter-riscv/src/smp/ipi.rs
Normal file
96
crates/adapter-riscv/src/smp/ipi.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
//! Reschedule and leave-root IPIs, serviced without the kernel lock.
|
||||
|
||||
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
|
||||
use super::sbi::sbi_send_ipi;
|
||||
use super::{MAX_HARTS, hart_id, kernel_root, num_harts};
|
||||
use crate::mmu;
|
||||
|
||||
const SIP_SSIP: usize = 1 << 1;
|
||||
|
||||
/// The root table each hart translates with.
|
||||
static ACTIVE_ROOT: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||
static RESCHEDULE: [AtomicBool; MAX_HARTS] = [const { AtomicBool::new(false) }; MAX_HARTS];
|
||||
static LEAVE_ROOT: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||
static LEFT: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||
|
||||
/// Called by `mmu::activate`.
|
||||
pub fn note_active_root(root: usize) {
|
||||
if let Some(r) = ACTIVE_ROOT.get(hart_id()) {
|
||||
r.store(root, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this hart has an IPI or a reschedule request it has not acted
|
||||
/// on; fast paths must then take the slow path.
|
||||
pub fn has_pending_request() -> bool {
|
||||
let sip: usize;
|
||||
// SAFETY: reading sip has no side effects.
|
||||
unsafe { core::arch::asm!("csrr {0}, sip", out(reg) sip) };
|
||||
sip & SIP_SSIP != 0
|
||||
|| RESCHEDULE
|
||||
.get(hart_id())
|
||||
.is_some_and(|r| r.load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
// ---- IPIs ----
|
||||
|
||||
/// The lock-free half of an IPI: acknowledges it and leaves the root being
|
||||
/// changed, if asked. Safe to call at any time.
|
||||
pub fn service_ipi() {
|
||||
let sip: usize;
|
||||
// SAFETY: reading sip has no side effects.
|
||||
unsafe { core::arch::asm!("csrr {0}, sip", out(reg) sip) };
|
||||
if sip & SIP_SSIP == 0 {
|
||||
return;
|
||||
}
|
||||
// Clear first: a request posted after this raises SSIP again.
|
||||
// SAFETY: clearing our own pending software interrupt.
|
||||
unsafe { core::arch::asm!("csrc sip, {0}", in(reg) SIP_SSIP) };
|
||||
let me = hart_id();
|
||||
let (Some(leave), Some(left)) = (LEAVE_ROOT.get(me), LEFT.get(me)) else {
|
||||
return;
|
||||
};
|
||||
let root = leave.swap(0, Ordering::SeqCst);
|
||||
if root != 0 {
|
||||
if ACTIVE_ROOT.get(me).map(|r| r.load(Ordering::SeqCst)) == Some(root) {
|
||||
mmu::activate(mmu::page_table::table_at(kernel_root()));
|
||||
}
|
||||
// SAFETY: flushing translations has no other effect.
|
||||
unsafe { core::arch::asm!("sfence.vma zero, zero") };
|
||||
left.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Asks `hart` to look at its run queue again, on its way out of the
|
||||
/// kernel.
|
||||
pub fn kick(hart: usize) {
|
||||
if let Some(r) = RESCHEDULE.get(hart) {
|
||||
r.store(true, Ordering::SeqCst);
|
||||
sbi_send_ipi(hart);
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes this hart's reschedule request.
|
||||
pub fn take_reschedule() -> bool {
|
||||
RESCHEDULE
|
||||
.get(hart_id())
|
||||
.is_some_and(|r| r.swap(false, Ordering::SeqCst))
|
||||
}
|
||||
|
||||
/// Makes every other hart leave `root` and flush, waiting for each.
|
||||
/// Kernel lock held, so no hart can start using `root` meanwhile.
|
||||
pub fn shoot_down(root: usize) {
|
||||
let me = hart_id();
|
||||
for hart in 0..num_harts() {
|
||||
if hart == me || ACTIVE_ROOT[hart].load(Ordering::SeqCst) != root {
|
||||
continue;
|
||||
}
|
||||
let seen = LEFT[hart].load(Ordering::SeqCst);
|
||||
LEAVE_ROOT[hart].store(root, Ordering::SeqCst);
|
||||
sbi_send_ipi(hart);
|
||||
while LEFT[hart].load(Ordering::SeqCst) == seen {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
crates/adapter-riscv/src/smp/lock.rs
Normal file
22
crates/adapter-riscv/src/smp/lock.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! The kernel lock: a ticket lock that services IPIs while waiting.
|
||||
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use super::service_ipi;
|
||||
|
||||
static NEXT_TICKET: AtomicU32 = AtomicU32::new(0);
|
||||
static SERVING: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
/// Takes the kernel lock, first come first served. Leave-root requests
|
||||
/// are serviced while waiting.
|
||||
pub fn lock() {
|
||||
let ticket = NEXT_TICKET.fetch_add(1, Ordering::Relaxed);
|
||||
while SERVING.load(Ordering::Acquire) != ticket {
|
||||
service_ipi();
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlock() {
|
||||
SERVING.fetch_add(1, Ordering::Release);
|
||||
}
|
||||
61
crates/adapter-riscv/src/smp/mod.rs
Normal file
61
crates/adapter-riscv/src/smp/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
//! Harts: per-hart kernel stacks (the hart id doubles as the kernel `CoreId`),
|
||||
//! the kernel lock, and IPIs. The kernel runs under one lock; userspace runs in parallel.
|
||||
|
||||
mod hints;
|
||||
mod ipi;
|
||||
mod lock;
|
||||
mod sbi;
|
||||
|
||||
pub use hints::{publish_running, publish_waiting, running_pid, yield_is_noop};
|
||||
pub use ipi::{
|
||||
has_pending_request, kick, note_active_root, service_ipi, shoot_down, take_reschedule,
|
||||
};
|
||||
pub use lock::{lock, unlock};
|
||||
pub use sbi::{probe_harts, sbi_hart_start};
|
||||
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
/// Must match linker.ld.
|
||||
pub const MAX_HARTS: usize = 8;
|
||||
/// A guard page and a 512 KiB stack. Must match linker.ld.
|
||||
pub const STACK_SLOT: usize = 4096 + 0x8_0000;
|
||||
|
||||
/// The hart running this code, from which kernel stack it is on.
|
||||
pub fn hart_id() -> usize {
|
||||
let sp: usize;
|
||||
// SAFETY: reading sp has no side effects.
|
||||
unsafe { core::arch::asm!("mv {0}, sp", out(reg) sp) };
|
||||
(sp - stacks_start()) / STACK_SLOT
|
||||
}
|
||||
|
||||
pub fn stacks_start() -> usize {
|
||||
unsafe extern "C" {
|
||||
static _stacks_start: u8;
|
||||
}
|
||||
// SAFETY: only the symbol's address is taken.
|
||||
unsafe { &_stacks_start as *const u8 as usize }
|
||||
}
|
||||
|
||||
static BOOT_HART: AtomicUsize = AtomicUsize::new(0);
|
||||
static NUM_HARTS: AtomicUsize = AtomicUsize::new(1);
|
||||
/// Physical address of the kernel's own root table.
|
||||
static KERNEL_ROOT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub fn boot_hart() -> usize {
|
||||
BOOT_HART.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn num_harts() -> usize {
|
||||
NUM_HARTS.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn kernel_root() -> usize {
|
||||
KERNEL_ROOT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Records the boot hart, the hart count and the kernel root table.
|
||||
pub fn init(boot: usize, harts: usize, kernel_root: usize) {
|
||||
BOOT_HART.store(boot, Ordering::Relaxed);
|
||||
NUM_HARTS.store(harts, Ordering::Relaxed);
|
||||
KERNEL_ROOT.store(kernel_root, Ordering::Relaxed);
|
||||
}
|
||||
60
crates/adapter-riscv/src/smp/sbi.rs
Normal file
60
crates/adapter-riscv/src/smp/sbi.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! SBI calls: IPIs and hart state management.
|
||||
|
||||
use super::MAX_HARTS;
|
||||
|
||||
const SBI_EXT_IPI: usize = 0x735049;
|
||||
const SBI_EXT_HSM: usize = 0x48534D;
|
||||
|
||||
pub(super) fn sbi_send_ipi(hart: usize) {
|
||||
// SAFETY: an SBI call; a0 and a1 come back clobbered.
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
inlateout("a0") 1usize => _,
|
||||
inlateout("a1") hart => _,
|
||||
in("a6") 0usize,
|
||||
in("a7") SBI_EXT_IPI,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `hart` exists, per SBI HSM `hart_get_status`.
|
||||
fn sbi_hart_exists(hart: usize) -> bool {
|
||||
let error: isize;
|
||||
// SAFETY: an SBI call; a0 and a1 come back clobbered.
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
inlateout("a0") hart => error,
|
||||
lateout("a1") _,
|
||||
in("a6") 2usize,
|
||||
in("a7") SBI_EXT_HSM,
|
||||
);
|
||||
}
|
||||
error == 0
|
||||
}
|
||||
|
||||
/// The number of harts, counting up from hart 0 until one is missing.
|
||||
pub fn probe_harts() -> usize {
|
||||
(0..MAX_HARTS)
|
||||
.take_while(|h| sbi_hart_exists(*h))
|
||||
.count()
|
||||
.max(1)
|
||||
}
|
||||
|
||||
/// Starts `hart` at the physical address `entry` with the MMU off.
|
||||
pub fn sbi_hart_start(hart: usize, entry: usize) -> bool {
|
||||
let error: isize;
|
||||
// SAFETY: an SBI call; a0 and a1 come back clobbered.
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
inlateout("a0") hart => error,
|
||||
inlateout("a1") entry => _,
|
||||
in("a2") 0usize,
|
||||
in("a6") 0usize,
|
||||
in("a7") SBI_EXT_HSM,
|
||||
);
|
||||
}
|
||||
error == 0
|
||||
}
|
||||
22
crates/adapter-riscv/src/trap/fast.rs
Normal file
22
crates/adapter-riscv/src/trap/fast.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use super::{TrapFrame, write_result};
|
||||
|
||||
/// Syscalls answered without the kernel lock because they touch nothing it
|
||||
/// guards. A pending IPI or reschedule request forces the slow path.
|
||||
pub(super) fn fast_syscall(frame: &mut TrapFrame) -> bool {
|
||||
use types::syscall_abi::{SYS_GET_PROCESS_ID, SYS_GET_TIME, SYS_YIELD};
|
||||
if crate::smp::has_pending_request() {
|
||||
return false;
|
||||
}
|
||||
let result = match frame.regs[17] {
|
||||
SYS_GET_TIME => kernel::SyscallResult::Value(crate::hw::timer::read_time() as usize),
|
||||
SYS_GET_PROCESS_ID => match crate::smp::running_pid() {
|
||||
Some(pid) => kernel::SyscallResult::Value(pid),
|
||||
None => return false,
|
||||
},
|
||||
SYS_YIELD if crate::smp::yield_is_noop() => kernel::SyscallResult::Ok,
|
||||
_ => return false,
|
||||
};
|
||||
frame.sepc += 4;
|
||||
write_result(frame, &result);
|
||||
true
|
||||
}
|
||||
165
crates/adapter-riscv/src/trap/fp.rs
Normal file
165
crates/adapter-riscv/src/trap/fp.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
use core::arch::global_asm;
|
||||
mod fast;
|
||||
mod fp;
|
||||
mod sched;
|
||||
mod vector;
|
||||
|
||||
use fast::fast_syscall;
|
||||
pub use sched::enter_first;
|
||||
use sched::{finish, release, switch_to_next, switch_to_thread};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct TrapFrame {
|
||||
@@ -7,135 +14,26 @@ pub struct TrapFrame {
|
||||
pub sstatus: usize,
|
||||
}
|
||||
|
||||
impl TrapFrame {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
regs: [0; 32],
|
||||
sepc: 0,
|
||||
sstatus: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global_asm!(r#"
|
||||
.align 4
|
||||
.globl trap_vector
|
||||
trap_vector:
|
||||
csrrw sp, sscratch, sp
|
||||
bnez sp, _from_user
|
||||
csrrw sp, sscratch, sp
|
||||
_from_user:
|
||||
addi sp, sp, -272
|
||||
|
||||
sd x1, 8(sp)
|
||||
sd x3, 24(sp)
|
||||
sd x4, 32(sp)
|
||||
sd x5, 40(sp)
|
||||
sd x6, 48(sp)
|
||||
sd x7, 56(sp)
|
||||
sd x8, 64(sp)
|
||||
sd x9, 72(sp)
|
||||
sd x10, 80(sp)
|
||||
sd x11, 88(sp)
|
||||
sd x12, 96(sp)
|
||||
sd x13, 104(sp)
|
||||
sd x14, 112(sp)
|
||||
sd x15, 120(sp)
|
||||
sd x16, 128(sp)
|
||||
sd x17, 136(sp)
|
||||
sd x18, 144(sp)
|
||||
sd x19, 152(sp)
|
||||
sd x20, 160(sp)
|
||||
sd x21, 168(sp)
|
||||
sd x22, 176(sp)
|
||||
sd x23, 184(sp)
|
||||
sd x24, 192(sp)
|
||||
sd x25, 200(sp)
|
||||
sd x26, 208(sp)
|
||||
sd x27, 216(sp)
|
||||
sd x28, 224(sp)
|
||||
sd x29, 232(sp)
|
||||
sd x30, 240(sp)
|
||||
sd x31, 248(sp)
|
||||
|
||||
csrr t0, sscratch
|
||||
sd t0, 16(sp)
|
||||
|
||||
csrr t0, sepc
|
||||
sd t0, 256(sp)
|
||||
csrr t0, sstatus
|
||||
sd t0, 264(sp)
|
||||
|
||||
mv a0, sp
|
||||
call trap_handler_rust
|
||||
|
||||
ld t0, 256(sp)
|
||||
csrw sepc, t0
|
||||
ld t0, 264(sp)
|
||||
csrw sstatus, t0
|
||||
|
||||
andi t1, t0, 0x100
|
||||
bnez t1, _restore_smode
|
||||
|
||||
ld t0, 16(sp)
|
||||
csrw sscratch, t0
|
||||
|
||||
_restore_smode:
|
||||
ld x1, 8(sp)
|
||||
ld x3, 24(sp)
|
||||
ld x4, 32(sp)
|
||||
ld x5, 40(sp)
|
||||
ld x6, 48(sp)
|
||||
ld x7, 56(sp)
|
||||
ld x8, 64(sp)
|
||||
ld x9, 72(sp)
|
||||
ld x10, 80(sp)
|
||||
ld x11, 88(sp)
|
||||
ld x12, 96(sp)
|
||||
ld x13, 104(sp)
|
||||
ld x14, 112(sp)
|
||||
ld x15, 120(sp)
|
||||
ld x16, 128(sp)
|
||||
ld x17, 136(sp)
|
||||
ld x18, 144(sp)
|
||||
ld x19, 152(sp)
|
||||
ld x20, 160(sp)
|
||||
ld x21, 168(sp)
|
||||
ld x22, 176(sp)
|
||||
ld x23, 184(sp)
|
||||
ld x24, 192(sp)
|
||||
ld x25, 200(sp)
|
||||
ld x26, 208(sp)
|
||||
ld x27, 216(sp)
|
||||
ld x28, 224(sp)
|
||||
ld x29, 232(sp)
|
||||
ld x30, 240(sp)
|
||||
ld x31, 248(sp)
|
||||
|
||||
addi sp, sp, 272
|
||||
|
||||
csrr t0, sstatus
|
||||
andi t0, t0, 0x100
|
||||
bnez t0, _sret_smode
|
||||
csrrw sp, sscratch, sp
|
||||
_sret_smode:
|
||||
sret
|
||||
"#);
|
||||
|
||||
const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
|
||||
const CAUSE_S_SOFTWARE: usize = 1;
|
||||
const CAUSE_S_TIMER: usize = 5;
|
||||
const CAUSE_S_EXTERNAL: usize = 9;
|
||||
const CAUSE_ILLEGAL_INSTRUCTION: usize = 2;
|
||||
const CAUSE_ECALL_U: usize = 8;
|
||||
/// Set in `sstatus` when the trap came from supervisor mode.
|
||||
const SSTATUS_SPP: usize = 1 << 8;
|
||||
|
||||
pub fn init() {
|
||||
unsafe extern "C" {
|
||||
fn trap_vector();
|
||||
}
|
||||
unsafe {
|
||||
let addr = trap_vector as usize;
|
||||
let addr = trap_vector as *const () as usize;
|
||||
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
|
||||
core::arch::asm!("csrw sscratch, zero");
|
||||
// SIE stays clear — interrupts are enabled only when entering userspace via sret
|
||||
// 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.
|
||||
let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1);
|
||||
core::arch::asm!("csrs sie, {0}", in(reg) sie_bits);
|
||||
}
|
||||
@@ -147,232 +45,38 @@ pub fn set_kernel_stack(kernel_sp: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> {
|
||||
use domain::message::Message;
|
||||
use domain::syscall::{Syscall, WaitSet};
|
||||
use types::syscall_abi::*;
|
||||
|
||||
let nr = frame.regs[17]; // a7
|
||||
let a0 = frame.regs[10];
|
||||
let a1 = frame.regs[11];
|
||||
let a2 = frame.regs[12];
|
||||
let a3 = frame.regs[13];
|
||||
let a4 = frame.regs[14];
|
||||
|
||||
match nr {
|
||||
SYS_EXIT => Some(Syscall::Exit),
|
||||
SYS_HALT => Some(Syscall::Halt),
|
||||
SYS_EXIT_THREAD => Some(Syscall::ExitThread),
|
||||
SYS_YIELD => Some(Syscall::Yield),
|
||||
SYS_GET_TIME => Some(Syscall::GetTime),
|
||||
SYS_GET_PROCESS_ID => Some(Syscall::GetProcessId),
|
||||
SYS_SEND => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
let msg = Message::from_words([a1, a2, a3, a4]);
|
||||
Some(Syscall::Send { endpoint: slot, message: msg })
|
||||
}
|
||||
SYS_RECEIVE => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::Receive { endpoint: slot })
|
||||
}
|
||||
SYS_DELEGATE => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
let to = types::bounded::CapSlot::new(a1 as u8)?;
|
||||
let perms = types::permission::PermissionSet::from_bits(a2 as u8);
|
||||
Some(Syscall::Delegate { slot, to_endpoint: to, permissions: perms })
|
||||
}
|
||||
SYS_WAIT_FOR_INTERRUPT => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::WaitForInterrupt { irq: slot })
|
||||
}
|
||||
SYS_ACKNOWLEDGE_INTERRUPT => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::AcknowledgeInterrupt { irq: slot })
|
||||
}
|
||||
SYS_WAIT_ANY => {
|
||||
let ep0 = if a0 != usize::MAX { types::bounded::CapSlot::new(a0 as u8) } else { None };
|
||||
let ep1 = if a1 != usize::MAX { types::bounded::CapSlot::new(a1 as u8) } else { None };
|
||||
let ep2 = if a2 != usize::MAX { types::bounded::CapSlot::new(a2 as u8) } else { None };
|
||||
let ep3 = if a3 != usize::MAX { types::bounded::CapSlot::new(a3 as u8) } else { None };
|
||||
let sources = WaitSet::new(
|
||||
[ep0, ep1, ep2, ep3],
|
||||
[None, None, None, None],
|
||||
);
|
||||
Some(Syscall::WaitAny { sources })
|
||||
}
|
||||
SYS_CREATE_PROCESS => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::CreateProcess {
|
||||
create_cap: slot,
|
||||
image: types::address::DataRef::empty(),
|
||||
})
|
||||
}
|
||||
SYS_KILL => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::Kill { process: slot })
|
||||
}
|
||||
SYS_REVOKE => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::Revoke { slot })
|
||||
}
|
||||
SYS_SPAWN_THREAD => Some(Syscall::SpawnThread {
|
||||
entry: types::address::EntryPoint::from_raw(a0),
|
||||
stack: types::address::StackPointer::from_raw(a1),
|
||||
}),
|
||||
SYS_REQUEST_PAGES => {
|
||||
let count = types::bounded::PageCount::new(a0 as u16)?;
|
||||
Some(Syscall::RequestPages { count })
|
||||
}
|
||||
SYS_RELEASE_PAGES => {
|
||||
let count = types::bounded::PageCount::new(a1 as u16)?;
|
||||
Some(Syscall::ReleasePages {
|
||||
addr: types::address::VirtualAddress::from_raw(a0),
|
||||
count,
|
||||
})
|
||||
}
|
||||
SYS_SPAWN_THREAD_IN => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::SpawnThreadIn {
|
||||
process: slot,
|
||||
entry: types::address::EntryPoint::from_raw(a1),
|
||||
stack: types::address::StackPointer::from_raw(a2),
|
||||
})
|
||||
}
|
||||
SYS_CREATE_ENDPOINT => Some(Syscall::CreateEndpoint),
|
||||
SYS_DEBUG_PUTCHAR => Some(Syscall::DebugPutchar { byte: a0 as u8 }),
|
||||
SYS_MAP_PAGE => {
|
||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
||||
Some(Syscall::MapPage {
|
||||
process: slot,
|
||||
src_va: types::address::VirtualAddress::from_raw(a1),
|
||||
dst_va: types::address::VirtualAddress::from_raw(a2),
|
||||
executable: a3 != 0,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
fn args(frame: &TrapFrame) -> [usize; 6] {
|
||||
let mut a = [0usize; 6];
|
||||
a.copy_from_slice(&frame.regs[10..16]);
|
||||
a
|
||||
}
|
||||
|
||||
fn write_result(frame: &mut TrapFrame, outcome: &kernel::SyscallOutcome) {
|
||||
use kernel::{SyscallOutcome, SyscallResult};
|
||||
|
||||
match outcome {
|
||||
SyscallOutcome::Complete(result) => match result {
|
||||
SyscallResult::Ok => frame.regs[10] = 0,
|
||||
SyscallResult::Value(v) => frame.regs[10] = *v,
|
||||
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
|
||||
SyscallResult::Message(m) => {
|
||||
frame.regs[10] = m.word(0);
|
||||
frame.regs[11] = m.word(1);
|
||||
frame.regs[12] = m.word(2);
|
||||
frame.regs[13] = m.word(3);
|
||||
}
|
||||
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
|
||||
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
|
||||
},
|
||||
SyscallOutcome::BlockCaller(_) => {}
|
||||
SyscallOutcome::SwitchTo(_) => frame.regs[10] = 0,
|
||||
SyscallOutcome::TerminateCaller => {}
|
||||
}
|
||||
fn set_results(frame: &mut TrapFrame, regs: [usize; 8]) {
|
||||
frame.regs[10..18].copy_from_slice(®s);
|
||||
}
|
||||
|
||||
fn save_frame_to_context(frame: &TrapFrame, ctx: &mut domain::thread::SavedContext) {
|
||||
ctx.registers_mut().copy_from_slice(&frame.regs);
|
||||
ctx.set_pc(frame.sepc);
|
||||
ctx.set_status(frame.sstatus);
|
||||
fn write_result(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
|
||||
set_results(frame, kernel::abi::encode(result));
|
||||
}
|
||||
|
||||
fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFrame) {
|
||||
frame.regs.copy_from_slice(ctx.registers());
|
||||
frame.sepc = ctx.program_counter();
|
||||
frame.sstatus = ctx.status();
|
||||
// ensure returning to U-mode
|
||||
frame.sstatus &= !(1 << 8); // clear SPP
|
||||
frame.sstatus |= 1 << 5; // set SPIE
|
||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
// SAFETY: only called with the kernel lock held.
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
if let Some(result) = kern.take_pending_result(tid) {
|
||||
write_result_direct(frame, &result);
|
||||
}
|
||||
fn this_core() -> types::id::CoreId {
|
||||
types::id::CoreId::from_raw(crate::smp::hart_id() as u8)
|
||||
}
|
||||
|
||||
fn write_result_direct(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
|
||||
use kernel::SyscallResult;
|
||||
match result {
|
||||
SyscallResult::Ok => frame.regs[10] = 0,
|
||||
SyscallResult::Value(v) => frame.regs[10] = *v,
|
||||
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
|
||||
SyscallResult::Message(m) => {
|
||||
frame.regs[10] = m.word(0);
|
||||
frame.regs[11] = m.word(1);
|
||||
frame.regs[12] = m.word(2);
|
||||
frame.regs[13] = m.word(3);
|
||||
/// Claims every pending PLIC interrupt (boot hart only) and hands it to the kernel.
|
||||
fn dispatch_external() {
|
||||
loop {
|
||||
let irq_num = crate::hw::plic::Plic::claim();
|
||||
if irq_num == 0 {
|
||||
return;
|
||||
}
|
||||
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
|
||||
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_to_thread(frame: &mut TrapFrame, current_tid: types::id::ThreadId, next_tid: types::id::ThreadId) {
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
|
||||
if let Some(ctx) = kern.thread_context_mut(current_tid) {
|
||||
save_frame_to_context(frame, ctx);
|
||||
}
|
||||
|
||||
let next_pid = kern.thread_process(next_tid);
|
||||
let current_pid = kern.thread_process(current_tid);
|
||||
|
||||
if let Some(ctx) = kern.thread_context(next_tid) {
|
||||
load_context_to_frame(ctx, frame);
|
||||
}
|
||||
|
||||
apply_pending_result(frame, next_tid);
|
||||
|
||||
if next_pid != current_pid {
|
||||
if let Some(pid) = next_pid {
|
||||
let pt_addr = kern.platform_mut().page_table_for(pid);
|
||||
if pt_addr != 0 {
|
||||
let pt = unsafe { &*(pt_addr as *const crate::mmu::PageTable) };
|
||||
crate::mmu::activate(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
|
||||
if let Some(ctx) = kern.thread_context_mut(current_tid) {
|
||||
save_frame_to_context(frame, ctx);
|
||||
}
|
||||
|
||||
if let Some(next_tid) = kern.pick_next_thread() {
|
||||
let next_pid = kern.thread_process(next_tid);
|
||||
let current_pid = kern.thread_process(current_tid);
|
||||
|
||||
let core = types::id::CoreId::from_raw(0);
|
||||
let _ = kern.set_thread_running(next_tid, core);
|
||||
|
||||
if let Some(ctx) = kern.thread_context(next_tid) {
|
||||
load_context_to_frame(ctx, frame);
|
||||
}
|
||||
|
||||
apply_pending_result(frame, next_tid);
|
||||
|
||||
if next_pid != current_pid {
|
||||
if let Some(pid) = next_pid {
|
||||
let pt_addr = kern.platform_mut().page_table_for(pid);
|
||||
if pt_addr != 0 {
|
||||
let pt = unsafe { &*(pt_addr as *const crate::mmu::PageTable) };
|
||||
crate::mmu::activate(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
kern().handle_interrupt(types::id::IrqId::from_raw(irq_num as u16));
|
||||
crate::hw::plic::Plic::complete(irq_num);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,75 +85,55 @@ extern "C" fn trap_handler_rust(frame: &mut TrapFrame) {
|
||||
let scause: usize;
|
||||
unsafe { core::arch::asm!("csrr {0}, scause", out(reg) scause) };
|
||||
|
||||
if frame.sstatus & SSTATUS_SPP != 0 {
|
||||
// Checked before taking the lock: this hart may already hold it.
|
||||
kernel_fault(frame, scause);
|
||||
}
|
||||
if scause == CAUSE_ECALL_U && fast_syscall(frame) {
|
||||
return;
|
||||
}
|
||||
crate::smp::service_ipi();
|
||||
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 {
|
||||
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
|
||||
} else {
|
||||
handle_exception(frame, scause);
|
||||
}
|
||||
finish(frame);
|
||||
release();
|
||||
}
|
||||
|
||||
fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
|
||||
match cause {
|
||||
CAUSE_S_TIMER => {
|
||||
crate::hw::timer::handle_interrupt();
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
let core = types::id::CoreId::from_raw(0);
|
||||
let kern = kern();
|
||||
kern.fire_timers();
|
||||
if let Some(current_tid) = kern.current_thread() {
|
||||
let decision = kern.handle_tick(core);
|
||||
let decision = kern.handle_tick(this_core());
|
||||
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {
|
||||
switch_to_thread(frame, current_tid, next);
|
||||
switch_to_thread(frame, Some(current_tid), next);
|
||||
}
|
||||
}
|
||||
}
|
||||
CAUSE_S_EXTERNAL => {
|
||||
let irq_num = crate::hw::plic::Plic::claim();
|
||||
if irq_num != 0 {
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
let irq = types::id::IrqId::from_raw(irq_num as u16);
|
||||
kern.handle_interrupt(irq);
|
||||
crate::hw::plic::Plic::complete(irq_num);
|
||||
}
|
||||
}
|
||||
CAUSE_S_EXTERNAL => dispatch_external(),
|
||||
CAUSE_S_SOFTWARE => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
match scause {
|
||||
CAUSE_ECALL_U => {
|
||||
frame.sepc += 4;
|
||||
|
||||
if let Some(syscall) = decode_syscall(frame) {
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
let current_tid = kern.current_thread();
|
||||
let outcome = kern.handle_syscall(syscall);
|
||||
write_result(frame, &outcome);
|
||||
|
||||
match outcome {
|
||||
kernel::SyscallOutcome::Complete(_) => {}
|
||||
kernel::SyscallOutcome::BlockCaller(_) => {
|
||||
if let Some(tid) = current_tid {
|
||||
switch_to_next(frame, tid);
|
||||
}
|
||||
}
|
||||
kernel::SyscallOutcome::SwitchTo(next) => {
|
||||
if let Some(tid) = current_tid {
|
||||
switch_to_thread(frame, tid, next);
|
||||
}
|
||||
}
|
||||
kernel::SyscallOutcome::TerminateCaller => {
|
||||
/// The kernel itself faulted; its state is untrusted, so halt.
|
||||
fn kernel_fault(frame: &TrapFrame, scause: usize) -> ! {
|
||||
let uart = crate::hw::uart::Uart::new();
|
||||
uart.write_str("k-os: process exited\n");
|
||||
if let Some(tid) = current_tid {
|
||||
switch_to_next(frame, tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let uart = crate::hw::uart::Uart::new();
|
||||
uart.write_str("FAULT: scause=");
|
||||
uart.write_str("KERNEL FAULT: scause=");
|
||||
uart.write_hex(scause);
|
||||
let stval: usize;
|
||||
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
||||
@@ -457,9 +141,70 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
uart.write_hex(stval);
|
||||
uart.write_str(" sepc=");
|
||||
uart.write_hex(frame.sepc);
|
||||
uart.write_str(" hart=");
|
||||
uart.write_dec(crate::smp::hart_id());
|
||||
uart.put_char(b'\n');
|
||||
crate::halt(&uart, "fault in kernel mode");
|
||||
}
|
||||
|
||||
fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
match scause {
|
||||
CAUSE_ECALL_U => {
|
||||
frame.sepc += 4;
|
||||
|
||||
let Some(syscall) = kernel::abi::decode(frame.regs[17], &args(frame)) else {
|
||||
set_results(frame, kernel::abi::unknown_syscall());
|
||||
return;
|
||||
};
|
||||
{
|
||||
let kern = kern();
|
||||
let current_tid = kern.current_thread();
|
||||
let outcome = kern.handle_syscall(syscall);
|
||||
|
||||
match outcome {
|
||||
kernel::SyscallOutcome::Complete(result) => write_result(frame, &result),
|
||||
kernel::SyscallOutcome::BlockCaller(_) => switch_to_next(frame, current_tid),
|
||||
kernel::SyscallOutcome::SwitchTo(next) => {
|
||||
switch_to_thread(frame, current_tid, next)
|
||||
}
|
||||
kernel::SyscallOutcome::TerminateCaller => {
|
||||
let uart = crate::hw::uart::Uart::new();
|
||||
uart.write_str("k-os: process exited\n");
|
||||
switch_to_next(frame, current_tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 stval: usize;
|
||||
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) 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);
|
||||
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.put_char(b'\n');
|
||||
|
||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||
if let Some(current_tid) = kern.current_thread() {
|
||||
let fault = match scause {
|
||||
2 => domain::fault::Fault::IllegalInstruction,
|
||||
@@ -469,10 +214,9 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||
_ => domain::fault::Fault::InvalidSyscall,
|
||||
};
|
||||
kern.handle_fault(current_tid, fault, frame.sepc);
|
||||
switch_to_next(frame, current_tid);
|
||||
} else {
|
||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
||||
switch_to_next(frame, Some(current_tid));
|
||||
}
|
||||
// No current thread (destroyed by another hart): `finish` picks one.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
141
crates/adapter-riscv/src/trap/sched.rs
Normal file
141
crates/adapter-riscv/src/trap/sched.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use super::{
|
||||
CAUSE_S_EXTERNAL, CAUSE_S_TIMER, TrapFrame, dispatch_external, kern, this_core, write_result,
|
||||
};
|
||||
|
||||
fn save_frame_to_context(frame: &TrapFrame, ctx: &mut domain::thread::SavedContext) {
|
||||
ctx.registers_mut().copy_from_slice(&frame.regs);
|
||||
ctx.set_pc(frame.sepc);
|
||||
ctx.set_status(frame.sstatus);
|
||||
}
|
||||
|
||||
fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFrame) {
|
||||
frame.regs.copy_from_slice(ctx.registers());
|
||||
frame.sepc = ctx.program_counter();
|
||||
frame.sstatus = ctx.status();
|
||||
frame.sstatus &= !(1 << 8); // SPP: return to U-mode
|
||||
frame.sstatus |= 1 << 5; // SPIE
|
||||
super::fp::load_for(frame, ctx);
|
||||
}
|
||||
|
||||
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
|
||||
if let Some(result) = kern().take_pending_result(tid) {
|
||||
write_result(frame, &result);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
fn trap_return(frame: *mut TrapFrame) -> !;
|
||||
}
|
||||
|
||||
/// Saves `frame` into `from` (if it still exists) and loads `to`.
|
||||
pub(super) fn switch_to_thread(
|
||||
frame: &mut TrapFrame,
|
||||
from: Option<types::id::ThreadId>,
|
||||
to: types::id::ThreadId,
|
||||
) {
|
||||
let kern = kern();
|
||||
if let Some(ctx) = from.and_then(|t| kern.thread_context_mut(t)) {
|
||||
save_frame_to_context(frame, ctx);
|
||||
}
|
||||
if let Some(ctx) = kern.thread_context(to) {
|
||||
load_context_to_frame(ctx, frame);
|
||||
}
|
||||
apply_pending_result(frame, to);
|
||||
}
|
||||
|
||||
/// Saves `from` and runs the next ready thread, idling until there is one.
|
||||
pub(super) fn switch_to_next(frame: &mut TrapFrame, from: Option<types::id::ThreadId>) {
|
||||
if let Some(ctx) = from.and_then(|t| kern().thread_context_mut(t)) {
|
||||
save_frame_to_context(frame, ctx);
|
||||
}
|
||||
let next = next_runnable();
|
||||
let _ = kern().set_thread_running(next, this_core());
|
||||
switch_to_thread(frame, None, next);
|
||||
}
|
||||
|
||||
/// The next thread to run on this hart, idling (lock released, kernel
|
||||
/// address space active so no shootdown is needed) until one is ready.
|
||||
fn next_runnable() -> types::id::ThreadId {
|
||||
loop {
|
||||
if let Some(tid) = kern().pick_next_thread() {
|
||||
return tid;
|
||||
}
|
||||
crate::mmu::activate(crate::mmu::page_table::table_at(crate::smp::kernel_root()));
|
||||
release();
|
||||
unsafe { core::arch::asm!("wfi") };
|
||||
crate::smp::service_ipi();
|
||||
crate::smp::lock();
|
||||
let _ = crate::smp::take_reschedule();
|
||||
let sip: usize;
|
||||
unsafe { core::arch::asm!("csrr {0}, sip", out(reg) sip) };
|
||||
if sip & (1 << CAUSE_S_TIMER) != 0 {
|
||||
// No thread is running, so there is no time slice to charge.
|
||||
crate::hw::timer::handle_interrupt();
|
||||
kern().fire_timers();
|
||||
}
|
||||
if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
|
||||
dispatch_external();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases the kernel lock after publishing state the lock-free fast paths read.
|
||||
pub(super) fn release() {
|
||||
let kern = kern();
|
||||
for core in 0..crate::smp::num_harts() {
|
||||
let id = types::id::CoreId::from_raw(core as u8);
|
||||
crate::smp::publish_waiting(core, kern.highest_waiting(id).map(|p| p.value()));
|
||||
}
|
||||
let running = kern.current_thread();
|
||||
crate::smp::publish_running(
|
||||
running
|
||||
.and_then(|t| kern.thread_process(t))
|
||||
.map(|p| p.raw()),
|
||||
running
|
||||
.and_then(|t| kern.thread_priority(t))
|
||||
.map(|p| p.value()),
|
||||
);
|
||||
crate::smp::unlock();
|
||||
}
|
||||
|
||||
/// Last step before returning to userspace: apply a reschedule request,
|
||||
/// ensure this hart has a thread, and activate its address space.
|
||||
pub(super) fn finish(frame: &mut TrapFrame) {
|
||||
let kern = kern();
|
||||
let current = kern.current_thread();
|
||||
if crate::smp::take_reschedule()
|
||||
&& let Some(cur) = current
|
||||
&& let domain::scheduler::SchedulerDecision::Preempt(next) = kern.reschedule(this_core())
|
||||
{
|
||||
switch_to_thread(frame, Some(cur), next);
|
||||
}
|
||||
if kern.current_thread().is_none() {
|
||||
switch_to_next(frame, None);
|
||||
}
|
||||
let root = kern
|
||||
.current_thread()
|
||||
.and_then(|t| kern.thread_process(t))
|
||||
.map(|pid| kern.platform_mut().page_table_for(pid))
|
||||
.unwrap_or(0);
|
||||
if root != 0 && crate::mmu::active_root() != root {
|
||||
crate::mmu::activate(crate::mmu::page_table::table_at(root));
|
||||
}
|
||||
}
|
||||
|
||||
/// Enters userspace for the first time on this hart (kernel lock held).
|
||||
pub fn enter_first() -> ! {
|
||||
let mut frame = TrapFrame {
|
||||
regs: [0; 32],
|
||||
sepc: 0,
|
||||
sstatus: 0,
|
||||
};
|
||||
match kern().current_thread() {
|
||||
Some(tid) => switch_to_thread(&mut frame, None, tid),
|
||||
None => switch_to_next(&mut frame, None),
|
||||
}
|
||||
finish(&mut frame);
|
||||
release();
|
||||
// SAFETY: `frame` holds a user context; sscratch is set from it on
|
||||
// the way out, so later traps land at the top of this frame.
|
||||
unsafe { trap_return(&mut frame) }
|
||||
}
|
||||
118
crates/adapter-riscv/src/trap/vector.rs
Normal file
118
crates/adapter-riscv/src/trap/vector.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use core::arch::global_asm;
|
||||
|
||||
global_asm!(
|
||||
r#"
|
||||
.section .text
|
||||
.align 4
|
||||
.globl trap_vector
|
||||
trap_vector:
|
||||
csrrw sp, sscratch, sp
|
||||
bnez sp, _from_user
|
||||
csrrw sp, sscratch, sp
|
||||
_from_user:
|
||||
addi sp, sp, -272
|
||||
|
||||
sd x1, 8(sp)
|
||||
sd x3, 24(sp)
|
||||
sd x4, 32(sp)
|
||||
sd x5, 40(sp)
|
||||
sd x6, 48(sp)
|
||||
sd x7, 56(sp)
|
||||
sd x8, 64(sp)
|
||||
sd x9, 72(sp)
|
||||
sd x10, 80(sp)
|
||||
sd x11, 88(sp)
|
||||
sd x12, 96(sp)
|
||||
sd x13, 104(sp)
|
||||
sd x14, 112(sp)
|
||||
sd x15, 120(sp)
|
||||
sd x16, 128(sp)
|
||||
sd x17, 136(sp)
|
||||
sd x18, 144(sp)
|
||||
sd x19, 152(sp)
|
||||
sd x20, 160(sp)
|
||||
sd x21, 168(sp)
|
||||
sd x22, 176(sp)
|
||||
sd x23, 184(sp)
|
||||
sd x24, 192(sp)
|
||||
sd x25, 200(sp)
|
||||
sd x26, 208(sp)
|
||||
sd x27, 216(sp)
|
||||
sd x28, 224(sp)
|
||||
sd x29, 232(sp)
|
||||
sd x30, 240(sp)
|
||||
sd x31, 248(sp)
|
||||
|
||||
csrr t0, sscratch
|
||||
sd t0, 16(sp)
|
||||
|
||||
csrr t0, sepc
|
||||
sd t0, 256(sp)
|
||||
csrr t0, sstatus
|
||||
sd t0, 264(sp)
|
||||
|
||||
mv a0, sp
|
||||
call trap_handler_rust
|
||||
j _restore_frame
|
||||
|
||||
# trap_return(frame): resume `frame` as if returning from the trap that saved it.
|
||||
.globl trap_return
|
||||
trap_return:
|
||||
mv sp, a0
|
||||
_restore_frame:
|
||||
ld t0, 256(sp)
|
||||
csrw sepc, t0
|
||||
ld t0, 264(sp)
|
||||
csrw sstatus, t0
|
||||
|
||||
andi t1, t0, 0x100
|
||||
bnez t1, _restore_smode
|
||||
|
||||
ld t0, 16(sp)
|
||||
csrw sscratch, t0
|
||||
|
||||
_restore_smode:
|
||||
ld x1, 8(sp)
|
||||
ld x3, 24(sp)
|
||||
ld x4, 32(sp)
|
||||
ld x6, 48(sp)
|
||||
ld x7, 56(sp)
|
||||
ld x8, 64(sp)
|
||||
ld x9, 72(sp)
|
||||
ld x10, 80(sp)
|
||||
ld x11, 88(sp)
|
||||
ld x12, 96(sp)
|
||||
ld x13, 104(sp)
|
||||
ld x14, 112(sp)
|
||||
ld x15, 120(sp)
|
||||
ld x16, 128(sp)
|
||||
ld x17, 136(sp)
|
||||
ld x18, 144(sp)
|
||||
ld x19, 152(sp)
|
||||
ld x20, 160(sp)
|
||||
ld x21, 168(sp)
|
||||
ld x22, 176(sp)
|
||||
ld x23, 184(sp)
|
||||
ld x24, 192(sp)
|
||||
ld x25, 200(sp)
|
||||
ld x26, 208(sp)
|
||||
ld x27, 216(sp)
|
||||
ld x28, 224(sp)
|
||||
ld x29, 232(sp)
|
||||
ld x30, 240(sp)
|
||||
ld x31, 248(sp)
|
||||
|
||||
# t0 (x5) is restored last on each path; it is the only scratch register.
|
||||
csrr t0, sstatus
|
||||
andi t0, t0, 0x100
|
||||
bnez t0, _sret_smode
|
||||
ld x5, 40(sp)
|
||||
addi sp, sp, 272
|
||||
csrrw sp, sscratch, sp
|
||||
sret
|
||||
_sret_smode:
|
||||
ld x5, 40(sp)
|
||||
addi sp, sp, 272
|
||||
sret
|
||||
"#
|
||||
);
|
||||
13
crates/apps/cat/Cargo.toml
Normal file
13
crates/apps/cat/Cargo.toml
Normal 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 }
|
||||
6
crates/apps/cat/manifest.toml
Normal file
6
crates/apps/cat/manifest.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[app]
|
||||
id = "kos.cat"
|
||||
name = "cat"
|
||||
version = "1.0.0"
|
||||
binary = "cat"
|
||||
author = "K-OS"
|
||||
48
crates/apps/cat/src/main.rs
Normal file
48
crates/apps/cat/src/main.rs
Normal 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>...");
|
||||
}
|
||||
}
|
||||
14
crates/apps/counter/Cargo.toml
Normal file
14
crates/apps/counter/Cargo.toml
Normal 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 }
|
||||
10
crates/apps/counter/manifest.toml
Normal file
10
crates/apps/counter/manifest.toml
Normal 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"]
|
||||
59
crates/apps/counter/src/main.rs
Normal file
59
crates/apps/counter/src/main.rs
Normal 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);
|
||||
@@ -1,4 +1,5 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
@@ -6,12 +7,14 @@ extern crate libkos;
|
||||
use types::bounded::CapSlot;
|
||||
use libkos::syscall;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
let ep = CapSlot::const_new(0);
|
||||
|
||||
let (w0, w1, w2, w3) = syscall::receive(ep);
|
||||
syscall::send(ep, w0.wrapping_add(1), w1, w2, w3);
|
||||
if let Ok(syscall::Received::Call { words: [w0, w1, w2, w3], .. }) = syscall::receive_msg(ep) {
|
||||
let _ = syscall::reply([w0.wrapping_add(1), w1, w2, w3], None);
|
||||
}
|
||||
|
||||
syscall::exit();
|
||||
}
|
||||
|
||||
14
crates/apps/flaky/Cargo.toml
Normal file
14
crates/apps/flaky/Cargo.toml
Normal 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 }
|
||||
82
crates/apps/flaky/src/lib.rs
Normal file
82
crates/apps/flaky/src/lib.rs
Normal 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))
|
||||
}
|
||||
11
crates/apps/flaky/src/main.rs
Normal file
11
crates/apps/flaky/src/main.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
flaky::run();
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
libkos::println!("Hello from k-os!");
|
||||
}
|
||||
|
||||
15
crates/apps/home/Cargo.toml
Normal file
15
crates/apps/home/Cargo.toml
Normal 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 }
|
||||
10
crates/apps/home/manifest.toml
Normal file
10
crates/apps/home/manifest.toml
Normal 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"]
|
||||
118
crates/apps/home/src/main.rs
Normal file
118
crates/apps/home/src/main.rs
Normal 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
13
crates/apps/ls/Cargo.toml
Normal 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 }
|
||||
6
crates/apps/ls/manifest.toml
Normal file
6
crates/apps/ls/manifest.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[app]
|
||||
id = "kos.ls"
|
||||
name = "ls"
|
||||
version = "1.0.0"
|
||||
binary = "ls"
|
||||
author = "K-OS"
|
||||
39
crates/apps/ls/src/main.rs
Normal file
39
crates/apps/ls/src/main.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
15
crates/apps/settings/Cargo.toml
Normal file
15
crates/apps/settings/Cargo.toml
Normal 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 }
|
||||
10
crates/apps/settings/manifest.toml
Normal file
10
crates/apps/settings/manifest.toml
Normal 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"]
|
||||
285
crates/apps/settings/src/main.rs
Normal file
285
crates/apps/settings/src/main.rs
Normal 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>();
|
||||
}
|
||||
14
crates/apps/shell/Cargo.toml
Normal file
14
crates/apps/shell/Cargo.toml
Normal 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 }
|
||||
337
crates/apps/shell/src/main.rs
Normal file
337
crates/apps/shell/src/main.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
14
crates/apps/sleep/Cargo.toml
Normal file
14
crates/apps/sleep/Cargo.toml
Normal 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 }
|
||||
6
crates/apps/sleep/manifest.toml
Normal file
6
crates/apps/sleep/manifest.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[app]
|
||||
id = "kos.sleep"
|
||||
name = "sleep"
|
||||
version = "1.0.0"
|
||||
binary = "sleep"
|
||||
author = "K-OS"
|
||||
30
crates/apps/sleep/src/main.rs
Normal file
30
crates/apps/sleep/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
13
crates/apps/smptest/Cargo.toml
Normal file
13
crates/apps/smptest/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "smptest"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "smptest"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
46
crates/apps/smptest/src/main.rs
Normal file
46
crates/apps/smptest/src/main.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! Spinner half of init's SMP self-test: one thread per hart, each
|
||||
//! incrementing its own counter in the page given in slot 0.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use libkos::syscall;
|
||||
use types::address::VirtualAddress;
|
||||
use types::bounded::CapSlot;
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters.
|
||||
const COUNTERS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
|
||||
const THREADS: usize = 4;
|
||||
|
||||
fn main() {
|
||||
let page = CapSlot::const_new(0);
|
||||
let Ok(window) = libkos::window::map(page, COUNTERS_VA, PagePerms::ReadWrite) else {
|
||||
libkos::println!("smptest: cannot map the counter page");
|
||||
syscall::exit();
|
||||
};
|
||||
let words = window.atomics(types::address::ByteOffset::ZERO, 1 + THREADS);
|
||||
for _ in 1..THREADS {
|
||||
if !libkos::thread::spawn(words, spin) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin(words);
|
||||
}
|
||||
|
||||
libkos::entry!(main);
|
||||
|
||||
fn spin(word: &'static [AtomicUsize]) -> ! {
|
||||
let mine = 1 + word[0].fetch_add(1, Ordering::Relaxed) % THREADS;
|
||||
loop {
|
||||
// Yield so a spinner sharing init's hart cannot starve it.
|
||||
for _ in 0..1024 {
|
||||
word[mine].fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
syscall::yield_now();
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,4 @@ bench = false
|
||||
types = { workspace = true }
|
||||
|
||||
libkos = { workspace = true }
|
||||
display-server = { workspace = true }
|
||||
server-framework = { workspace = true }
|
||||
port-framebuffer = { workspace = true }
|
||||
|
||||
9
crates/apps/snake/manifest.toml
Normal file
9
crates/apps/snake/manifest.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[app]
|
||||
id = "kos.snake"
|
||||
name = "Snake"
|
||||
version = "1.0.0"
|
||||
binary = "snake"
|
||||
author = "K-OS"
|
||||
|
||||
[capabilities]
|
||||
required = ["display"]
|
||||
@@ -44,6 +44,12 @@ pub struct Snake {
|
||||
rng_state: u32,
|
||||
}
|
||||
|
||||
impl Default for Snake {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Snake {
|
||||
pub fn new() -> Self {
|
||||
let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod game;
|
||||
pub mod renderer;
|
||||
|
||||
@@ -1,91 +1,139 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::display::Canvas;
|
||||
use libkos::syscall;
|
||||
use port_framebuffer::Framebuffer;
|
||||
use snake::game::{Direction, GameState, Snake};
|
||||
use snake::renderer::{self, RenderCommand};
|
||||
use types::bounded::CapSlot;
|
||||
use types::display::{Color, Dimension, ScreenCoord};
|
||||
use types::time::Ticks;
|
||||
use libkos::syscall;
|
||||
use server_framework::Encode;
|
||||
use display_server::protocol::DisplayRequest;
|
||||
|
||||
const INPUT_EP: CapSlot = CapSlot::const_new(0);
|
||||
const DISPLAY_EP: CapSlot = CapSlot::const_new(1);
|
||||
const TICK_DELAY: u64 = 5_000_000;
|
||||
/// Steps a second; QEMU virt's timebase is 10 MHz.
|
||||
const STEPS_PER_SECOND: u64 = 8;
|
||||
const TICK_DELAY: u64 = 10_000_000 / STEPS_PER_SECOND;
|
||||
const MAX_RENDER_CMDS: usize = 140;
|
||||
|
||||
fn display_send(req: &DisplayRequest) {
|
||||
let words = req.encode();
|
||||
syscall::send(DISPLAY_EP, words[0], words[1], words[2], words[3]);
|
||||
syscall::receive(DISPLAY_EP);
|
||||
fn draw_rect(canvas: &mut Canvas, x: u16, y: u16, w: u16, h: u16, color: u32) {
|
||||
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) {
|
||||
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),
|
||||
});
|
||||
}
|
||||
libkos::entry!(main);
|
||||
|
||||
fn flush() {
|
||||
display_send(&DisplayRequest::Flush);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
fn main() {
|
||||
// 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 {
|
||||
libkos::println!("snake: needs the display (see its manifest)");
|
||||
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 commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS];
|
||||
|
||||
loop {
|
||||
let count = renderer::render(&snake, &mut commands);
|
||||
for i in 0..count {
|
||||
let cmd = &commands[i];
|
||||
draw_rect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
|
||||
for cmd in &commands[..count] {
|
||||
draw_rect(&mut canvas, cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
|
||||
}
|
||||
flush();
|
||||
canvas.flush();
|
||||
|
||||
if snake.state() == GameState::GameOver {
|
||||
let gw = renderer::grid_pixel_width();
|
||||
let gh = renderer::grid_pixel_height();
|
||||
draw_rect(gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFFCC0000);
|
||||
flush();
|
||||
loop { syscall::yield_now(); }
|
||||
draw_rect(&mut canvas, gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFFCC0000);
|
||||
canvas.flush();
|
||||
// Any key leaves.
|
||||
while next_input(window) == Input::None {
|
||||
clock.wait_tick();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
wait_tick();
|
||||
handle_input(&mut snake);
|
||||
clock.wait_tick();
|
||||
if handle_input(window, &mut snake) == Input::Quit {
|
||||
return;
|
||||
}
|
||||
snake.tick();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_input(snake: &mut Snake) {
|
||||
// Drain all pending events, apply the latest direction
|
||||
loop {
|
||||
let _ = syscall::send(INPUT_EP, 0, 0, 0, 0);
|
||||
let (tag, kind, _, _) = syscall::receive(INPUT_EP);
|
||||
if tag == 0 { break; } // NoEvent — queue drained
|
||||
if tag == 1 {
|
||||
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),
|
||||
_ => {}
|
||||
#[derive(PartialEq)]
|
||||
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 {
|
||||
match next_input(window) {
|
||||
Input::None => return Input::None,
|
||||
Input::Quit => return Input::Quit,
|
||||
Input::Turn(d) => snake.set_direction(d),
|
||||
Input::Other => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_tick() {
|
||||
let start = syscall::get_time();
|
||||
let deadline = Ticks::from_raw(start.raw() + TICK_DELAY);
|
||||
while syscall::get_time().raw() < deadline.raw() {
|
||||
syscall::yield_now();
|
||||
/// The game's clock: a step every `TICK_DELAY`, counted from the last
|
||||
/// step's deadline, so the time drawing takes does not slow it down.
|
||||
struct Clock {
|
||||
alarm: CapSlot,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const CELL_SIZE: u16 = 24;
|
||||
const BG_COLOR: u32 = 0xFF202040;
|
||||
const SNAKE_COLOR: u32 = 0xFF00FF00;
|
||||
const HEAD_COLOR: u32 = 0xFF00CC00;
|
||||
const FOOD_COLOR: u32 = 0xFFFF0000;
|
||||
pub(crate) const FOOD_COLOR: u32 = 0xFFFF0000;
|
||||
const BORDER_COLOR: u32 = 0xFF505080;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -28,5 +28,5 @@ fn food_is_rendered() {
|
||||
let count = render(&snake, &mut commands);
|
||||
// Food is command index 2 (after bg and border)
|
||||
assert!(count > 2);
|
||||
assert_eq!(commands[2].color, 0xFF0040);
|
||||
assert_eq!(commands[2].color, crate::renderer::FOOD_COLOR);
|
||||
}
|
||||
|
||||
15
crates/apps/spawntest/Cargo.toml
Normal file
15
crates/apps/spawntest/Cargo.toml
Normal 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 }
|
||||
424
crates/apps/spawntest/src/main.rs
Normal file
424
crates/apps/spawntest/src/main.rs
Normal 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);
|
||||
}
|
||||
13
crates/apps/tally/Cargo.toml
Normal file
13
crates/apps/tally/Cargo.toml
Normal 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 }
|
||||
13
crates/apps/tally/manifest.toml
Normal file
13
crates/apps/tally/manifest.toml
Normal 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"
|
||||
61
crates/apps/tally/src/main.rs
Normal file
61
crates/apps/tally/src/main.rs
Normal 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>();
|
||||
}
|
||||
@@ -15,5 +15,4 @@ libkos = { workspace = true }
|
||||
server-framework = { workspace = true }
|
||||
display-server = { workspace = true }
|
||||
driver-framebuffer-virtio = { workspace = true }
|
||||
driver-framebuffer-mock = { workspace = true }
|
||||
port-framebuffer = { workspace = true }
|
||||
|
||||
@@ -1,38 +1,133 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use types::bounded::CapSlot;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
let ep = CapSlot::const_new(0);
|
||||
/// Where init put them (see init's manifest: own endpoint, then `uses`).
|
||||
const EP: CapSlot = 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;
|
||||
|
||||
if let Some(fb) = driver_framebuffer_virtio::VirtioFramebuffer::try_new() {
|
||||
run_virtio(fb, ep);
|
||||
} else {
|
||||
run_mock(ep);
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
// Init hands over the GPU, or nothing if the machine has none.
|
||||
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) {
|
||||
run_virtio(fb, input);
|
||||
}
|
||||
libkos::println!("display: no GPU, running headless");
|
||||
run_headless(input);
|
||||
}
|
||||
|
||||
loop { libkos::syscall::yield_now(); }
|
||||
/// 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)]
|
||||
fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot) {
|
||||
use port_framebuffer::Framebuffer;
|
||||
fb.clear(types::display::Color::rgb(30, 30, 60));
|
||||
fb.flush();
|
||||
let mut srv = display_server::server::DisplayServer::new(fb);
|
||||
server_framework::serve(&mut srv, ep);
|
||||
fn run_virtio(fb: driver_framebuffer_virtio::VirtioFramebuffer, input: Option<CapSlot>) -> ! {
|
||||
let mut srv = server(fb, input);
|
||||
server_framework::serve(&mut srv, EP)
|
||||
}
|
||||
|
||||
/// No GPU: clients still get answers (and the dimensions they draw for);
|
||||
/// pixels go nowhere.
|
||||
struct Headless;
|
||||
|
||||
impl port_framebuffer::Framebuffer for Headless {
|
||||
fn width(&self) -> types::display::Dimension {
|
||||
types::display::Dimension::new(640)
|
||||
}
|
||||
fn height(&self) -> types::display::Dimension {
|
||||
types::display::Dimension::new(480)
|
||||
}
|
||||
fn write_pixel(&mut self, _: types::display::ScreenCoord, _: types::display::ScreenCoord, _: types::display::Color) {}
|
||||
fn fill_rect(
|
||||
&mut self,
|
||||
_: types::display::ScreenCoord,
|
||||
_: types::display::ScreenCoord,
|
||||
_: types::display::Dimension,
|
||||
_: types::display::Dimension,
|
||||
_: types::display::Color,
|
||||
) {
|
||||
}
|
||||
fn clear(&mut self, _: types::display::Color) {}
|
||||
fn flush(&mut self) {}
|
||||
fn scroll_up(
|
||||
&mut self,
|
||||
_: types::display::ScreenCoord,
|
||||
_: types::display::Dimension,
|
||||
_: types::display::Dimension,
|
||||
_: types::display::Color,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn run_mock(ep: CapSlot) {
|
||||
let fb = driver_framebuffer_mock::MockFramebuffer::new(
|
||||
types::display::Dimension::new(640),
|
||||
types::display::Dimension::new(480),
|
||||
);
|
||||
let mut srv = display_server::server::DisplayServer::new(fb);
|
||||
server_framework::serve(&mut srv, ep);
|
||||
fn run_headless(input: Option<CapSlot>) -> ! {
|
||||
let mut srv = server(Headless, input);
|
||||
server_framework::serve(&mut srv, EP)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
port-input-device = { workspace = true }
|
||||
types = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
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 port_input_device::InputDevice;
|
||||
use types::bounded::CapSlot;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
let ep = CapSlot::new(0).unwrap();
|
||||
|
||||
if let Some(dev) = driver_input_virtio::VirtioInputDevice::try_new() {
|
||||
run_input(dev, ep);
|
||||
} else {
|
||||
run_uart(ep);
|
||||
// Init hands over the input devices, two (a keyboard, a tablet), each
|
||||
// virtio or, failing a keyboard, the UART; either may be none.
|
||||
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)) => {
|
||||
if let Some(d) = VirtioInputDevice::try_new(mmio, 0) {
|
||||
run(Pair { first: d, second: tablet }, irqs, ep);
|
||||
}
|
||||
|
||||
loop { libkos::syscall::yield_now(); }
|
||||
}
|
||||
(KIND_UART_16550, Some(mmio)) => {
|
||||
if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) {
|
||||
run(Pair { first: d, second: tablet }, irqs, ep);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
libkos::println!("input: no usable input device, exiting");
|
||||
libkos::syscall::exit();
|
||||
}
|
||||
|
||||
/// Serves `device`, woken by its interrupts when it has them.
|
||||
#[inline(never)]
|
||||
fn run_input(device: driver_input_virtio::VirtioInputDevice, ep: CapSlot) {
|
||||
let mut srv = input_server::server::InputServer::new(device);
|
||||
server_framework::serve(&mut srv, ep);
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn run_uart(ep: CapSlot) {
|
||||
let device = driver_input_uart::UartInputDevice::new();
|
||||
let mut srv = input_server::server::InputServer::new(device);
|
||||
server_framework::serve(&mut srv, ep);
|
||||
fn run<D: InputDevice>(device: D, irqs: [Option<CapSlot>; MAX_DEVICES], ep: CapSlot) -> ! {
|
||||
let mut lines = [CapSlot::const_new(0); MAX_DEVICES];
|
||||
let mut n = 0;
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
storage_server::run();
|
||||
}
|
||||
|
||||
@@ -5,3 +5,6 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] }
|
||||
|
||||
80
crates/domain/src/budget.rs
Normal file
80
crates/domain/src/budget.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! What a process may hold: frames and kernel objects. A child's limits
|
||||
//! are carved from its creator's unused budget and go back when it dies,
|
||||
//! so the limits of all live processes never add up to more than init got.
|
||||
|
||||
use crate::memory::quota::Quota;
|
||||
pub use types::budget::Limits;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "budget_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Budget {
|
||||
pub pages: Quota,
|
||||
pub endpoints: Quota,
|
||||
pub notifications: Quota,
|
||||
pub regions: Quota,
|
||||
}
|
||||
|
||||
impl Budget {
|
||||
pub const EMPTY: Self = Self::new(Limits {
|
||||
pages: 0,
|
||||
endpoints: 0,
|
||||
notifications: 0,
|
||||
regions: 0,
|
||||
});
|
||||
|
||||
pub const fn new(limits: Limits) -> Self {
|
||||
Self {
|
||||
pages: Quota::new(limits.pages),
|
||||
endpoints: Quota::new(limits.endpoints),
|
||||
notifications: Quota::new(limits.notifications),
|
||||
regions: Quota::new(limits.regions),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn limits(&self) -> Limits {
|
||||
Limits {
|
||||
pages: self.pages.limit(),
|
||||
endpoints: self.endpoints.limit(),
|
||||
notifications: self.notifications.limit(),
|
||||
regions: self.regions.limit(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parts(&mut self) -> [&mut Quota; 4] {
|
||||
[
|
||||
&mut self.pages,
|
||||
&mut self.endpoints,
|
||||
&mut self.notifications,
|
||||
&mut self.regions,
|
||||
]
|
||||
}
|
||||
|
||||
/// Carves `child` out of the unused budget: all of it or nothing.
|
||||
pub fn carve(&mut self, child: Limits) -> bool {
|
||||
let amounts = amounts(child);
|
||||
let fits = self.parts().iter().zip(amounts).all(|(q, n)| {
|
||||
q.limit()
|
||||
.checked_sub(n)
|
||||
.is_some_and(|rest| rest >= q.used())
|
||||
});
|
||||
if fits {
|
||||
for (q, n) in self.parts().into_iter().zip(amounts) {
|
||||
q.carve(n);
|
||||
}
|
||||
}
|
||||
fits
|
||||
}
|
||||
|
||||
pub fn refund(&mut self, child: Limits) {
|
||||
for (q, n) in self.parts().into_iter().zip(amounts(child)) {
|
||||
q.refund(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn amounts(l: Limits) -> [usize; 4] {
|
||||
[l.pages, l.endpoints, l.notifications, l.regions]
|
||||
}
|
||||
35
crates/domain/src/budget_tests.rs
Normal file
35
crates/domain/src/budget_tests.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::{Budget, Limits};
|
||||
|
||||
const L: Limits = Limits {
|
||||
pages: 10,
|
||||
endpoints: 2,
|
||||
notifications: 2,
|
||||
regions: 1,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn a_carve_takes_all_parts_or_none() {
|
||||
let mut b = Budget::new(L);
|
||||
b.endpoints.charge(2);
|
||||
let child = Limits {
|
||||
pages: 1,
|
||||
endpoints: 1,
|
||||
..Limits::default()
|
||||
};
|
||||
assert!(!b.carve(child));
|
||||
assert_eq!(b.limits(), L);
|
||||
assert!(b.carve(Limits {
|
||||
endpoints: 0,
|
||||
..child
|
||||
}));
|
||||
assert_eq!(b.limits().pages, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refund_restores_what_was_carved() {
|
||||
let mut b = Budget::new(L);
|
||||
assert!(b.carve(L));
|
||||
assert_eq!(b.limits(), Limits::default());
|
||||
b.refund(L);
|
||||
assert_eq!(b.limits(), L);
|
||||
}
|
||||
83
crates/domain/src/capability/derivation.rs
Normal file
83
crates/domain/src/capability/derivation.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! The capability derivation relation, reduced to its essence: every
|
||||
//! capability is a node with at most one parent. Kept free of kernel
|
||||
//! types so the revocation rule can be checked on its own.
|
||||
|
||||
/// `result[i]` is true iff `root` is `i` or an ancestor of `i`. `parents` must be acyclic.
|
||||
pub fn descendants<const N: usize>(parents: &[Option<u16>; N], root: usize) -> [bool; N] {
|
||||
let mut marked = [false; N];
|
||||
let Some(r) = marked.get_mut(root) else {
|
||||
return marked;
|
||||
};
|
||||
*r = true;
|
||||
// Each pass extends the marked set by at least one generation of
|
||||
// children; a chain in an N-node forest is at most N - 1 long.
|
||||
for _ in 0..N {
|
||||
let mut changed = false;
|
||||
for i in 0..N {
|
||||
let parent_marked = parents
|
||||
.get(i)
|
||||
.copied()
|
||||
.flatten()
|
||||
.and_then(|p| marked.get(usize::from(p)).copied())
|
||||
.unwrap_or(false);
|
||||
if let Some(m) = marked.get_mut(i)
|
||||
&& !*m
|
||||
&& parent_marked
|
||||
{
|
||||
*m = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
marked
|
||||
}
|
||||
|
||||
#[cfg(kani)]
|
||||
mod proofs {
|
||||
use super::descendants;
|
||||
|
||||
const N: usize = 4;
|
||||
|
||||
fn is_ancestor(parents: &[Option<u16>; N], root: usize, mut i: usize) -> bool {
|
||||
for _ in 0..=N {
|
||||
if i == root {
|
||||
return true;
|
||||
}
|
||||
match parents[i].map(usize::from) {
|
||||
Some(p) if p < N => i = p,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// On every acyclic forest, `descendants` marks exactly the subtree under `root`.
|
||||
#[kani::proof]
|
||||
#[kani::unwind(6)]
|
||||
fn marks_exactly_the_subtree() {
|
||||
let mut parents = [None; N];
|
||||
for slot in parents.iter_mut() {
|
||||
if kani::any() {
|
||||
*slot = Some(kani::any::<u16>() % N as u16);
|
||||
}
|
||||
}
|
||||
// Only forests: following parents never revisits a node.
|
||||
for start in 0..N {
|
||||
let mut i = start;
|
||||
let mut steps = 0;
|
||||
while let Some(p) = parents[i] {
|
||||
steps += 1;
|
||||
kani::assume(steps <= N - 1);
|
||||
i = p as usize;
|
||||
}
|
||||
}
|
||||
let root = kani::any::<usize>() % N;
|
||||
let marked = descendants(&parents, root);
|
||||
for i in 0..N {
|
||||
assert!(marked[i] == is_ancestor(&parents, root, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
40
crates/domain/src/capability/derivation_tests.rs
Normal file
40
crates/domain/src/capability/derivation_tests.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use super::derivation::descendants;
|
||||
|
||||
#[test]
|
||||
fn a_root_with_no_children_marks_only_itself() {
|
||||
let parents = [None, None, None];
|
||||
assert_eq!(descendants(&parents, 1), [false, true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marks_children_and_grandchildren() {
|
||||
// 0 -> 1 -> 2, 0 -> 3, 4 unrelated
|
||||
let parents = [None, Some(0), Some(1), Some(0), None];
|
||||
assert_eq!(descendants(&parents, 0), [true, true, true, true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoking_a_middle_node_spares_its_ancestors_and_siblings() {
|
||||
// 0 -> 1 -> 2, 0 -> 3
|
||||
let parents = [None, Some(0), Some(1), Some(0)];
|
||||
assert_eq!(descendants(&parents, 1), [false, true, true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_chains_are_followed_to_the_end() {
|
||||
let parents = [None, Some(0), Some(1), Some(2), Some(3), Some(4)];
|
||||
assert_eq!(descendants(&parents, 0), [true; 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chains_listed_out_of_order_are_still_found() {
|
||||
// 3 -> 2 -> 1 -> 0 (children appear before their parents)
|
||||
let parents = [Some(1), Some(2), Some(3), None];
|
||||
assert_eq!(descendants(&parents, 3), [true, true, true, true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_out_of_range_root_marks_nothing() {
|
||||
let parents = [None, Some(0)];
|
||||
assert_eq!(descendants(&parents, 7), [false, false]);
|
||||
}
|
||||
@@ -1,34 +1,83 @@
|
||||
use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet};
|
||||
use types::{
|
||||
badge::Badge, bounded::CapSlot, generation::Generation, id::ProcessId,
|
||||
permission::PermissionSet,
|
||||
};
|
||||
|
||||
pub mod authority;
|
||||
pub mod derivation;
|
||||
pub mod tree;
|
||||
|
||||
use crate::{error::KernelError, resource::Resource};
|
||||
#[cfg(test)]
|
||||
#[path = "derivation_tests.rs"]
|
||||
mod derivation_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tree_tests.rs"]
|
||||
mod tree_tests;
|
||||
|
||||
use crate::{error::KernelError, generations::SlotGenerations, resource::Resource};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
mod tests;
|
||||
|
||||
const MAX_CAPS_PER_PROCESS: usize = 16;
|
||||
// Sized so that every representable `CapSlot` indexes a real slot.
|
||||
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
||||
|
||||
/// One capability slot at one point in time; both generations must match to resolve.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CapRef {
|
||||
pub process: ProcessId,
|
||||
pub process_generation: Generation,
|
||||
pub slot: CapSlot,
|
||||
pub slot_generation: Generation,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Capability {
|
||||
resource: Resource,
|
||||
permissions: PermissionSet,
|
||||
generation: Generation,
|
||||
badge: Badge,
|
||||
/// The capability this one was derived from (Mint, Delegate, IPC
|
||||
/// transfer); `None` for capabilities the kernel created directly.
|
||||
parent: Option<CapRef>,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
pub fn new(
|
||||
resource: Resource,
|
||||
permissions: PermissionSet,
|
||||
generation: Generation,
|
||||
) -> Self {
|
||||
pub fn new(resource: Resource, permissions: PermissionSet, generation: Generation) -> Self {
|
||||
Self {
|
||||
resource,
|
||||
permissions,
|
||||
generation,
|
||||
badge: Badge::NONE,
|
||||
parent: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_parent(mut self, parent: Option<CapRef>) -> Self {
|
||||
self.parent = parent;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<CapRef> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
pub fn set_parent(&mut self, parent: Option<CapRef>) {
|
||||
self.parent = parent;
|
||||
}
|
||||
|
||||
/// The same capability with `badge` stamped on it.
|
||||
pub fn with_badge(mut self, badge: Badge) -> Self {
|
||||
self.badge = badge;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn badge(&self) -> Badge {
|
||||
self.badge
|
||||
}
|
||||
|
||||
pub fn resource(&self) -> &Resource {
|
||||
&self.resource
|
||||
}
|
||||
@@ -44,39 +93,71 @@ impl Capability {
|
||||
|
||||
pub struct CapabilityTable {
|
||||
slots: [Option<Capability>; MAX_CAPS_PER_PROCESS],
|
||||
generations: SlotGenerations<MAX_CAPS_PER_PROCESS>,
|
||||
}
|
||||
|
||||
impl CapabilityTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
slots: [const { None }; MAX_CAPS_PER_PROCESS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
|
||||
let idx = slot.value() as usize;
|
||||
self.slots[idx]
|
||||
.as_ref()
|
||||
fn slot(&self, slot: CapSlot) -> Option<&Capability> {
|
||||
self.slots.get(slot.value() as usize)?.as_ref()
|
||||
}
|
||||
|
||||
/// Generation of an occupied slot.
|
||||
pub fn slot_generation(&self, slot: CapSlot) -> Option<Generation> {
|
||||
self.slot(slot)?;
|
||||
self.generations.current(slot.value() as usize)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, slot: CapSlot) -> Result<&mut Capability, KernelError> {
|
||||
self.slots
|
||||
.get_mut(slot.value() as usize)
|
||||
.and_then(Option::as_mut)
|
||||
.ok_or(KernelError::InvalidSlot(slot))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
|
||||
let idx = self
|
||||
.slots
|
||||
.iter()
|
||||
.position(|s| s.is_none())
|
||||
.ok_or(KernelError::NoFreeSlot)?;
|
||||
pub fn iter(&self) -> impl Iterator<Item = (CapSlot, &Capability)> {
|
||||
self.slots.iter().enumerate().filter_map(|(i, c)| {
|
||||
let cap = c.as_ref()?;
|
||||
Some((CapSlot::new(i as u8)?, cap))
|
||||
})
|
||||
}
|
||||
|
||||
self.slots[idx] = Some(cap);
|
||||
CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)
|
||||
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
|
||||
self.slot(slot).ok_or(KernelError::InvalidSlot(slot))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
|
||||
let generations = &self.generations;
|
||||
let (idx, free) = self
|
||||
.slots
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(i, s)| s.is_none() && generations.is_usable(*i))
|
||||
.ok_or(KernelError::NoFreeSlot)?;
|
||||
let slot = CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)?;
|
||||
*free = Some(cap);
|
||||
Ok(slot)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> {
|
||||
let idx = slot.value() as usize;
|
||||
if self.slots[idx].is_none() {
|
||||
let taken = self.slots.get_mut(idx).and_then(Option::take);
|
||||
if taken.is_none() {
|
||||
return Err(KernelError::InvalidSlot(slot));
|
||||
}
|
||||
self.slots[idx] = None;
|
||||
self.generations.advance(idx);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CapabilityTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ fn full_table_returns_no_free_slot() {
|
||||
#[test]
|
||||
fn generation_mismatch_detected() {
|
||||
let gen_v1 = Generation::initial();
|
||||
let gen_v2 = gen_v1.next();
|
||||
let gen_v2 = gen_v1.next().unwrap();
|
||||
|
||||
assert!(gen_v1.is_valid_against(&gen_v1));
|
||||
assert!(!gen_v1.is_valid_against(&gen_v2));
|
||||
@@ -109,7 +109,6 @@ fn restrict_to_cannot_add_permissions() {
|
||||
Permission::Write,
|
||||
Permission::Execute,
|
||||
Permission::Delegate,
|
||||
Permission::Revoke,
|
||||
]);
|
||||
|
||||
let result = read_only.restrict_to(&wants_everything);
|
||||
@@ -118,7 +117,6 @@ fn restrict_to_cannot_add_permissions() {
|
||||
assert!(!result.allows(Permission::Write));
|
||||
assert!(!result.allows(Permission::Execute));
|
||||
assert!(!result.allows(Permission::Delegate));
|
||||
assert!(!result.allows(Permission::Revoke));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -138,7 +136,6 @@ fn empty_permission_set_allows_nothing() {
|
||||
assert!(!empty.allows(Permission::Write));
|
||||
assert!(!empty.allows(Permission::Execute));
|
||||
assert!(!empty.allows(Permission::Delegate));
|
||||
assert!(!empty.allows(Permission::Revoke));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -150,3 +147,45 @@ fn capability_preserves_resource_and_permissions() {
|
||||
assert_eq!(cap.permissions(), &expected_permissions);
|
||||
assert_eq!(cap.generation(), &Generation::initial());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_a_cap_changes_its_slot_generation() {
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||
let before = table.slot_generation(slot).unwrap();
|
||||
table.remove(slot).unwrap();
|
||||
assert_eq!(
|
||||
table.slot_generation(slot),
|
||||
None,
|
||||
"empty slots have no generation"
|
||||
);
|
||||
let again = table.insert(endpoint_cap(2, &[Permission::Read])).unwrap();
|
||||
assert_eq!(again, slot);
|
||||
assert!(!before.is_valid_against(&table.slot_generation(again).unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_lists_occupied_slots() {
|
||||
let mut table = CapabilityTable::new();
|
||||
let a = table.insert(endpoint_cap(1, &[])).unwrap();
|
||||
let b = table.insert(endpoint_cap(2, &[])).unwrap();
|
||||
table.remove(a).unwrap();
|
||||
let slots: [Option<types::bounded::CapSlot>; 2] = {
|
||||
let mut it = table.iter().map(|(s, _)| s);
|
||||
[it.next(), it.next()]
|
||||
};
|
||||
assert_eq!(slots, [Some(b), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caps_record_their_parent() {
|
||||
let parent = CapRef {
|
||||
process: types::id::ProcessId::from_raw(3),
|
||||
process_generation: Generation::initial(),
|
||||
slot: types::bounded::CapSlot::new(4).unwrap(),
|
||||
slot_generation: Generation::initial(),
|
||||
};
|
||||
let cap = endpoint_cap(1, &[]).with_parent(Some(parent));
|
||||
assert_eq!(cap.parent(), Some(parent));
|
||||
assert_eq!(endpoint_cap(1, &[]).parent(), None);
|
||||
}
|
||||
|
||||
216
crates/domain/src/capability/tree.rs
Normal file
216
crates/domain/src/capability/tree.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
//! Derivation tree with explicit links: delete costs its children, revoke its subtree.
|
||||
//! Nodes are `0..N`; `derivation::descendants` is the reference model it is checked against.
|
||||
|
||||
/// Longest walk any operation may take: every node visited once, plus the
|
||||
/// climb back up. Corrupted links can therefore never hang the kernel.
|
||||
const fn budget(n: usize) -> usize {
|
||||
2 * n + 1
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
struct Node {
|
||||
live: bool,
|
||||
parent: Option<u16>,
|
||||
first_child: Option<u16>,
|
||||
prev: 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> {
|
||||
nodes: [Node; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for CapTree<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn id(i: usize) -> Option<u16> {
|
||||
u16::try_from(i).ok()
|
||||
}
|
||||
|
||||
impl<const N: usize> CapTree<N> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
nodes: [Node::EMPTY; N],
|
||||
}
|
||||
}
|
||||
|
||||
fn node(&self, i: usize) -> Option<&Node> {
|
||||
self.nodes.get(i).filter(|n| n.live)
|
||||
}
|
||||
|
||||
fn node_mut(&mut self, i: usize) -> Option<&mut Node> {
|
||||
self.nodes.get_mut(i).filter(|n| n.live)
|
||||
}
|
||||
|
||||
pub fn is_live(&self, i: usize) -> bool {
|
||||
self.node(i).is_some()
|
||||
}
|
||||
|
||||
pub fn parent(&self, i: usize) -> Option<usize> {
|
||||
self.node(i)?.parent.map(usize::from)
|
||||
}
|
||||
|
||||
/// Adds `i` under `parent` (or as a root); fails without change if `i` is live or out of range, or `parent` is not live.
|
||||
pub fn attach(&mut self, i: usize, parent: Option<usize>) -> bool {
|
||||
let (Some(me), Some(node)) = (id(i), self.nodes.get(i)) else {
|
||||
return false;
|
||||
};
|
||||
if node.live || parent.is_some_and(|p| p == i || !self.is_live(p)) {
|
||||
return false;
|
||||
}
|
||||
let next = parent
|
||||
.and_then(|p| self.node(p))
|
||||
.and_then(|p| p.first_child);
|
||||
if let Some(n) = next.and_then(|n| self.node_mut(n.into())) {
|
||||
n.prev = Some(me);
|
||||
}
|
||||
if let Some(p) = parent.and_then(|p| self.node_mut(p)) {
|
||||
p.first_child = Some(me);
|
||||
}
|
||||
if let Some(node) = self.nodes.get_mut(i) {
|
||||
*node = Node {
|
||||
live: true,
|
||||
parent: parent.and_then(id),
|
||||
first_child: None,
|
||||
prev: None,
|
||||
next,
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Unlinks `i` from its parent's child list, leaving its own children.
|
||||
fn unlink(&mut self, i: usize) {
|
||||
let Some(node) = self.node(i).copied() else {
|
||||
return;
|
||||
};
|
||||
match node.prev {
|
||||
Some(p) => {
|
||||
if let Some(p) = self.node_mut(p.into()) {
|
||||
p.next = node.next;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Some(p) = node.parent.and_then(|p| self.node_mut(p.into())) {
|
||||
p.first_child = node.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(n) = node.next.and_then(|n| self.node_mut(n.into())) {
|
||||
n.prev = node.prev;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes `i`; its children are adopted by its parent (or become
|
||||
/// roots), so no descendant escapes a revocation further up.
|
||||
pub fn detach(&mut self, i: usize) {
|
||||
let Some(node) = self.node(i).copied() else {
|
||||
return;
|
||||
};
|
||||
self.unlink(i);
|
||||
let mut child = node.first_child;
|
||||
for _ in 0..budget(N) {
|
||||
let Some(c) = child else { break };
|
||||
let next = self.node(c.into()).and_then(|n| n.next);
|
||||
if let Some(n) = self.node_mut(c.into()) {
|
||||
n.parent = None;
|
||||
n.prev = None;
|
||||
n.next = None;
|
||||
}
|
||||
// Re-attach under the grandparent; `attach` needs a dead node.
|
||||
if let Some(n) = self.nodes.get_mut(usize::from(c)) {
|
||||
n.live = false;
|
||||
let kids = n.first_child;
|
||||
if self.attach(c.into(), node.parent.map(usize::from))
|
||||
&& let Some(n) = self.nodes.get_mut(usize::from(c))
|
||||
{
|
||||
n.first_child = kids;
|
||||
}
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
if let Some(n) = self.nodes.get_mut(i) {
|
||||
*n = Node::default();
|
||||
}
|
||||
}
|
||||
|
||||
/// The direct children of `i`, written into `out`; returns how many.
|
||||
pub fn children(&self, i: usize, out: &mut [u16; N]) -> usize {
|
||||
let mut count = 0;
|
||||
let mut cur = self.node(i).and_then(|n| n.first_child);
|
||||
for _ in 0..budget(N) {
|
||||
let Some(c) = cur else { break };
|
||||
if let Some(slot) = out.get_mut(count) {
|
||||
*slot = c;
|
||||
count += 1;
|
||||
}
|
||||
cur = self.node(c.into()).and_then(|n| n.next);
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// Every node strictly below `root`, written into `out` in pre-order;
|
||||
/// returns how many.
|
||||
pub fn descendants(&self, root: usize, out: &mut [u16; N]) -> usize {
|
||||
let mut count = 0;
|
||||
let Some(start) = self.node(root) else {
|
||||
return 0;
|
||||
};
|
||||
let mut cur = start.first_child;
|
||||
for _ in 0..budget(N) {
|
||||
let Some(c) = cur else { break };
|
||||
if let Some(slot) = out.get_mut(count) {
|
||||
*slot = c;
|
||||
count += 1;
|
||||
}
|
||||
cur = self.next_in_subtree(root, c.into());
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// The node after `cur` in a pre-order walk of `root`'s subtree.
|
||||
fn next_in_subtree(&self, root: usize, cur: usize) -> Option<u16> {
|
||||
if let Some(child) = self.node(cur)?.first_child {
|
||||
return Some(child);
|
||||
}
|
||||
let mut at = cur;
|
||||
for _ in 0..budget(N) {
|
||||
if at == root {
|
||||
return None;
|
||||
}
|
||||
let node = self.node(at)?;
|
||||
if let Some(next) = node.next {
|
||||
return Some(next);
|
||||
}
|
||||
at = node.parent?.into();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Removes every node strictly below `root`, keeping `root`.
|
||||
pub fn remove_descendants(&mut self, root: usize) {
|
||||
let mut doomed = [0u16; N];
|
||||
let n = self.descendants(root, &mut doomed);
|
||||
for d in doomed.iter().take(n) {
|
||||
if let Some(node) = self.nodes.get_mut(usize::from(*d)) {
|
||||
*node = Node::default();
|
||||
}
|
||||
}
|
||||
if let Some(r) = self.node_mut(root) {
|
||||
r.first_child = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
120
crates/domain/src/capability/tree_tests.rs
Normal file
120
crates/domain/src/capability/tree_tests.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use super::derivation::descendants;
|
||||
use super::tree::CapTree;
|
||||
|
||||
const N: usize = 12;
|
||||
|
||||
/// The tree as a plain parent array, updated by the rules the tree must
|
||||
/// follow; `descendants` from derivation.rs answers subtree questions.
|
||||
struct Model {
|
||||
live: [bool; N],
|
||||
parents: [Option<u16>; N],
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn detach(&mut self, i: usize) {
|
||||
let grandparent = self.parents[i];
|
||||
for p in self.parents.iter_mut() {
|
||||
if *p == Some(i as u16) {
|
||||
*p = grandparent;
|
||||
}
|
||||
}
|
||||
self.live[i] = false;
|
||||
self.parents[i] = None;
|
||||
}
|
||||
|
||||
fn below(&self, root: usize) -> [bool; N] {
|
||||
let mut d = descendants(&self.parents, root);
|
||||
d[root] = false;
|
||||
d
|
||||
}
|
||||
}
|
||||
|
||||
struct Rng(u64);
|
||||
impl Rng {
|
||||
fn below(&mut self, n: usize) -> usize {
|
||||
self.0 ^= self.0 << 13;
|
||||
self.0 ^= self.0 >> 7;
|
||||
self.0 ^= self.0 << 17;
|
||||
(self.0 % n as u64) as usize
|
||||
}
|
||||
}
|
||||
|
||||
fn check(t: &CapTree<N>, m: &Model) {
|
||||
for i in 0..N {
|
||||
assert_eq!(t.is_live(i), m.live[i], "liveness of {i}");
|
||||
assert_eq!(t.parent(i), m.parents[i].map(usize::from), "parent of {i}");
|
||||
if !m.live[i] {
|
||||
continue;
|
||||
}
|
||||
let mut out = [0u16; N];
|
||||
let n = t.descendants(i, &mut out);
|
||||
let mut got = [false; N];
|
||||
for d in &out[..n] {
|
||||
assert!(!got[*d as usize], "{d} listed twice under {i}");
|
||||
got[*d as usize] = true;
|
||||
}
|
||||
assert_eq!(got, m.below(i), "subtree of {i}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_the_parent_array_model_under_random_operations() {
|
||||
for seed in 1..200u64 {
|
||||
let mut rng = Rng(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15));
|
||||
let mut t = CapTree::<N>::new();
|
||||
let mut m = Model {
|
||||
live: [false; N],
|
||||
parents: [None; N],
|
||||
};
|
||||
for _ in 0..300 {
|
||||
let i = rng.below(N);
|
||||
match rng.below(4) {
|
||||
0 | 1 => {
|
||||
let parent = match rng.below(3) {
|
||||
0 => None,
|
||||
_ => Some(rng.below(N)),
|
||||
};
|
||||
let ok = !m.live[i] && parent.is_none_or(|p| p != i && m.live[p]);
|
||||
assert_eq!(t.attach(i, parent), ok, "attach {i} under {parent:?}");
|
||||
if ok {
|
||||
m.live[i] = true;
|
||||
m.parents[i] = parent.map(|p| p as u16);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
t.detach(i);
|
||||
if m.live[i] {
|
||||
m.detach(i);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
t.remove_descendants(i);
|
||||
if m.live[i] {
|
||||
let below = m.below(i);
|
||||
for (j, b) in below.iter().enumerate() {
|
||||
if *b {
|
||||
m.live[j] = false;
|
||||
m.parents[j] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
check(&t, &m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detaching_a_middle_node_hands_its_children_up() {
|
||||
let mut t = CapTree::<4>::new();
|
||||
assert!(t.attach(0, None));
|
||||
assert!(t.attach(1, Some(0)));
|
||||
assert!(t.attach(2, Some(1)));
|
||||
assert!(t.attach(3, Some(1)));
|
||||
t.detach(1);
|
||||
assert_eq!(t.parent(2), Some(0));
|
||||
assert_eq!(t.parent(3), Some(0));
|
||||
let mut out = [0u16; 4];
|
||||
assert_eq!(t.descendants(0, &mut out), 2);
|
||||
}
|
||||
@@ -4,7 +4,10 @@ use types::{
|
||||
id::{EndpointId, ProcessId, ThreadId},
|
||||
};
|
||||
|
||||
use crate::{endpoint::wait_queue::WaitQueue, error::KernelError, message::Message};
|
||||
use crate::{
|
||||
endpoint::wait_queue::WaitQueue, error::KernelError, generations::SlotGenerations,
|
||||
message::Message,
|
||||
};
|
||||
|
||||
pub mod wait_queue;
|
||||
|
||||
@@ -14,18 +17,19 @@ mod tests;
|
||||
|
||||
pub struct Endpoint {
|
||||
id: EndpointId,
|
||||
/// The process that created it; the endpoint dies with its owner.
|
||||
owner: ProcessId,
|
||||
waiting_senders: WaitQueue,
|
||||
waiting_receivers: WaitQueue,
|
||||
generation: Generation,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub fn new(id: EndpointId) -> Self {
|
||||
pub fn new(id: EndpointId, owner: ProcessId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
owner,
|
||||
waiting_senders: WaitQueue::new(),
|
||||
waiting_receivers: WaitQueue::new(),
|
||||
generation: Generation::initial(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +37,8 @@ impl Endpoint {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> &Generation {
|
||||
&self.generation
|
||||
pub fn owner(&self) -> ProcessId {
|
||||
self.owner
|
||||
}
|
||||
|
||||
pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> {
|
||||
@@ -53,6 +57,22 @@ impl Endpoint {
|
||||
self.waiting_receivers.pop()
|
||||
}
|
||||
|
||||
pub fn senders(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||
self.waiting_senders.iter()
|
||||
}
|
||||
|
||||
pub fn receivers(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||
self.waiting_receivers.iter()
|
||||
}
|
||||
|
||||
pub fn is_sender(&self, thread: ThreadId) -> bool {
|
||||
self.waiting_senders.contains(thread)
|
||||
}
|
||||
|
||||
pub fn is_receiver(&self, thread: ThreadId) -> bool {
|
||||
self.waiting_receivers.contains(thread)
|
||||
}
|
||||
|
||||
pub fn has_waiting_sender(&self) -> bool {
|
||||
!self.waiting_senders.is_empty()
|
||||
}
|
||||
@@ -68,51 +88,77 @@ impl Endpoint {
|
||||
pub fn remove_receiver(&mut self, thread: ThreadId) -> bool {
|
||||
self.waiting_receivers.remove_thread(thread)
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self) {
|
||||
self.generation = self.generation.next();
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_ENDPOINTS: usize = 256;
|
||||
pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 };
|
||||
|
||||
pub struct EndpointTable {
|
||||
endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
|
||||
endpoints: [crate::slot::Slot<Endpoint>; MAX_ENDPOINTS],
|
||||
generations: SlotGenerations<MAX_ENDPOINTS>,
|
||||
}
|
||||
|
||||
impl EndpointTable {
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
endpoints: [const { None }; MAX_ENDPOINTS],
|
||||
endpoints: [const { crate::slot::Slot::Empty }; MAX_ENDPOINTS],
|
||||
generations: SlotGenerations::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&mut self) -> Option<EndpointId> {
|
||||
let idx = self.endpoints.iter().position(|s| s.is_none())?;
|
||||
pub fn create(&mut self, owner: ProcessId) -> Option<EndpointId> {
|
||||
let generations = &self.generations;
|
||||
let (idx, slot) = self
|
||||
.endpoints
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(i, s)| !s.is_full() && generations.is_usable(*i))?;
|
||||
let id = EndpointId::from_raw(idx as u16);
|
||||
self.endpoints[idx] = Some(Endpoint::new(id));
|
||||
slot.set(Endpoint::new(id, owner));
|
||||
Some(id)
|
||||
}
|
||||
|
||||
/// Generation capabilities to a live endpoint must carry.
|
||||
pub fn generation(&self, id: EndpointId) -> Option<Generation> {
|
||||
self.get(id)?;
|
||||
self.generations.current(id.raw() as usize)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Endpoint> {
|
||||
self.endpoints.iter().filter_map(crate::slot::Slot::as_ref)
|
||||
}
|
||||
|
||||
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
|
||||
self.endpoints
|
||||
.iter()
|
||||
.filter_map(crate::slot::Slot::as_ref)
|
||||
.filter(move |ep| ep.owner == owner)
|
||||
.map(|ep| ep.id)
|
||||
}
|
||||
|
||||
pub fn get(&self, id: EndpointId) -> Option<&Endpoint> {
|
||||
self.endpoints.get(id.raw() as usize).and_then(|s| s.as_ref())
|
||||
self.endpoints
|
||||
.get(id.raw() as usize)
|
||||
.and_then(|s| s.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: EndpointId) -> Option<&mut Endpoint> {
|
||||
self.endpoints.get_mut(id.raw() as usize).and_then(|s| s.as_mut())
|
||||
self.endpoints
|
||||
.get_mut(id.raw() as usize)
|
||||
.and_then(|s| s.as_mut())
|
||||
}
|
||||
|
||||
/// Removes the endpoint; capabilities to it become invalid even if the
|
||||
/// slot is later reused.
|
||||
pub fn destroy(&mut self, id: EndpointId) -> Option<Endpoint> {
|
||||
self.endpoints.get_mut(id.raw() as usize).and_then(|s| s.take())
|
||||
let ep = self.endpoints.get_mut(id.raw() as usize)?.take()?;
|
||||
self.generations.advance(id.raw() as usize);
|
||||
Some(ep)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
||||
for slot in &mut self.endpoints {
|
||||
if let Some(ep) = slot {
|
||||
ep.remove_sender(thread);
|
||||
ep.remove_receiver(thread);
|
||||
}
|
||||
}
|
||||
impl Default for EndpointTable {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
use crate::endpoint::wait_queue::MAX_WAITERS;
|
||||
|
||||
use super::*;
|
||||
use types::id::{EndpointId, ThreadId};
|
||||
use types::id::{EndpointId, ProcessId, ThreadId};
|
||||
|
||||
fn thread(id: u16) -> ThreadId {
|
||||
ThreadId::from_raw(id)
|
||||
}
|
||||
|
||||
fn owner() -> ProcessId {
|
||||
ProcessId::from_raw(0)
|
||||
}
|
||||
|
||||
fn test_endpoint() -> Endpoint {
|
||||
Endpoint::new(EndpointId::from_raw(1))
|
||||
Endpoint::new(EndpointId::from_raw(1), owner())
|
||||
}
|
||||
|
||||
// --- WaitQueue tests ---
|
||||
|
||||
#[test]
|
||||
fn empty_queue_pop_returns_none() {
|
||||
let mut queue = WaitQueue::new();
|
||||
let mut queue: WaitQueue = WaitQueue::new();
|
||||
assert!(queue.pop().is_none());
|
||||
assert!(queue.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_then_pop_returns_same_thread() {
|
||||
let mut queue = WaitQueue::new();
|
||||
let mut queue: WaitQueue = WaitQueue::new();
|
||||
queue.push(thread(1)).unwrap();
|
||||
assert_eq!(queue.pop(), Some(thread(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop_order_is_fifo() {
|
||||
let mut queue = WaitQueue::new();
|
||||
let mut queue: WaitQueue = WaitQueue::new();
|
||||
queue.push(thread(1)).unwrap();
|
||||
queue.push(thread(2)).unwrap();
|
||||
queue.push(thread(3)).unwrap();
|
||||
@@ -42,7 +46,7 @@ fn pop_order_is_fifo() {
|
||||
|
||||
#[test]
|
||||
fn full_queue_rejects_push() {
|
||||
let mut queue = WaitQueue::new();
|
||||
let mut queue: WaitQueue = WaitQueue::new();
|
||||
for i in 0..MAX_WAITERS {
|
||||
assert!(queue.push(thread(i as u16)).is_some());
|
||||
}
|
||||
@@ -51,7 +55,7 @@ fn full_queue_rejects_push() {
|
||||
|
||||
#[test]
|
||||
fn is_empty_after_drain() {
|
||||
let mut queue = WaitQueue::new();
|
||||
let mut queue: WaitQueue = WaitQueue::new();
|
||||
queue.push(thread(1)).unwrap();
|
||||
queue.push(thread(2)).unwrap();
|
||||
queue.pop();
|
||||
@@ -99,13 +103,34 @@ fn senders_and_receivers_are_independent() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_bumps_generation() {
|
||||
let mut ep = test_endpoint();
|
||||
let gen_before = *ep.generation();
|
||||
ep.revoke();
|
||||
let gen_after = *ep.generation();
|
||||
fn reused_slot_gets_a_new_generation() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create(owner()).unwrap();
|
||||
let old = table.generation(id).unwrap();
|
||||
table.destroy(id);
|
||||
assert_eq!(
|
||||
table.generation(id),
|
||||
None,
|
||||
"dead endpoints have no generation"
|
||||
);
|
||||
let again = table.create(owner()).unwrap();
|
||||
assert_eq!(again, id);
|
||||
assert!(!old.is_valid_against(&table.generation(again).unwrap()));
|
||||
}
|
||||
|
||||
assert!(!gen_before.is_valid_against(&gen_after));
|
||||
#[test]
|
||||
fn ids_owned_by_lists_only_that_owners_endpoints() {
|
||||
let mut table = EndpointTable::new();
|
||||
let a = table.create(owner()).unwrap();
|
||||
table.create(ProcessId::from_raw(1)).unwrap();
|
||||
let c = table.create(owner()).unwrap();
|
||||
let mut ids = [EndpointId::from_raw(u16::MAX); 4];
|
||||
let mut n = 0;
|
||||
for id in table.ids_owned_by(owner()) {
|
||||
ids[n] = id;
|
||||
n += 1;
|
||||
}
|
||||
assert_eq!(&ids[..n], &[a, c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -141,22 +166,22 @@ fn rendezvous_receiver_first() {
|
||||
#[test]
|
||||
fn table_create_and_lookup() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
assert!(table.get(id).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_create_returns_unique_ids() {
|
||||
let mut table = EndpointTable::new();
|
||||
let a = table.create().unwrap();
|
||||
let b = table.create().unwrap();
|
||||
let a = table.create(owner()).unwrap();
|
||||
let b = table.create(owner()).unwrap();
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_destroy_then_get_fails() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
let removed = table.destroy(id);
|
||||
assert!(removed.is_some());
|
||||
assert!(table.get(id).is_none());
|
||||
@@ -165,16 +190,57 @@ fn table_destroy_then_get_fails() {
|
||||
#[test]
|
||||
fn table_get_mut_allows_enqueue() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
table.get_mut(id).unwrap().enqueue_sender(thread(1)).unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
table
|
||||
.get_mut(id)
|
||||
.unwrap()
|
||||
.enqueue_sender(thread(1))
|
||||
.unwrap();
|
||||
assert!(table.get(id).unwrap().has_waiting_sender());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_destroy_reuses_slot() {
|
||||
let mut table = EndpointTable::new();
|
||||
let id = table.create().unwrap();
|
||||
let id = table.create(owner()).unwrap();
|
||||
table.destroy(id);
|
||||
let new_id = table.create().unwrap();
|
||||
let new_id = table.create(owner()).unwrap();
|
||||
assert_eq!(id, new_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_queue_is_fifo_even_after_a_pop_leaves_a_hole() {
|
||||
let mut q: WaitQueue = WaitQueue::new();
|
||||
q.push(thread(1)).unwrap();
|
||||
q.push(thread(2)).unwrap();
|
||||
assert_eq!(q.pop(), Some(thread(1)));
|
||||
q.push(thread(3)).unwrap();
|
||||
assert_eq!(q.pop(), Some(thread(2)), "thread 3 jumped the queue");
|
||||
assert_eq!(q.pop(), Some(thread(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_queue_keeps_order_around_a_removed_thread() {
|
||||
let mut q: WaitQueue = WaitQueue::new();
|
||||
for n in 1..=3 {
|
||||
q.push(thread(n)).unwrap();
|
||||
}
|
||||
assert!(q.remove_thread(thread(2)));
|
||||
q.push(thread(4)).unwrap();
|
||||
assert_eq!(q.pop(), Some(thread(1)));
|
||||
assert_eq!(q.pop(), Some(thread(3)));
|
||||
assert_eq!(q.pop(), Some(thread(4)));
|
||||
assert_eq!(q.pop(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_queue_kani_trace_push_push_remove() {
|
||||
let mut q: WaitQueue<3> = WaitQueue::new();
|
||||
q.push(thread(3)).unwrap();
|
||||
q.push(thread(2)).unwrap();
|
||||
assert!(q.remove_thread(thread(3)));
|
||||
assert!(!q.remove_thread(thread(0)));
|
||||
assert!(!q.remove_thread(thread(3)));
|
||||
assert_eq!(q.pop(), Some(thread(2)));
|
||||
assert_eq!(q.pop(), None);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,139 @@
|
||||
use types::id::ThreadId;
|
||||
|
||||
pub const MAX_WAITERS: usize = 16;
|
||||
pub const MAX_WAITERS: usize = if cfg!(kani) { 2 } else { 16 };
|
||||
|
||||
pub struct WaitQueue {
|
||||
entries: [Option<ThreadId>; MAX_WAITERS],
|
||||
count: u8,
|
||||
/// Bounded FIFO of blocked threads. Order is preserved across pops and
|
||||
/// removals, so no waiter can be overtaken.
|
||||
pub struct WaitQueue<const N: usize = MAX_WAITERS> {
|
||||
entries: [Option<ThreadId>; N],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl WaitQueue {
|
||||
pub fn new() -> Self {
|
||||
impl<const N: usize> WaitQueue<N> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
entries: [None; MAX_WAITERS],
|
||||
count: 0,
|
||||
entries: [None; N],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, thread: ThreadId) -> Option<()> {
|
||||
let slot = self.entries.iter_mut().find(|s| s.is_none())?;
|
||||
*slot = Some(thread);
|
||||
self.count += 1;
|
||||
if self.len == N || self.contains(thread) {
|
||||
return None;
|
||||
}
|
||||
*self.entries.get_mut(self.len)? = Some(thread);
|
||||
self.len += 1;
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<ThreadId> {
|
||||
let slot = self.entries.iter_mut().find(|s| s.is_some())?;
|
||||
let thread = slot.take();
|
||||
self.count -= 1;
|
||||
thread
|
||||
let head = self.entries.first().copied().flatten()?;
|
||||
self.close_gap(0);
|
||||
Some(head)
|
||||
}
|
||||
|
||||
pub fn remove_thread(&mut self, thread: ThreadId) -> bool {
|
||||
for slot in &mut self.entries {
|
||||
if *slot == Some(thread) {
|
||||
*slot = None;
|
||||
self.count -= 1;
|
||||
return true;
|
||||
match self.entries[..self.len]
|
||||
.iter()
|
||||
.position(|e| *e == Some(thread))
|
||||
{
|
||||
Some(i) => {
|
||||
self.close_gap(i);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
false
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||
self.entries.iter().take(self.len).flatten().copied()
|
||||
}
|
||||
|
||||
pub fn contains(&self, thread: ThreadId) -> bool {
|
||||
self.entries
|
||||
.iter()
|
||||
.take(self.len)
|
||||
.any(|e| *e == Some(thread))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.count == 0
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Removes entry `i`, shifting later entries forward to keep order.
|
||||
fn close_gap(&mut self, i: usize) {
|
||||
for j in i..self.len.saturating_sub(1) {
|
||||
let next = self.entries.get(j + 1).copied().flatten();
|
||||
if let Some(e) = self.entries.get_mut(j) {
|
||||
*e = next;
|
||||
}
|
||||
}
|
||||
self.len = self.len.saturating_sub(1);
|
||||
if let Some(last) = self.entries.get_mut(self.len) {
|
||||
*last = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for WaitQueue<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(kani)]
|
||||
mod proofs {
|
||||
use super::*;
|
||||
|
||||
/// Matches a plain arrival-ordered array model: FIFO, no duplicates, nothing lost.
|
||||
#[kani::proof]
|
||||
#[kani::unwind(7)]
|
||||
fn behaves_like_a_fifo_without_duplicates() {
|
||||
const CAP: usize = 3;
|
||||
let mut q: WaitQueue<CAP> = WaitQueue::new();
|
||||
let mut model = [0u16; CAP];
|
||||
let mut len = 0usize;
|
||||
|
||||
for _ in 0..5 {
|
||||
let t: u16 = kani::any::<u16>() % 4;
|
||||
match kani::any::<u8>() % 3 {
|
||||
0 => {
|
||||
let ok = q.push(ThreadId::from_raw(t)).is_some();
|
||||
let dup = model[..len].contains(&t);
|
||||
assert!(ok == (!dup && len < CAP));
|
||||
if ok {
|
||||
model[len] = t;
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
let got = q.pop();
|
||||
if len == 0 {
|
||||
assert!(got.is_none());
|
||||
} else {
|
||||
assert!(got == Some(ThreadId::from_raw(model[0])));
|
||||
for j in 0..len - 1 {
|
||||
model[j] = model[j + 1];
|
||||
}
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let removed = q.remove_thread(ThreadId::from_raw(t));
|
||||
match model[..len].iter().position(|&m| m == t) {
|
||||
Some(i) => {
|
||||
assert!(removed);
|
||||
for j in i..len - 1 {
|
||||
model[j] = model[j + 1];
|
||||
}
|
||||
len -= 1;
|
||||
}
|
||||
None => assert!(!removed),
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(q.len == len);
|
||||
assert!(q.is_empty() == (len == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ use types::{
|
||||
pub enum KernelError {
|
||||
InvalidSlot(CapSlot),
|
||||
RevokedCapability(CapSlot),
|
||||
PermissionDenied { slot: CapSlot, needed: Permission },
|
||||
PermissionDenied {
|
||||
slot: CapSlot,
|
||||
needed: Permission,
|
||||
},
|
||||
NoFreeSlot,
|
||||
InvalidProcess(ProcessId),
|
||||
WouldBlock,
|
||||
@@ -23,4 +26,19 @@ pub enum KernelError {
|
||||
EndpointFull,
|
||||
ProcessCreateDenied,
|
||||
IrqAlreadyOwned(IrqId),
|
||||
/// Address outside the user window, unaligned, or not owned by the caller.
|
||||
InvalidAddress,
|
||||
/// The target virtual range already holds a mapping.
|
||||
AddressInUse,
|
||||
InvalidRegion,
|
||||
/// The region is mapped executable somewhere and the request would map
|
||||
/// it writable, or the other way round (W^X across processes).
|
||||
WritableAndExecutable,
|
||||
/// The object a capability would refer to no longer exists.
|
||||
NoSuchObject,
|
||||
/// The capability names the wrong kind of object for this operation.
|
||||
WrongObject,
|
||||
/// `Reply` with an empty reply slot, or a call whose reply was dropped.
|
||||
NoReply,
|
||||
InvalidArgument,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user