Compare commits
54 Commits
4049ba5007
...
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 |
508
Cargo.lock
generated
508
Cargo.lock
generated
@@ -12,16 +12,61 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"domain",
|
"domain",
|
||||||
"elf",
|
"elf",
|
||||||
"hw-virtio",
|
|
||||||
"kernel",
|
"kernel",
|
||||||
"platform",
|
"platform",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alarm-server"
|
name = "alarm-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "app-image"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"manifest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "audio-capture-server"
|
name = "audio-capture-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -30,10 +75,40 @@ version = "0.1.0"
|
|||||||
name = "audio-playback-server"
|
name = "audio-playback-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bluetooth-server"
|
name = "bluetooth-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calendar-server"
|
name = "calendar-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -42,6 +117,55 @@ version = "0.1.0"
|
|||||||
name = "camera-server"
|
name = "camera-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cat"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20poly1305"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"chacha20",
|
||||||
|
"cipher",
|
||||||
|
"poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-server"
|
name = "clipboard-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -50,6 +174,53 @@ version = "0.1.0"
|
|||||||
name = "contacts-server"
|
name = "contacts-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core_maths"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "counter"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "display-qemu"
|
name = "display-qemu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -67,8 +238,11 @@ name = "display-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"driver-framebuffer-mock",
|
"driver-framebuffer-mock",
|
||||||
|
"libkos",
|
||||||
"port-framebuffer",
|
"port-framebuffer",
|
||||||
|
"raster",
|
||||||
"server-framework",
|
"server-framework",
|
||||||
|
"theme",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -126,6 +300,16 @@ dependencies = [
|
|||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "driver-rng-virtio"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"hw-virtio",
|
||||||
|
"libkos",
|
||||||
|
"port-entropy",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "echo"
|
name = "echo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -138,14 +322,86 @@ dependencies = [
|
|||||||
name = "elf"
|
name = "elf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filecrypt"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"blake2",
|
||||||
|
"chacha20",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flaky"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"server-framework",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fontdue"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e796cb6894e6418daacea84b28dd4f98d3b32c74d5bb452ba1ecfbf5ad8e84ca"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
"ttf-parser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gatekeeper"
|
name = "gatekeeper"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"driver-block-virtio",
|
||||||
|
"driver-rng-virtio",
|
||||||
|
"keystore",
|
||||||
|
"libkos",
|
||||||
|
"port-block-device",
|
||||||
|
"port-entropy",
|
||||||
|
"types",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gps-server"
|
name = "gps-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hello"
|
name = "hello"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -153,10 +409,21 @@ dependencies = [
|
|||||||
"libkos",
|
"libkos",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"k-ui",
|
||||||
|
"libkos",
|
||||||
|
"manifest",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hw-virtio"
|
name = "hw-virtio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -167,9 +434,19 @@ dependencies = [
|
|||||||
"elf",
|
"elf",
|
||||||
"hw-virtio",
|
"hw-virtio",
|
||||||
"libkos",
|
"libkos",
|
||||||
|
"server-framework",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "input-qemu"
|
name = "input-qemu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -193,6 +470,19 @@ dependencies = [
|
|||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "k-ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"fontdue",
|
||||||
|
"libkos",
|
||||||
|
"libm",
|
||||||
|
"port-framebuffer",
|
||||||
|
"theme",
|
||||||
|
"tiny-skia",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel"
|
name = "kernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -206,13 +496,42 @@ dependencies = [
|
|||||||
name = "keychain-server"
|
name = "keychain-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keystore"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
|
"blake2",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.191"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libkos"
|
name = "libkos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"port-framebuffer",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log-server"
|
name = "log-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -222,6 +541,18 @@ dependencies = [
|
|||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ls"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "manifest"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "media-index-server"
|
name = "media-index-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -234,6 +565,12 @@ version = "0.1.0"
|
|||||||
name = "notification-server"
|
name = "notification-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "package-manager"
|
name = "package-manager"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -243,6 +580,7 @@ name = "permission-manager"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libkos",
|
"libkos",
|
||||||
|
"manifest",
|
||||||
"server-framework",
|
"server-framework",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
@@ -254,10 +592,25 @@ dependencies = [
|
|||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "port-block-device"
|
name = "port-block-device"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "port-entropy"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "port-framebuffer"
|
name = "port-framebuffer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -277,6 +630,15 @@ version = "0.1.0"
|
|||||||
name = "print-server"
|
name = "print-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "raster"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"driver-framebuffer-mock",
|
||||||
|
"port-framebuffer",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server-framework"
|
name = "server-framework"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -294,6 +656,33 @@ dependencies = [
|
|||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "settings"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"k-ui",
|
||||||
|
"libkos",
|
||||||
|
"manifest",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shell"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"manifest",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sleep"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smptest"
|
name = "smptest"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -306,9 +695,18 @@ dependencies = [
|
|||||||
name = "snake"
|
name = "snake"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"display-server",
|
|
||||||
"libkos",
|
"libkos",
|
||||||
"server-framework",
|
"port-framebuffer",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spawntest"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"flaky",
|
||||||
|
"libkos",
|
||||||
|
"manifest",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -325,24 +723,116 @@ name = "storage-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"driver-block-virtio",
|
"driver-block-virtio",
|
||||||
|
"filecrypt",
|
||||||
"libkos",
|
"libkos",
|
||||||
"port-block-device",
|
"port-block-device",
|
||||||
"server-framework",
|
"server-framework",
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strict-num"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync-server"
|
name = "sync-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tally"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"k-ui",
|
||||||
|
"libkos",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "telephony-server"
|
name = "telephony-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libkos",
|
||||||
|
"port-framebuffer",
|
||||||
|
"raster",
|
||||||
|
"server-framework",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "theme"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
"manifest",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"tiny-skia-path",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia-path"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"bytemuck",
|
||||||
|
"libm",
|
||||||
|
"strict-num",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||||
|
dependencies = [
|
||||||
|
"core_maths",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types"
|
name = "types"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usb-server"
|
name = "usb-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -350,3 +840,15 @@ version = "0.1.0"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "user-manager"
|
name = "user-manager"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||||
|
|||||||
34
Cargo.toml
34
Cargo.toml
@@ -1,12 +1,31 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/adapter-riscv",
|
"crates/adapter-riscv",
|
||||||
|
"crates/raster",
|
||||||
|
"crates/k-ui",
|
||||||
|
"crates/apps/flaky",
|
||||||
|
"crates/apps/tally",
|
||||||
|
"crates/apps/settings",
|
||||||
|
"crates/apps/home",
|
||||||
|
"crates/theme",
|
||||||
"crates/apps/hello",
|
"crates/apps/hello",
|
||||||
|
"crates/apps/shell",
|
||||||
|
"crates/apps/cat",
|
||||||
|
"crates/apps/counter",
|
||||||
|
"crates/apps/ls",
|
||||||
"crates/apps/echo",
|
"crates/apps/echo",
|
||||||
|
"crates/apps/sleep",
|
||||||
"crates/apps/smptest",
|
"crates/apps/smptest",
|
||||||
|
"crates/apps/spawntest",
|
||||||
"crates/apps/snake",
|
"crates/apps/snake",
|
||||||
"crates/domain",
|
"crates/domain",
|
||||||
"crates/elf",
|
"crates/elf",
|
||||||
|
"crates/manifest",
|
||||||
|
"crates/keystore",
|
||||||
|
"crates/filecrypt",
|
||||||
|
"crates/ports/entropy",
|
||||||
|
"crates/drivers/rng/virtio",
|
||||||
|
"crates/tools/app-image",
|
||||||
"crates/init",
|
"crates/init",
|
||||||
"crates/kernel",
|
"crates/kernel",
|
||||||
"crates/platform",
|
"crates/platform",
|
||||||
@@ -34,6 +53,7 @@ members = [
|
|||||||
"crates/servers/power-server",
|
"crates/servers/power-server",
|
||||||
"crates/servers/print-server",
|
"crates/servers/print-server",
|
||||||
"crates/servers/session-server",
|
"crates/servers/session-server",
|
||||||
|
"crates/servers/terminal-server",
|
||||||
"crates/servers/storage-server",
|
"crates/servers/storage-server",
|
||||||
"crates/servers/sync-server",
|
"crates/servers/sync-server",
|
||||||
"crates/servers/telephony-server",
|
"crates/servers/telephony-server",
|
||||||
@@ -62,6 +82,18 @@ resolver = "2"
|
|||||||
types = { path = "crates/types" }
|
types = { path = "crates/types" }
|
||||||
domain = { path = "crates/domain" }
|
domain = { path = "crates/domain" }
|
||||||
elf = { path = "crates/elf" }
|
elf = { path = "crates/elf" }
|
||||||
|
manifest = { path = "crates/manifest" }
|
||||||
|
theme = { path = "crates/theme" }
|
||||||
|
keystore = { path = "crates/keystore" }
|
||||||
|
filecrypt = { path = "crates/filecrypt" }
|
||||||
|
# Audited RustCrypto implementations; never our own.
|
||||||
|
argon2 = { version = "0.5", default-features = false }
|
||||||
|
chacha20poly1305 = { version = "0.10", default-features = false }
|
||||||
|
blake2 = { version = "0.10", default-features = false }
|
||||||
|
zeroize = { version = "1", default-features = false }
|
||||||
|
chacha20 = { version = "0.9", default-features = false }
|
||||||
|
port-entropy = { path = "crates/ports/entropy" }
|
||||||
|
driver-rng-virtio = { path = "crates/drivers/rng/virtio" }
|
||||||
platform = { path = "crates/platform" }
|
platform = { path = "crates/platform" }
|
||||||
kernel = { path = "crates/kernel" }
|
kernel = { path = "crates/kernel" }
|
||||||
server-framework = { path = "crates/server-framework" }
|
server-framework = { path = "crates/server-framework" }
|
||||||
@@ -102,6 +134,8 @@ libkos = { path = "crates/libkos" }
|
|||||||
|
|
||||||
# ports
|
# ports
|
||||||
port-framebuffer = { path = "crates/ports/framebuffer" }
|
port-framebuffer = { path = "crates/ports/framebuffer" }
|
||||||
|
raster = { path = "crates/raster" }
|
||||||
|
k-ui = { path = "crates/k-ui" }
|
||||||
port-block-device = { path = "crates/ports/block-device" }
|
port-block-device = { path = "crates/ports/block-device" }
|
||||||
port-input-device = { path = "crates/ports/input-device" }
|
port-input-device = { path = "crates/ports/input-device" }
|
||||||
|
|
||||||
|
|||||||
44
Makefile
44
Makefile
@@ -28,8 +28,11 @@ check:
|
|||||||
fmt:
|
fmt:
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
# Libraries on the host; programs (no_std, they link only for RISC-V) for
|
||||||
|
# their target, as `build` builds them. The adapter is `kernel-clippy`'s.
|
||||||
clippy:
|
clippy:
|
||||||
cargo clippy --workspace -- -D warnings
|
cargo clippy --workspace --exclude adapter-riscv --lib -- -D warnings
|
||||||
|
cargo clippy --target $(RISCV_TARGET) --release $(PROGRAMS) -- -D warnings
|
||||||
|
|
||||||
test:
|
test:
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
@@ -48,36 +51,53 @@ kani-kernel:
|
|||||||
|
|
||||||
kani: kani-types kani-domain kani-kernel
|
kani: kani-types kani-domain kani-kernel
|
||||||
|
|
||||||
|
PROGRAMS = -p init -p echo -p sleep -p snake \
|
||||||
|
-p display-qemu -p input-qemu -p storage-qemu \
|
||||||
|
-p log-server -p permission-manager -p session-server -p smptest -p spawntest -p hello \
|
||||||
|
-p terminal-server -p shell -p cat -p ls -p counter -p gatekeeper -p flaky -p tally -p settings -p home
|
||||||
|
|
||||||
|
# Programs first: the kernel image packs init and storage (see the
|
||||||
|
# adapter's build.rs).
|
||||||
build:
|
build:
|
||||||
cargo build --target $(RISCV_TARGET) --release \
|
cargo build --target $(RISCV_TARGET) --release $(PROGRAMS)
|
||||||
-p adapter-riscv -p init -p echo -p snake \
|
cargo build --target $(RISCV_TARGET) --release -p adapter-riscv
|
||||||
-p display-qemu -p input-qemu -p storage-qemu \
|
@# The kernel's tables are one static: if every empty entry is zero it
|
||||||
-p log-server -p permission-manager -p session-server -p smptest
|
@# lives in .bss and costs nothing in the image (see domain::slot).
|
||||||
|
@data=$$(llvm-size -A $(KERNEL_BIN) | awk '$$1 == ".data" { print $$2 }'); \
|
||||||
|
if [ "$${data:-0}" -gt 16384 ]; then \
|
||||||
|
echo "kernel .data is $$data bytes: some table's empty value is not all zero bytes (use domain::slot::Slot)"; exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The keystore holds the owner's sealed keys: made once, never rebuilt.
|
||||||
|
KEYS_IMG ?= keys.img
|
||||||
|
QEMU_KEYS = -drive file=$(KEYS_IMG),if=none,format=raw,id=ks -device virtio-blk-device,drive=ks \
|
||||||
|
-device virtio-rng-device
|
||||||
|
|
||||||
image: build
|
image: build
|
||||||
scripts/make-disk-image.sh
|
scripts/make-disk-image.sh
|
||||||
|
[ -f $(KEYS_IMG) ] || truncate -s 64K $(KEYS_IMG)
|
||||||
|
|
||||||
# Boots a test image (normal image + smptest.elf) on $(SMP) harts; init runs
|
# Boots a test image (normal image + bin/smptest) on $(SMP) harts; init runs
|
||||||
# the SMP self-test and halts. Passes only on "SMPTEST PASS".
|
# the SMP self-test and halts. Passes only on "SMPTEST PASS".
|
||||||
test-qemu: build
|
test-qemu: build
|
||||||
IMG=test.img SMPTEST=1 scripts/make-disk-image.sh > /dev/null
|
IMG=test.img SMPTEST=1 scripts/make-disk-image.sh > /dev/null
|
||||||
SMP=$(SMP) scripts/test-qemu.sh test.img
|
SMP=$(SMP) scripts/test-qemu.sh test.img
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
qemu-system-riscv64 -machine virt -smp $(SMP) -nographic -bios default \
|
qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -nographic -bios default \
|
||||||
-kernel $(KERNEL_BIN)
|
-kernel $(KERNEL_BIN)
|
||||||
|
|
||||||
run-disk: image
|
run-disk: image
|
||||||
qemu-system-riscv64 -machine virt -smp $(SMP) -nographic -bios default \
|
qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -nographic -bios default \
|
||||||
-kernel $(KERNEL_BIN) \
|
-kernel $(KERNEL_BIN) \
|
||||||
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
||||||
-device virtio-blk-device,drive=hd0
|
-device virtio-blk-device,drive=hd0 $(QEMU_KEYS)
|
||||||
|
|
||||||
run-gui: image
|
run-gui: image
|
||||||
qemu-system-riscv64 -machine virt -smp $(SMP) -bios default \
|
qemu-system-riscv64 -machine virt -m 512M -smp $(SMP) -bios default \
|
||||||
-kernel $(KERNEL_BIN) \
|
-kernel $(KERNEL_BIN) \
|
||||||
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
||||||
-device virtio-blk-device,drive=hd0 \
|
-device virtio-blk-device,drive=hd0 $(QEMU_KEYS) \
|
||||||
-device virtio-gpu-device \
|
-device virtio-gpu-device \
|
||||||
-device virtio-keyboard-device \
|
-device virtio-keyboard-device -device virtio-tablet-device \
|
||||||
-serial stdio
|
-serial stdio
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ domain = { workspace = true }
|
|||||||
elf = { workspace = true }
|
elf = { workspace = true }
|
||||||
platform = { workspace = true }
|
platform = { workspace = true }
|
||||||
kernel = { workspace = true }
|
kernel = { workspace = true }
|
||||||
hw-virtio = { workspace = true }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ld = std::path::Path::new("linker.ld").canonicalize().unwrap();
|
let ld = std::path::Path::new("linker.ld").canonicalize().unwrap();
|
||||||
println!("cargo:rustc-link-arg=-T{}", ld.display());
|
println!("cargo:rustc-link-arg=-T{}", ld.display());
|
||||||
println!("cargo:rerun-if-changed=linker.ld");
|
println!("cargo:rerun-if-changed=linker.ld");
|
||||||
|
|
||||||
|
// Init and storage are packed into the kernel image: they are what
|
||||||
|
// reads the disk, so they cannot come from it. `make build` builds them
|
||||||
|
// first; without them the image boots to "init missing".
|
||||||
|
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let bins = manifest.join("../../target/riscv64gc-unknown-none-elf/release");
|
||||||
|
let out = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||||
|
for (var, bin) in [
|
||||||
|
("KOS_INIT_ELF", "init"),
|
||||||
|
("KOS_STORAGE_ELF", "storage-qemu"),
|
||||||
|
] {
|
||||||
|
let path = bins.join(bin);
|
||||||
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
|
let path = if path.is_file() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
let empty = out.join(bin);
|
||||||
|
std::fs::write(&empty, []).unwrap();
|
||||||
|
empty
|
||||||
|
};
|
||||||
|
println!("cargo:rustc-env={}={}", var, path.display());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
pub const RAM_BASE: usize = 0x80000000;
|
pub const RAM_BASE: usize = 0x80000000;
|
||||||
pub const RAM_SIZE: usize = 128 * 1024 * 1024;
|
/// What the Makefile and scripts give QEMU (`-m 512M`).
|
||||||
|
pub const RAM_SIZE: usize = 512 * 1024 * 1024;
|
||||||
pub const UART_BASE: usize = 0x10000000;
|
pub const UART_BASE: usize = 0x10000000;
|
||||||
pub const VIRTIO_BASE: usize = 0x10001000;
|
pub const VIRTIO_BASE: usize = 0x10001000;
|
||||||
pub const VIRTIO_SIZE: usize = 0x8000;
|
pub const VIRTIO_SIZE: usize = 0x8000;
|
||||||
pub const PLIC_BASE: usize = 0x0C000000;
|
pub const PLIC_BASE: usize = 0x0C000000;
|
||||||
pub const CLINT_BASE: usize = 0x02000000;
|
pub const CLINT_BASE: usize = 0x02000000;
|
||||||
pub const USER_POOL_BASE: usize = 0x84000000;
|
pub const USER_POOL_BASE: usize = 0x84000000;
|
||||||
pub const USER_POOL_PAGES: usize = 4096;
|
/// The rest of RAM, from `USER_POOL_BASE` (448 MiB).
|
||||||
|
pub const USER_POOL_PAGES: usize = (RAM_BASE + RAM_SIZE - USER_POOL_BASE) / 4096;
|
||||||
pub const TICK_INTERVAL: u64 = 10_000_000;
|
pub const TICK_INTERVAL: u64 = 10_000_000;
|
||||||
|
|
||||||
/// QEMU virt's PLIC lines.
|
/// QEMU virt's PLIC lines.
|
||||||
|
|||||||
@@ -5,22 +5,20 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
|||||||
unsafe { &mut *crate::kernel_ptr() }
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grant_all(
|
/// Init gets storage's process and endpoint (slots 1 and 2), storage its
|
||||||
uart: &Uart,
|
/// endpoint (slot 0); everything else init starts itself.
|
||||||
init_pid: ProcessId,
|
pub fn grant_all(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId) {
|
||||||
storage_pid: ProcessId,
|
|
||||||
display_pid: ProcessId,
|
|
||||||
input_pid: ProcessId,
|
|
||||||
) {
|
|
||||||
let all_perms = types::permission::PermissionSet::empty()
|
let all_perms = types::permission::PermissionSet::empty()
|
||||||
.with(types::permission::Permission::Read)
|
.with(types::permission::Permission::Read)
|
||||||
.with(types::permission::Permission::Write)
|
.with(types::permission::Permission::Write)
|
||||||
.with(types::permission::Permission::Execute)
|
.with(types::permission::Permission::Execute)
|
||||||
.with(types::permission::Permission::Delegate);
|
.with(types::permission::Permission::Delegate);
|
||||||
|
|
||||||
|
// Delegate so storage can mint the badged copies it grants.
|
||||||
let ipc_perms = types::permission::PermissionSet::empty()
|
let ipc_perms = types::permission::PermissionSet::empty()
|
||||||
.with(types::permission::Permission::Read)
|
.with(types::permission::Permission::Read)
|
||||||
.with(types::permission::Permission::Execute);
|
.with(types::permission::Permission::Execute)
|
||||||
|
.with(types::permission::Permission::Delegate);
|
||||||
|
|
||||||
let storage_ep = kern()
|
let storage_ep = kern()
|
||||||
.create_endpoint(storage_pid)
|
.create_endpoint(storage_pid)
|
||||||
@@ -49,74 +47,5 @@ pub fn grant_all(
|
|||||||
)
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
let display_ep = kern()
|
|
||||||
.create_endpoint(display_pid)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "display endpoint failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
display_pid,
|
|
||||||
domain::resource::Resource::Endpoint(display_ep),
|
|
||||||
ipc_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Endpoint(display_ep),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Process(display_pid),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
let input_ep = kern()
|
|
||||||
.create_endpoint(input_pid)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
input_pid,
|
|
||||||
domain::resource::Resource::Endpoint(input_ep),
|
|
||||||
ipc_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Endpoint(input_ep),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Process(input_pid),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
uart.write_str("k-os: capabilities granted\n");
|
uart.write_str("k-os: capabilities granted\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Until the log server owns the console, the servers loaded here print
|
|
||||||
/// through a console cap of their own, its slot passed in `a0`.
|
|
||||||
pub fn grant_console(uart: &Uart, servers: &[super::loader::LoadedProcess]) {
|
|
||||||
let write =
|
|
||||||
types::permission::PermissionSet::empty().with(types::permission::Permission::Write);
|
|
||||||
for server in servers {
|
|
||||||
let slot = kern()
|
|
||||||
.grant_capability(server.pid, domain::resource::Resource::Console, write)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "console grant failed"));
|
|
||||||
if let Some(ctx) = kern().thread_context_mut(server.tid) {
|
|
||||||
ctx.registers_mut()[10] = types::start_arg::encode(Some(slot), None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::hw::virtio_blk::VirtioBlk;
|
use crate::{board, mmu, platform, trap};
|
||||||
use crate::{KERNEL, board, mmu, platform, trap};
|
|
||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
static _bss_start: u8;
|
static _bss_start: u8;
|
||||||
@@ -57,31 +56,15 @@ pub fn init(uart: &Uart, boot_hart: usize) -> &'static mut mmu::PageTable {
|
|||||||
}
|
}
|
||||||
let riscv_platform =
|
let riscv_platform =
|
||||||
platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8);
|
platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8);
|
||||||
// SAFETY: `init_in_place` writes every field of the kernel.
|
crate::kern_mut().install_platform(riscv_platform);
|
||||||
unsafe {
|
crate::kern_mut().install_pool(
|
||||||
KERNEL.init_with(|slot| {
|
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
|
||||||
kernel::Kernel::init_in_place(
|
board::USER_POOL_PAGES,
|
||||||
slot,
|
);
|
||||||
riscv_platform,
|
|
||||||
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
|
|
||||||
board::USER_POOL_PAGES,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
kernel_pt
|
kernel_pt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn probe_block_device(uart: &Uart) -> Option<VirtioBlk> {
|
|
||||||
let blk = VirtioBlk::probe();
|
|
||||||
if blk.is_some() {
|
|
||||||
uart.write_str("k-os: VirtIO block device found\n");
|
|
||||||
} else {
|
|
||||||
uart.write_str("k-os: no block device\n");
|
|
||||||
}
|
|
||||||
blk
|
|
||||||
}
|
|
||||||
|
|
||||||
fn zero_bss() {
|
fn zero_bss() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let start = &_bss_start as *const u8 as *mut u8;
|
let start = &_bss_start as *const u8 as *mut u8;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::hw::virtio_blk::VirtioBlk;
|
use crate::mmu;
|
||||||
use crate::{boot_fs, mmu};
|
|
||||||
use types::address::VirtualAddress;
|
use types::address::VirtualAddress;
|
||||||
use types::budget::Limits;
|
use types::budget::Limits;
|
||||||
use types::id::{ProcessId, ThreadId};
|
use types::id::{ProcessId, ThreadId};
|
||||||
@@ -10,35 +9,20 @@ const PAGE_SIZE: usize = mmu::PAGE_SIZE;
|
|||||||
const USER_VA_BASE: usize = 0x10000;
|
const USER_VA_BASE: usize = 0x10000;
|
||||||
/// The stack sits just below the image and stops short of the null page.
|
/// The stack sits just below the image and stops short of the null page.
|
||||||
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
|
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
|
||||||
const MAX_FILE_SIZE: usize = 64 * 1024;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct LoadedProcess {
|
pub struct LoadedProcess {
|
||||||
pub pid: ProcessId,
|
pub pid: ProcessId,
|
||||||
pub tid: ThreadId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scratch space for reading ELF files, allocated once from the boot heap.
|
/// The two programs boot starts, packed into the kernel image.
|
||||||
fn elf_buffer() -> &'static mut [u8] {
|
pub static INIT_ELF: &[u8] = include_bytes!(env!("KOS_INIT_ELF"));
|
||||||
static mut BUF: usize = 0;
|
pub static STORAGE_ELF: &[u8] = include_bytes!(env!("KOS_STORAGE_ELF"));
|
||||||
// SAFETY: boot is single threaded; the buffer is kernel-only memory
|
|
||||||
// from the boot allocator and is never handed to a process.
|
|
||||||
unsafe {
|
|
||||||
if BUF == 0 {
|
|
||||||
BUF = mmu::allocator::alloc_zeroed_page();
|
|
||||||
for _ in 1..MAX_FILE_SIZE / PAGE_SIZE {
|
|
||||||
mmu::allocator::alloc_zeroed_page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
core::slice::from_raw_parts_mut(BUF as *mut u8, MAX_FILE_SIZE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
|
pub fn load_init(uart: &Uart) -> LoadedProcess {
|
||||||
let (pid, tid) = kern()
|
let (pid, tid) = kern()
|
||||||
.boot(
|
.boot(
|
||||||
types::bounded::Priority::DEFAULT,
|
types::bounded::Priority::DEFAULT,
|
||||||
@@ -46,56 +30,40 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
|
|||||||
)
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
|
||||||
|
|
||||||
let entry = load_elf(uart, blk, pid, b"init.elf");
|
let entry = load_elf(uart, pid, INIT_ELF);
|
||||||
setup_thread(tid, entry);
|
setup_thread(tid, entry);
|
||||||
uart.write_str("k-os: init loaded\n");
|
uart.write_str("k-os: init loaded\n");
|
||||||
|
|
||||||
LoadedProcess { pid, tid }
|
LoadedProcess { pid }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The servers boot loads, their budgets carved from init's.
|
/// Storage's budget, carved from init's.
|
||||||
pub const STORAGE: Limits = Limits {
|
const STORAGE: Limits = {
|
||||||
pages: 256,
|
use types::storage::{BUFFER_PAGES, MAX_CLIENTS};
|
||||||
endpoints: 2,
|
Limits {
|
||||||
notifications: 2,
|
// A buffer per client, and its image, heap and cache.
|
||||||
// A buffer per client (8) and the disk's DMA.
|
pages: MAX_CLIENTS * BUFFER_PAGES + 256,
|
||||||
regions: 12,
|
endpoints: 2,
|
||||||
};
|
notifications: 2,
|
||||||
/// The framebuffer alone is ~1000 pages at 1280x800.
|
// A buffer per client, and the disk's DMA.
|
||||||
pub const DISPLAY: Limits = Limits {
|
regions: MAX_CLIENTS + 4,
|
||||||
pages: 1280,
|
}
|
||||||
endpoints: 2,
|
|
||||||
notifications: 2,
|
|
||||||
regions: 4,
|
|
||||||
};
|
|
||||||
pub const INPUT: Limits = Limits {
|
|
||||||
pages: 64,
|
|
||||||
endpoints: 2,
|
|
||||||
notifications: 2,
|
|
||||||
regions: 4,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn load_server(
|
/// Storage, which init needs to read anything else, its budget carved
|
||||||
uart: &Uart,
|
/// from init's.
|
||||||
blk: &mut VirtioBlk,
|
pub fn load_storage(uart: &Uart, init: ProcessId) -> LoadedProcess {
|
||||||
init: ProcessId,
|
|
||||||
filename: &[u8],
|
|
||||||
label: &str,
|
|
||||||
limits: Limits,
|
|
||||||
) -> LoadedProcess {
|
|
||||||
let pid = kern()
|
let pid = kern()
|
||||||
.create_process_internal(init, limits)
|
.create_process_internal(init, STORAGE)
|
||||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||||
let entry = load_elf(uart, blk, pid, filename);
|
let entry = load_elf(uart, pid, STORAGE_ELF);
|
||||||
let tid = kern()
|
let tid = kern()
|
||||||
.spawn_thread_internal(pid)
|
.spawn_thread_internal(pid)
|
||||||
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
|
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
|
||||||
setup_thread(tid, entry);
|
setup_thread(tid, entry);
|
||||||
uart.write_str("k-os: ");
|
uart.write_str("k-os: storage loaded\n");
|
||||||
uart.write_str(label);
|
|
||||||
uart.write_str(" loaded\n");
|
|
||||||
|
|
||||||
LoadedProcess { pid, tid }
|
LoadedProcess { pid }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_thread(tid: ThreadId, entry: usize) {
|
fn setup_thread(tid: ThreadId, entry: usize) {
|
||||||
@@ -143,13 +111,10 @@ fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
|
|||||||
/// Loads `filename` into `pid` through the kernel's own mapping API, so
|
/// Loads `filename` into `pid` through the kernel's own mapping API, so
|
||||||
/// every page is tracked, charged and checked like any other. Halts on any
|
/// every page is tracked, charged and checked like any other. Halts on any
|
||||||
/// malformed or overlapping image: boot servers are trusted to be sane.
|
/// malformed or overlapping image: boot servers are trusted to be sane.
|
||||||
fn load_elf(uart: &Uart, blk: &mut VirtioBlk, pid: ProcessId, filename: &[u8]) -> usize {
|
fn load_elf(uart: &Uart, pid: ProcessId, file: &[u8]) -> usize {
|
||||||
let buf = elf_buffer();
|
if file.is_empty() {
|
||||||
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0);
|
crate::halt(uart, "program missing from the kernel image (make build)");
|
||||||
if file_size == 0 {
|
|
||||||
crate::halt(uart, "ELF not found");
|
|
||||||
}
|
}
|
||||||
let file = &buf[..file_size];
|
|
||||||
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
|
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
|
||||||
|
|
||||||
for segment in elf_info.segments() {
|
for segment in elf_info.segments() {
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
use crate::hw::virtio_blk::VirtioBlk;
|
|
||||||
|
|
||||||
const SECTOR_SIZE: usize = 512;
|
|
||||||
|
|
||||||
struct BootFat16 {
|
|
||||||
reserved_sectors: u16,
|
|
||||||
root_dir_start: u32,
|
|
||||||
root_dir_sectors: u32,
|
|
||||||
data_start: u32,
|
|
||||||
sectors_per_cluster: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BootFat16 {
|
|
||||||
fn parse(boot_sector: &[u8; SECTOR_SIZE]) -> Option<Self> {
|
|
||||||
if boot_sector[510] != 0x55 || boot_sector[511] != 0xAA {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes_per_sector = u16::from_le_bytes([boot_sector[11], boot_sector[12]]);
|
|
||||||
if bytes_per_sector != SECTOR_SIZE as u16 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sectors_per_cluster = boot_sector[13];
|
|
||||||
let reserved_sectors = u16::from_le_bytes([boot_sector[14], boot_sector[15]]);
|
|
||||||
let num_fats = boot_sector[16];
|
|
||||||
let root_entry_count = u16::from_le_bytes([boot_sector[17], boot_sector[18]]);
|
|
||||||
let sectors_per_fat = u16::from_le_bytes([boot_sector[22], boot_sector[23]]);
|
|
||||||
|
|
||||||
let fat_start = reserved_sectors as u32;
|
|
||||||
let root_dir_start = fat_start + (num_fats as u32) * (sectors_per_fat as u32);
|
|
||||||
let root_dir_sectors = ((root_entry_count as u32) * 32).div_ceil(512);
|
|
||||||
let data_start = root_dir_start + root_dir_sectors;
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
reserved_sectors,
|
|
||||||
root_dir_start,
|
|
||||||
root_dir_sectors,
|
|
||||||
data_start,
|
|
||||||
sectors_per_cluster,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cluster_to_sector(&self, cluster: u16) -> u32 {
|
|
||||||
self.data_start + ((cluster as u32) - 2) * (self.sectors_per_cluster as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_fat_name(name: &[u8]) -> [u8; 11] {
|
|
||||||
let mut fat = [b' '; 11];
|
|
||||||
let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len());
|
|
||||||
for i in 0..dot_pos.min(8) {
|
|
||||||
fat[i] = name[i].to_ascii_uppercase();
|
|
||||||
}
|
|
||||||
if dot_pos < name.len() {
|
|
||||||
for i in 0..(name.len() - dot_pos - 1).min(3) {
|
|
||||||
fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fat
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_file_from_disk(
|
|
||||||
blk: &mut VirtioBlk,
|
|
||||||
filename: &[u8],
|
|
||||||
out_buf: &mut [u8],
|
|
||||||
) -> Option<usize> {
|
|
||||||
let mut sector_buf = [0u8; SECTOR_SIZE];
|
|
||||||
|
|
||||||
if !blk.read_sector(0, &mut sector_buf) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let fs = BootFat16::parse(§or_buf)?;
|
|
||||||
|
|
||||||
let fat_name = to_fat_name(filename);
|
|
||||||
|
|
||||||
let mut first_cluster: u16 = 0;
|
|
||||||
let mut file_size: u32 = 0;
|
|
||||||
let mut found = false;
|
|
||||||
|
|
||||||
for s in 0..fs.root_dir_sectors {
|
|
||||||
if !blk.read_sector(fs.root_dir_start + s, &mut sector_buf) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for i in 0..(SECTOR_SIZE / 32) {
|
|
||||||
let off = i * 32;
|
|
||||||
if sector_buf[off] == 0x00 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if sector_buf[off] == 0xE5 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if sector_buf[off..off + 11] == fat_name {
|
|
||||||
first_cluster = u16::from_le_bytes([sector_buf[off + 26], sector_buf[off + 27]]);
|
|
||||||
file_size = u32::from_le_bytes([
|
|
||||||
sector_buf[off + 28],
|
|
||||||
sector_buf[off + 29],
|
|
||||||
sector_buf[off + 30],
|
|
||||||
sector_buf[off + 31],
|
|
||||||
]);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let size = (file_size as usize).min(out_buf.len());
|
|
||||||
let mut read = 0;
|
|
||||||
let mut cluster = first_cluster;
|
|
||||||
let sectors_per_cluster = fs.sectors_per_cluster as u32;
|
|
||||||
|
|
||||||
while read < size && (2..0xFFF8).contains(&cluster) {
|
|
||||||
for s in 0..sectors_per_cluster {
|
|
||||||
if read >= size {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let sector = fs.cluster_to_sector(cluster) + s;
|
|
||||||
if !blk.read_sector(sector, &mut sector_buf) {
|
|
||||||
return Some(read);
|
|
||||||
}
|
|
||||||
let chunk = (size - read).min(SECTOR_SIZE);
|
|
||||||
out_buf[read..read + chunk].copy_from_slice(§or_buf[..chunk]);
|
|
||||||
read += chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fat_offset = (cluster as u32) * 2;
|
|
||||||
let fat_sector = fs.reserved_sectors as u32 + fat_offset / SECTOR_SIZE as u32;
|
|
||||||
let fat_byte = (fat_offset % SECTOR_SIZE as u32) as usize;
|
|
||||||
if !blk.read_sector(fat_sector, &mut sector_buf) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cluster = u16::from_le_bytes([sector_buf[fat_byte], sector_buf[fat_byte + 1]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(read)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod plic;
|
pub mod plic;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod uart;
|
pub mod uart;
|
||||||
pub mod virtio_blk;
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
use crate::board;
|
use crate::board;
|
||||||
|
use crate::smp::{MAX_HARTS, hart_id};
|
||||||
|
|
||||||
|
/// When each hart's timer is set to go off next.
|
||||||
|
static NEXT: [AtomicU64; MAX_HARTS] = [const { AtomicU64::new(u64::MAX) }; MAX_HARTS];
|
||||||
|
|
||||||
pub fn read_time() -> u64 {
|
pub fn read_time() -> u64 {
|
||||||
let time: u64;
|
let time: u64;
|
||||||
@@ -21,15 +27,27 @@ fn sbi_set_timer(stime: u64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
let now = read_time();
|
set_next(read_time() + board::TICK_INTERVAL);
|
||||||
sbi_set_timer(now + board::TICK_INTERVAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_next(deadline: u64) {
|
pub fn set_next(deadline: u64) {
|
||||||
|
if let Some(next) = NEXT.get(hart_id()) {
|
||||||
|
next.store(deadline, Ordering::Relaxed);
|
||||||
|
}
|
||||||
sbi_set_timer(deadline);
|
sbi_set_timer(deadline);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_interrupt() {
|
/// Goes off at `deadline` too, if that is before the next tick.
|
||||||
let now = read_time();
|
pub fn wake_at(deadline: u64) {
|
||||||
sbi_set_timer(now + board::TICK_INTERVAL);
|
if NEXT
|
||||||
|
.get(hart_id())
|
||||||
|
.is_some_and(|n| deadline < n.load(Ordering::Relaxed))
|
||||||
|
{
|
||||||
|
set_next(deadline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-arms the tick; the kernel then re-arms for its earliest timer.
|
||||||
|
pub fn handle_interrupt() {
|
||||||
|
set_next(read_time() + board::TICK_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
use crate::mmu::{phys_to_virt, virt_to_phys};
|
|
||||||
use hw_virtio::block::{self, DmaAddrs};
|
|
||||||
use hw_virtio::mmio::{self, DirectMmio};
|
|
||||||
use hw_virtio::queue::{QUEUE_SIZE, QueueAddrs, VirtioQueue};
|
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
|
||||||
|
|
||||||
const SECTOR_SIZE: usize = 512;
|
|
||||||
|
|
||||||
#[repr(C, align(4096))]
|
|
||||||
struct VirtqMem {
|
|
||||||
_data: [u8; 8192],
|
|
||||||
}
|
|
||||||
|
|
||||||
static mut VQUEUE: VirtqMem = VirtqMem { _data: [0; 8192] };
|
|
||||||
static mut REQ_HEADER: block::BlockReqHeader = block::BlockReqHeader {
|
|
||||||
req_type: 0,
|
|
||||||
reserved: 0,
|
|
||||||
sector: 0,
|
|
||||||
};
|
|
||||||
static mut REQ_STATUS: u8 = 0xFF;
|
|
||||||
static mut SECTOR_BUF: [u8; SECTOR_SIZE] = [0; SECTOR_SIZE];
|
|
||||||
|
|
||||||
pub struct VirtioBlk {
|
|
||||||
mmio: DirectMmio,
|
|
||||||
queue: VirtioQueue,
|
|
||||||
dma: DmaAddrs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VirtioBlk {
|
|
||||||
pub fn probe() -> Option<Self> {
|
|
||||||
let mmio = mmio::scan_for_device(phys_to_virt(mmio::MMIO_FIRST), mmio::DEVICE_ID_BLOCK)?;
|
|
||||||
|
|
||||||
// The device sees physical addresses; the CPU uses kernel ones.
|
|
||||||
let vq_va = core::ptr::addr_of!(VQUEUE) as usize;
|
|
||||||
mmio::init_legacy_device(&mmio, virt_to_phys(vq_va), QUEUE_SIZE as u32);
|
|
||||||
|
|
||||||
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_va));
|
|
||||||
|
|
||||||
let pair = |va: usize| {
|
|
||||||
(
|
|
||||||
PhysicalAddress::from_raw(virt_to_phys(va)),
|
|
||||||
VirtualAddress::from_raw(va),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let (header_pa, header_va) = pair(core::ptr::addr_of_mut!(REQ_HEADER) as usize);
|
|
||||||
let (status_pa, status_va) = pair(core::ptr::addr_of_mut!(REQ_STATUS) as usize);
|
|
||||||
let (data_pa, data_va) = pair(core::ptr::addr_of_mut!(SECTOR_BUF) as usize);
|
|
||||||
let dma = DmaAddrs {
|
|
||||||
header_pa,
|
|
||||||
header_va,
|
|
||||||
status_pa,
|
|
||||||
status_va,
|
|
||||||
data_pa,
|
|
||||||
data_va,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Self { mmio, queue, dma })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
|
|
||||||
block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
use core::cell::UnsafeCell;
|
use core::cell::UnsafeCell;
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
pub struct KernelCell<T> {
|
pub struct KernelCell<T> {
|
||||||
initialized: AtomicBool,
|
|
||||||
inner: UnsafeCell<MaybeUninit<T>>,
|
inner: UnsafeCell<MaybeUninit<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,25 +9,16 @@ pub struct KernelCell<T> {
|
|||||||
unsafe impl<T> Sync for KernelCell<T> {}
|
unsafe impl<T> Sync for KernelCell<T> {}
|
||||||
|
|
||||||
impl<T> KernelCell<T> {
|
impl<T> KernelCell<T> {
|
||||||
pub const fn uninit() -> Self {
|
/// Holds `value` from the start: built at compile time, so a large `T`
|
||||||
|
/// never lives on the stack.
|
||||||
|
pub const fn new(value: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
initialized: AtomicBool::new(false),
|
inner: UnsafeCell::new(MaybeUninit::new(value)),
|
||||||
inner: UnsafeCell::new(MaybeUninit::uninit()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes in place, so a large `T` never lives on the stack.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// `build` must fully initialize the `T` behind the pointer.
|
|
||||||
pub unsafe fn init_with(&self, build: impl FnOnce(*mut T)) {
|
|
||||||
build(unsafe { (*self.inner.get()).as_mut_ptr() });
|
|
||||||
self.initialized.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raw pointer to the kernel; callers must avoid aliasing `&mut`.
|
/// Raw pointer to the kernel; callers must avoid aliasing `&mut`.
|
||||||
pub fn as_ptr(&self) -> *mut T {
|
pub fn as_ptr(&self) -> *mut T {
|
||||||
debug_assert!(self.initialized.load(Ordering::Acquire));
|
|
||||||
unsafe { (*self.inner.get()).as_mut_ptr() }
|
unsafe { (*self.inner.get()).as_mut_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
mod boot;
|
mod boot;
|
||||||
mod boot_fs;
|
|
||||||
mod devices;
|
mod devices;
|
||||||
mod entry;
|
mod entry;
|
||||||
mod hw;
|
mod hw;
|
||||||
@@ -16,7 +15,16 @@ mod trap;
|
|||||||
use hw::uart::Uart;
|
use hw::uart::Uart;
|
||||||
use kernel_cell::KernelCell;
|
use kernel_cell::KernelCell;
|
||||||
|
|
||||||
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit();
|
/// Built at compile time with nothing in it (no platform, no cores, no
|
||||||
|
/// pool), so every byte is zero and it lives in `.bss`, not in the image;
|
||||||
|
/// boot installs the platform and the pool before anything runs.
|
||||||
|
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> =
|
||||||
|
KernelCell::new(kernel::Kernel::with_cores(
|
||||||
|
platform::RiscVPlatform::new(0, 0),
|
||||||
|
0,
|
||||||
|
types::address::PhysicalAddress::from_raw(0),
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
|
||||||
fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
|
fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
|
||||||
// SAFETY: boot runs with the kernel lock held and before any other
|
// SAFETY: boot runs with the kernel lock held and before any other
|
||||||
@@ -36,36 +44,15 @@ extern "C" fn kmain(hart: usize) -> ! {
|
|||||||
boot::hardware::init(&uart, hart);
|
boot::hardware::init(&uart, hart);
|
||||||
// Not before init: zeroing .bss resets the lock.
|
// Not before init: zeroing .bss resets the lock.
|
||||||
smp::lock();
|
smp::lock();
|
||||||
let mut blk =
|
// Both come from the kernel image; init starts everything else from disk.
|
||||||
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
let init = boot::loader::load_init(&uart);
|
||||||
|
let storage = boot::loader::load_storage(&uart, init.pid);
|
||||||
let init = boot::loader::load_init(&uart, &mut blk);
|
|
||||||
use boot::loader::{DISPLAY, INPUT, STORAGE, load_server};
|
|
||||||
let storage = load_server(
|
|
||||||
&uart,
|
|
||||||
&mut blk,
|
|
||||||
init.pid,
|
|
||||||
b"storage.elf",
|
|
||||||
"storage",
|
|
||||||
STORAGE,
|
|
||||||
);
|
|
||||||
let display = load_server(
|
|
||||||
&uart,
|
|
||||||
&mut blk,
|
|
||||||
init.pid,
|
|
||||||
b"display.elf",
|
|
||||||
"display",
|
|
||||||
DISPLAY,
|
|
||||||
);
|
|
||||||
let input = load_server(&uart, &mut blk, init.pid, b"input.elf", "input", INPUT);
|
|
||||||
|
|
||||||
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||||
kern_mut()
|
kern_mut()
|
||||||
.register_board(init.pid, &board)
|
.register_board(init.pid, &board)
|
||||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
boot::caps::grant_all(&uart, init.pid, storage.pid);
|
||||||
// Last, so the servers' fixed slots stay where they expect them.
|
|
||||||
boot::caps::grant_console(&uart, &[storage, display, input]);
|
|
||||||
// After grant_all: init already relies on those slots.
|
// After grant_all: init already relies on those slots.
|
||||||
let mut placed = [None; board::BOARD_DEVICES];
|
let mut placed = [None; board::BOARD_DEVICES];
|
||||||
let boot_caps = kern_mut()
|
let boot_caps = kern_mut()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use super::pte::{PAGE_SIZE, PageTable, PageTableEntry, Sv39Flags, table_at, vpn};
|
use super::pte::{PAGE_SIZE, PageTable, PageTableEntry, Sv39Flags, table_at, vpn};
|
||||||
use platform::{FrameSource, MapError};
|
use platform::{FrameSource, MapError};
|
||||||
|
|
||||||
/// User mappings live in root slot 0, in a private L1 per address space.
|
/// User mappings live in root slot 0, in a private L1 per address space:
|
||||||
|
/// all of its 1 GiB (leaf tables are made as pages are mapped).
|
||||||
pub const USER_VA_START: usize = 0x1000;
|
pub const USER_VA_START: usize = 0x1000;
|
||||||
pub const USER_VA_END: usize = 0x0200_0000;
|
pub const USER_VA_END: usize = 0x4000_0000;
|
||||||
|
|
||||||
fn fresh_table(frames: &mut dyn FrameSource) -> Result<usize, MapError> {
|
fn fresh_table(frames: &mut dyn FrameSource) -> Result<usize, MapError> {
|
||||||
let pa = frames
|
let pa = frames
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ use types::{
|
|||||||
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||||
id::{CoreId, IrqId, ProcessId},
|
id::{CoreId, IrqId, ProcessId},
|
||||||
mapping::PagePerms,
|
mapping::PagePerms,
|
||||||
time::{Ticks, TimerDuration},
|
time::Ticks,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::board;
|
|
||||||
use crate::hw::{plic::Plic, timer};
|
use crate::hw::{plic::Plic, timer};
|
||||||
use crate::mmu;
|
use crate::mmu;
|
||||||
use crate::smp;
|
use crate::smp;
|
||||||
@@ -21,7 +20,7 @@ pub struct RiscVPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RiscVPlatform {
|
impl RiscVPlatform {
|
||||||
pub fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
|
pub const fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
num_cores,
|
num_cores,
|
||||||
kernel_pt: kernel_pt_addr,
|
kernel_pt: kernel_pt_addr,
|
||||||
@@ -136,9 +135,8 @@ impl Platform for RiscVPlatform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_timer(&mut self, _duration: TimerDuration) {
|
fn wake_at(&mut self, deadline: Ticks) {
|
||||||
let now = timer::read_time();
|
timer::wake_at(deadline.raw());
|
||||||
timer::set_next(now + board::TICK_INTERVAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_ticks(&self) -> Ticks {
|
fn current_ticks(&self) -> Ticks {
|
||||||
|
|||||||
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,5 @@
|
|||||||
mod fast;
|
mod fast;
|
||||||
|
mod fp;
|
||||||
mod sched;
|
mod sched;
|
||||||
mod vector;
|
mod vector;
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
|
|||||||
const CAUSE_S_SOFTWARE: usize = 1;
|
const CAUSE_S_SOFTWARE: usize = 1;
|
||||||
const CAUSE_S_TIMER: usize = 5;
|
const CAUSE_S_TIMER: usize = 5;
|
||||||
const CAUSE_S_EXTERNAL: usize = 9;
|
const CAUSE_S_EXTERNAL: usize = 9;
|
||||||
|
const CAUSE_ILLEGAL_INSTRUCTION: usize = 2;
|
||||||
const CAUSE_ECALL_U: usize = 8;
|
const CAUSE_ECALL_U: usize = 8;
|
||||||
/// Set in `sstatus` when the trap came from supervisor mode.
|
/// Set in `sstatus` when the trap came from supervisor mode.
|
||||||
const SSTATUS_SPP: usize = 1 << 8;
|
const SSTATUS_SPP: usize = 1 << 8;
|
||||||
@@ -29,6 +31,8 @@ pub fn init() {
|
|||||||
let addr = trap_vector as *const () as usize;
|
let addr = trap_vector as *const () as usize;
|
||||||
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
|
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
|
||||||
core::arch::asm!("csrw sscratch, zero");
|
core::arch::asm!("csrw sscratch, zero");
|
||||||
|
// The kernel never uses the FPU; user threads get it lazily.
|
||||||
|
core::arch::asm!("csrc sstatus, {0}", in(reg) 3usize << 13);
|
||||||
// SIE stays clear; interrupts are enabled only by sret into userspace.
|
// SIE stays clear; interrupts are enabled only by sret into userspace.
|
||||||
let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1);
|
let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1);
|
||||||
core::arch::asm!("csrs sie, {0}", in(reg) sie_bits);
|
core::arch::asm!("csrs sie, {0}", in(reg) sie_bits);
|
||||||
@@ -90,6 +94,14 @@ extern "C" fn trap_handler_rust(frame: &mut TrapFrame) {
|
|||||||
}
|
}
|
||||||
crate::smp::service_ipi();
|
crate::smp::service_ipi();
|
||||||
crate::smp::lock();
|
crate::smp::lock();
|
||||||
|
// The thread's FP registers are saved before anything can switch away.
|
||||||
|
if let Some(ctx) = kern()
|
||||||
|
.current_thread()
|
||||||
|
.and_then(|t| kern().thread_context_mut(t))
|
||||||
|
{
|
||||||
|
fp::save_if_dirty(frame, ctx);
|
||||||
|
}
|
||||||
|
fp::kernel_off();
|
||||||
if scause & CAUSE_INTERRUPT_BIT != 0 {
|
if scause & CAUSE_INTERRUPT_BIT != 0 {
|
||||||
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
|
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
|
||||||
} else {
|
} else {
|
||||||
@@ -104,6 +116,7 @@ fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
|
|||||||
CAUSE_S_TIMER => {
|
CAUSE_S_TIMER => {
|
||||||
crate::hw::timer::handle_interrupt();
|
crate::hw::timer::handle_interrupt();
|
||||||
let kern = kern();
|
let kern = kern();
|
||||||
|
kern.fire_timers();
|
||||||
if let Some(current_tid) = kern.current_thread() {
|
if let Some(current_tid) = kern.current_thread() {
|
||||||
let decision = kern.handle_tick(this_core());
|
let decision = kern.handle_tick(this_core());
|
||||||
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {
|
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {
|
||||||
@@ -162,19 +175,36 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CAUSE_ILLEGAL_INSTRUCTION
|
||||||
|
if kern()
|
||||||
|
.current_thread()
|
||||||
|
.and_then(|t| kern().thread_context_mut(t))
|
||||||
|
.is_some_and(|ctx| fp::first_use(frame, ctx)) => {}
|
||||||
_ => {
|
_ => {
|
||||||
let uart = crate::hw::uart::Uart::new();
|
let uart = crate::hw::uart::Uart::new();
|
||||||
uart.write_str("FAULT: scause=");
|
|
||||||
uart.write_hex(scause);
|
|
||||||
let stval: usize;
|
let stval: usize;
|
||||||
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
||||||
uart.write_str(" stval=");
|
let kern = kern();
|
||||||
|
uart.write_str("FAULT pid=");
|
||||||
|
let pid = kern.current_thread().and_then(|t| kern.thread_process(t));
|
||||||
|
uart.write_hex(pid.map_or(usize::MAX, |p| p.raw() as usize));
|
||||||
|
uart.write_str(": ");
|
||||||
|
uart.write_str(match scause {
|
||||||
|
2 => "illegal instruction",
|
||||||
|
5 | 13 => "load fault",
|
||||||
|
7 | 15 => "store fault",
|
||||||
|
12 => "instruction fetch fault",
|
||||||
|
_ => "fault",
|
||||||
|
});
|
||||||
|
uart.write_str(" at ");
|
||||||
uart.write_hex(stval);
|
uart.write_hex(stval);
|
||||||
uart.write_str(" sepc=");
|
if matches!(scause, 5 | 7 | 13 | 15) && types::layout::is_stack_overflow(stval) {
|
||||||
|
uart.write_str(" (stack overflow: give it more stack)");
|
||||||
|
}
|
||||||
|
uart.write_str(", pc ");
|
||||||
uart.write_hex(frame.sepc);
|
uart.write_hex(frame.sepc);
|
||||||
uart.put_char(b'\n');
|
uart.put_char(b'\n');
|
||||||
|
|
||||||
let kern = kern();
|
|
||||||
if let Some(current_tid) = kern.current_thread() {
|
if let Some(current_tid) = kern.current_thread() {
|
||||||
let fault = match scause {
|
let fault = match scause {
|
||||||
2 => domain::fault::Fault::IllegalInstruction,
|
2 => domain::fault::Fault::IllegalInstruction,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra
|
|||||||
frame.sstatus = ctx.status();
|
frame.sstatus = ctx.status();
|
||||||
frame.sstatus &= !(1 << 8); // SPP: return to U-mode
|
frame.sstatus &= !(1 << 8); // SPP: return to U-mode
|
||||||
frame.sstatus |= 1 << 5; // SPIE
|
frame.sstatus |= 1 << 5; // SPIE
|
||||||
|
super::fp::load_for(frame, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
|
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
|
||||||
@@ -70,6 +71,7 @@ fn next_runnable() -> types::id::ThreadId {
|
|||||||
if sip & (1 << CAUSE_S_TIMER) != 0 {
|
if sip & (1 << CAUSE_S_TIMER) != 0 {
|
||||||
// No thread is running, so there is no time slice to charge.
|
// No thread is running, so there is no time slice to charge.
|
||||||
crate::hw::timer::handle_interrupt();
|
crate::hw::timer::handle_interrupt();
|
||||||
|
kern().fire_timers();
|
||||||
}
|
}
|
||||||
if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
|
if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
|
||||||
dispatch_external();
|
dispatch_external();
|
||||||
|
|||||||
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]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
@@ -6,8 +7,9 @@ extern crate libkos;
|
|||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
libkos::entry!(main);
|
||||||
extern "C" fn main() {
|
|
||||||
|
fn main() {
|
||||||
let ep = CapSlot::const_new(0);
|
let ep = CapSlot::const_new(0);
|
||||||
|
|
||||||
if let Ok(syscall::Received::Call { words: [w0, w1, w2, w3], .. }) = syscall::receive_msg(ep) {
|
if let Ok(syscall::Received::Call { words: [w0, w1, w2, w3], .. }) = syscall::receive_msg(ep) {
|
||||||
|
|||||||
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]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
libkos::entry!(main);
|
||||||
extern "C" fn main() {
|
|
||||||
|
fn main() {
|
||||||
libkos::println!("Hello from k-os!");
|
libkos::println!("Hello from k-os!");
|
||||||
}
|
}
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,44 +3,43 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
use types::address::VirtualAddress;
|
||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::CapSlot;
|
||||||
use types::mapping::PagePerms;
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters.
|
/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters.
|
||||||
const COUNTERS_VA: usize = 0x0100_0000;
|
const COUNTERS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
|
||||||
const THREADS: usize = 4;
|
const THREADS: usize = 4;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
fn main() {
|
||||||
extern "C" fn main() {
|
|
||||||
let page = CapSlot::const_new(0);
|
let page = CapSlot::const_new(0);
|
||||||
if syscall::map_region(page, VirtualAddress::from_raw(COUNTERS_VA), PagePerms::ReadWrite).is_err() {
|
let Ok(window) = libkos::window::map(page, COUNTERS_VA, PagePerms::ReadWrite) else {
|
||||||
libkos::println!("smptest: cannot map the counter page");
|
libkos::println!("smptest: cannot map the counter page");
|
||||||
syscall::exit();
|
syscall::exit();
|
||||||
}
|
};
|
||||||
|
let words = window.atomics(types::address::ByteOffset::ZERO, 1 + THREADS);
|
||||||
for _ in 1..THREADS {
|
for _ in 1..THREADS {
|
||||||
let Some(stack) = PageCount::new(1).and_then(syscall::request_pages) else {
|
if !libkos::thread::spawn(words, spin) {
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
let top = StackPointer::from_raw(stack.raw() + 4096);
|
|
||||||
syscall::spawn_thread(EntryPoint::from_raw(spin as *const () as usize), top, 0);
|
|
||||||
}
|
}
|
||||||
spin();
|
spin(words);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn spin() -> ! {
|
libkos::entry!(main);
|
||||||
// SAFETY: COUNTERS_VA maps the counter page; every access is atomic.
|
|
||||||
let word = |i: usize| unsafe { &*((COUNTERS_VA + i * 8) as *const AtomicUsize) };
|
fn spin(word: &'static [AtomicUsize]) -> ! {
|
||||||
let mine = 1 + word(0).fetch_add(1, Ordering::Relaxed) % THREADS;
|
let mine = 1 + word[0].fetch_add(1, Ordering::Relaxed) % THREADS;
|
||||||
loop {
|
loop {
|
||||||
// Yield so a spinner sharing init's hart cannot starve it.
|
// Yield so a spinner sharing init's hart cannot starve it.
|
||||||
for _ in 0..1024 {
|
for _ in 0..1024 {
|
||||||
word(mine).fetch_add(1, Ordering::Relaxed);
|
word[mine].fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
syscall::yield_now();
|
syscall::yield_now();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,4 @@ bench = false
|
|||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|
||||||
libkos = { workspace = true }
|
libkos = { workspace = true }
|
||||||
display-server = { workspace = true }
|
port-framebuffer = { workspace = true }
|
||||||
server-framework = { workspace = true }
|
|
||||||
|
|||||||
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,
|
rng_state: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Snake {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Snake {
|
impl Snake {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 };
|
let start = Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
pub mod game;
|
pub mod game;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
|||||||
@@ -1,90 +1,139 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
|
use libkos::display::Canvas;
|
||||||
|
use libkos::syscall;
|
||||||
|
use port_framebuffer::Framebuffer;
|
||||||
use snake::game::{Direction, GameState, Snake};
|
use snake::game::{Direction, GameState, Snake};
|
||||||
use snake::renderer::{self, RenderCommand};
|
use snake::renderer::{self, RenderCommand};
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use types::display::{Color, Dimension, ScreenCoord};
|
use types::display::{Color, Dimension, ScreenCoord};
|
||||||
use types::time::Ticks;
|
use types::time::Ticks;
|
||||||
use libkos::syscall;
|
|
||||||
use server_framework::Encode;
|
|
||||||
use display_server::protocol::DisplayRequest;
|
|
||||||
|
|
||||||
const INPUT_EP: CapSlot = CapSlot::const_new(0);
|
/// Steps a second; QEMU virt's timebase is 10 MHz.
|
||||||
const DISPLAY_EP: CapSlot = CapSlot::const_new(1);
|
const STEPS_PER_SECOND: u64 = 8;
|
||||||
const TICK_DELAY: u64 = 5_000_000;
|
const TICK_DELAY: u64 = 10_000_000 / STEPS_PER_SECOND;
|
||||||
const MAX_RENDER_CMDS: usize = 140;
|
const MAX_RENDER_CMDS: usize = 140;
|
||||||
|
|
||||||
fn display_send(req: &DisplayRequest) {
|
fn draw_rect(canvas: &mut Canvas, x: u16, y: u16, w: u16, h: u16, color: u32) {
|
||||||
let _ = syscall::request(DISPLAY_EP, req.encode());
|
canvas.fill_rect(ScreenCoord::new(x), ScreenCoord::new(y), Dimension::new(w), Dimension::new(h), Color::from_raw(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_rect(x: u16, y: u16, w: u16, h: u16, color: u32) {
|
libkos::entry!(main);
|
||||||
display_send(&DisplayRequest::DrawRect {
|
|
||||||
x: ScreenCoord::new(x),
|
|
||||||
y: ScreenCoord::new(y),
|
|
||||||
w: Dimension::new(w),
|
|
||||||
h: Dimension::new(h),
|
|
||||||
color: Color::from_raw(color),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush() {
|
fn main() {
|
||||||
display_send(&DisplayRequest::Flush);
|
// Its window, which the permission manager opened because snake's
|
||||||
}
|
// manifest asks for the display. Input comes through it too.
|
||||||
|
let Some(mut canvas) = libkos::start::display().and_then(Canvas::attach) else {
|
||||||
#[unsafe(no_mangle)]
|
libkos::println!("snake: needs the display (see its manifest)");
|
||||||
extern "C" fn main() {
|
return;
|
||||||
|
};
|
||||||
|
let Ok(alarm) = syscall::create_notification() else {
|
||||||
|
libkos::println!("snake: no timer");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Takes focus; keys are polled every tick, the signal only wakes it.
|
||||||
|
let window = canvas.window();
|
||||||
|
let _ = libkos::display::subscribe(window, alarm, types::badge::Badge::new(1 << 1));
|
||||||
|
let mut clock = Clock::new(alarm);
|
||||||
let mut snake = Snake::new();
|
let mut snake = Snake::new();
|
||||||
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS];
|
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS];
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let count = renderer::render(&snake, &mut commands);
|
let count = renderer::render(&snake, &mut commands);
|
||||||
for i in 0..count {
|
for cmd in &commands[..count] {
|
||||||
let cmd = &commands[i];
|
draw_rect(&mut canvas, cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
|
||||||
draw_rect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.color);
|
|
||||||
}
|
}
|
||||||
flush();
|
canvas.flush();
|
||||||
|
|
||||||
if snake.state() == GameState::GameOver {
|
if snake.state() == GameState::GameOver {
|
||||||
let gw = renderer::grid_pixel_width();
|
let gw = renderer::grid_pixel_width();
|
||||||
let gh = renderer::grid_pixel_height();
|
let gh = renderer::grid_pixel_height();
|
||||||
draw_rect(gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFFCC0000);
|
draw_rect(&mut canvas, gw / 2 - 60, gh / 2 - 12, 120, 24, 0xFFCC0000);
|
||||||
flush();
|
canvas.flush();
|
||||||
loop { syscall::yield_now(); }
|
// Any key leaves.
|
||||||
|
while next_input(window) == Input::None {
|
||||||
|
clock.wait_tick();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_tick();
|
clock.wait_tick();
|
||||||
handle_input(&mut snake);
|
if handle_input(window, &mut snake) == Input::Quit {
|
||||||
|
return;
|
||||||
|
}
|
||||||
snake.tick();
|
snake.tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_input(snake: &mut Snake) {
|
#[derive(PartialEq)]
|
||||||
// Drain all pending events, apply the latest direction
|
enum Input {
|
||||||
|
None,
|
||||||
|
Quit,
|
||||||
|
Turn(Direction),
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next key, if one is waiting: q or Escape quits, and so does the
|
||||||
|
/// window going.
|
||||||
|
fn next_input(window: CapSlot) -> Input {
|
||||||
|
use libkos::display::Event;
|
||||||
|
use libkos::input::{InputEvent, Key};
|
||||||
|
match libkos::display::poll(window) {
|
||||||
|
None => Input::None,
|
||||||
|
Some(Event::Closed) => Input::Quit,
|
||||||
|
Some(Event::Input(InputEvent::KeyDown(k))) => match k {
|
||||||
|
Key::Escape | Key::Char(b'q') => Input::Quit,
|
||||||
|
Key::Up | Key::Char(b'w') => Input::Turn(Direction::Up),
|
||||||
|
Key::Down | Key::Char(b's') => Input::Turn(Direction::Down),
|
||||||
|
Key::Left | Key::Char(b'a') => Input::Turn(Direction::Left),
|
||||||
|
Key::Right | Key::Char(b'd') => Input::Turn(Direction::Right),
|
||||||
|
_ => Input::Other,
|
||||||
|
},
|
||||||
|
Some(_) => Input::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drains the pending keys, applying each turn; reports a quit.
|
||||||
|
fn handle_input(window: CapSlot, snake: &mut Snake) -> Input {
|
||||||
loop {
|
loop {
|
||||||
let Ok([tag, kind, _, _]) = syscall::request(INPUT_EP, [0; 4]) else {
|
match next_input(window) {
|
||||||
break;
|
Input::None => return Input::None,
|
||||||
};
|
Input::Quit => return Input::Quit,
|
||||||
if tag == 0 { break; } // NoEvent — queue drained
|
Input::Turn(d) => snake.set_direction(d),
|
||||||
if tag == 1 {
|
Input::Other => {}
|
||||||
match kind {
|
|
||||||
0 => snake.set_direction(Direction::Up),
|
|
||||||
1 => snake.set_direction(Direction::Down),
|
|
||||||
2 => snake.set_direction(Direction::Left),
|
|
||||||
3 => snake.set_direction(Direction::Right),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_tick() {
|
/// The game's clock: a step every `TICK_DELAY`, counted from the last
|
||||||
let start = syscall::get_time();
|
/// step's deadline, so the time drawing takes does not slow it down.
|
||||||
let deadline = Ticks::from_raw(start.raw() + TICK_DELAY);
|
struct Clock {
|
||||||
while syscall::get_time().raw() < deadline.raw() {
|
alarm: CapSlot,
|
||||||
syscall::yield_now();
|
next: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clock {
|
||||||
|
fn new(alarm: CapSlot) -> Self {
|
||||||
|
Self { alarm, next: syscall::get_time().raw() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sleeps until the next step is due, instead of spinning. Keys
|
||||||
|
/// signal `alarm` too (snake has one notification), so it waits again
|
||||||
|
/// until the step really is due.
|
||||||
|
fn wait_tick(&mut self) {
|
||||||
|
// After a stall, carry on from now rather than rushing to catch up.
|
||||||
|
self.next = (self.next + TICK_DELAY).max(syscall::get_time().raw());
|
||||||
|
if syscall::set_timer(self.alarm, Ticks::from_raw(self.next)).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while syscall::get_time().raw() < self.next {
|
||||||
|
if syscall::wait(self.alarm).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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>();
|
||||||
|
}
|
||||||
@@ -1,29 +1,96 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
/// Where init put them (see init's manifest: own endpoint, then `uses`).
|
||||||
extern "C" fn main() {
|
const EP: CapSlot = CapSlot::const_new(0);
|
||||||
let ep = CapSlot::const_new(0);
|
const INPUT: CapSlot = CapSlot::const_new(1);
|
||||||
|
/// `themes/`, read-only.
|
||||||
|
const THEMES: CapSlot = CapSlot::const_new(2);
|
||||||
|
/// Where storage's buffer is mapped while the themes are read.
|
||||||
|
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x1800_0000);
|
||||||
|
/// The longest theme file read.
|
||||||
|
const THEME_BYTES: usize = 4096;
|
||||||
|
|
||||||
|
libkos::entry!(main);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
// Init hands over the GPU, or nothing if the machine has none.
|
// Init hands over the GPU, or nothing if the machine has none.
|
||||||
let dev = libkos::device::receive_device(ep);
|
let dev = libkos::device::receive_device(EP);
|
||||||
|
let input = subscribe_to_input();
|
||||||
if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) {
|
if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) {
|
||||||
run_virtio(fb, ep);
|
run_virtio(fb, input);
|
||||||
}
|
}
|
||||||
libkos::println!("display: no GPU, running headless");
|
libkos::println!("display: no GPU, running headless");
|
||||||
run_headless(ep);
|
run_headless(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Has the input server signal this thread whenever events arrive; the
|
||||||
|
/// server then passes them on to the app in focus.
|
||||||
|
fn subscribe_to_input() -> Option<CapSlot> {
|
||||||
|
use types::badge::Badge;
|
||||||
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
let notify = libkos::syscall::create_notification().ok()?;
|
||||||
|
libkos::syscall::bind_notification(notify).ok()?;
|
||||||
|
let signal = PermissionSet::empty().with(Permission::Write).with(Permission::Delegate);
|
||||||
|
let copy = libkos::syscall::mint(notify, Badge::NONE, signal).ok()?;
|
||||||
|
let sent = libkos::syscall::call(INPUT, [types::input::SUBSCRIBE, 0, 0, 0], Some(copy.into()));
|
||||||
|
libkos::syscall::drop_cap(copy);
|
||||||
|
sent.ok().map(|_| INPUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server<F: port_framebuffer::Framebuffer>(fb: F, input: Option<CapSlot>) -> display_server::server::DisplayServer<F, display_server::host::KernelHost> {
|
||||||
|
display_server::server::DisplayServer::new(fb, display_server::host::KernelHost::new(EP, input), themes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every theme in `themes/` that reads and passes contrast; the built-in
|
||||||
|
/// one if none does.
|
||||||
|
fn themes() -> display_server::themes::Themes {
|
||||||
|
let mut themes = display_server::themes::Themes::new();
|
||||||
|
let Ok(mut fs) = libkos::fs::Storage::attach(THEMES, STORAGE_BUF_VA) else {
|
||||||
|
libkos::println!("display: no themes folder; the built-in theme only");
|
||||||
|
return themes;
|
||||||
|
};
|
||||||
|
let mut names = [([0u8; types::storage::DIR_NAME_SIZE], 0usize); display_server::themes::MAX_THEMES];
|
||||||
|
let mut found = 0;
|
||||||
|
let _ = fs.list_dir(b"", |e| {
|
||||||
|
if !e.is_dir && e.name().ends_with(b".toml") && found < names.len() {
|
||||||
|
names[found] = (e.name, e.name_len);
|
||||||
|
found += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut text = [0u8; THEME_BYTES];
|
||||||
|
for (name, len) in &names[..found] {
|
||||||
|
let name = &name[..*len];
|
||||||
|
let read = fs.open(name).and_then(|h| {
|
||||||
|
let n = fs.read(h, &mut text);
|
||||||
|
let _ = fs.close(h);
|
||||||
|
n
|
||||||
|
});
|
||||||
|
let shown = core::str::from_utf8(name).unwrap_or("?");
|
||||||
|
match read.ok().and_then(|n| core::str::from_utf8(&text[..n]).ok()) {
|
||||||
|
Some(t) => {
|
||||||
|
if let Err(e) = themes.add(t) {
|
||||||
|
libkos::println!("display: theme {} refused: {:?}", shown, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => libkos::println!("display: cannot read theme {}", shown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.detach();
|
||||||
|
// Storage has few connections to give; this one is done with.
|
||||||
|
let _ = libkos::fs::release(THEMES);
|
||||||
|
themes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot) -> ! {
|
fn run_virtio(fb: driver_framebuffer_virtio::VirtioFramebuffer, input: Option<CapSlot>) -> ! {
|
||||||
use port_framebuffer::Framebuffer;
|
let mut srv = server(fb, input);
|
||||||
fb.clear(types::display::Color::rgb(30, 30, 60));
|
server_framework::serve(&mut srv, EP)
|
||||||
fb.flush();
|
|
||||||
let mut srv = display_server::server::DisplayServer::new(fb);
|
|
||||||
server_framework::serve(&mut srv, ep)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// No GPU: clients still get answers (and the dimensions they draw for);
|
/// No GPU: clients still get answers (and the dimensions they draw for);
|
||||||
@@ -49,10 +116,18 @@ impl port_framebuffer::Framebuffer for Headless {
|
|||||||
}
|
}
|
||||||
fn clear(&mut self, _: types::display::Color) {}
|
fn clear(&mut self, _: types::display::Color) {}
|
||||||
fn flush(&mut self) {}
|
fn flush(&mut self) {}
|
||||||
|
fn scroll_up(
|
||||||
|
&mut self,
|
||||||
|
_: types::display::ScreenCoord,
|
||||||
|
_: types::display::Dimension,
|
||||||
|
_: types::display::Dimension,
|
||||||
|
_: types::display::Color,
|
||||||
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn run_headless(ep: CapSlot) -> ! {
|
fn run_headless(input: Option<CapSlot>) -> ! {
|
||||||
let mut srv = display_server::server::DisplayServer::new(Headless);
|
let mut srv = server(Headless, input);
|
||||||
server_framework::serve(&mut srv, ep)
|
server_framework::serve(&mut srv, EP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
|
use driver_input_virtio::VirtioInputDevice;
|
||||||
|
use input_server::server::{InputServer, MAX_DEVICES, Pair};
|
||||||
use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO};
|
use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO};
|
||||||
use port_input_device::InputDevice;
|
use port_input_device::InputDevice;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
libkos::entry!(main);
|
||||||
extern "C" fn main() {
|
|
||||||
|
fn main() {
|
||||||
let ep = CapSlot::new(0).unwrap();
|
let ep = CapSlot::new(0).unwrap();
|
||||||
// Init hands over the keyboard (virtio) or, failing that, the UART.
|
// Init hands over the input devices, two (a keyboard, a tablet), each
|
||||||
let dev = device::receive_device(ep);
|
// virtio or, failing a keyboard, the UART; either may be none.
|
||||||
match (dev.kind, dev.mmio) {
|
let first = device::receive_device(ep);
|
||||||
|
let second = device::receive_device(ep);
|
||||||
|
let irqs = [first.irq, second.irq];
|
||||||
|
let tablet = match (second.kind, second.mmio) {
|
||||||
|
(KIND_VIRTIO, Some(mmio)) => VirtioInputDevice::try_new(mmio, 1),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
match (first.kind, first.mmio) {
|
||||||
(KIND_VIRTIO, Some(mmio)) => {
|
(KIND_VIRTIO, Some(mmio)) => {
|
||||||
if let Some(d) = driver_input_virtio::VirtioInputDevice::try_new(mmio) {
|
if let Some(d) = VirtioInputDevice::try_new(mmio, 0) {
|
||||||
run(d, dev.irq, ep);
|
run(Pair { first: d, second: tablet }, irqs, ep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(KIND_UART_16550, Some(mmio)) => {
|
(KIND_UART_16550, Some(mmio)) => {
|
||||||
if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) {
|
if let Some(d) = driver_input_uart::UartInputDevice::new(mmio) {
|
||||||
run(d, dev.irq, ep);
|
run(Pair { first: d, second: tablet }, irqs, ep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -29,12 +40,18 @@ extern "C" fn main() {
|
|||||||
libkos::syscall::exit();
|
libkos::syscall::exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serves `device`, woken by its interrupt when it has one.
|
/// Serves `device`, woken by its interrupts when it has them.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn run<D: InputDevice>(device: D, irq: Option<CapSlot>, ep: CapSlot) -> ! {
|
fn run<D: InputDevice>(device: D, irqs: [Option<CapSlot>; MAX_DEVICES], ep: CapSlot) -> ! {
|
||||||
let mut srv = match irq.filter(|irq| device::bind_irq(*irq).is_ok()) {
|
let mut lines = [CapSlot::const_new(0); MAX_DEVICES];
|
||||||
Some(irq) => input_server::server::InputServer::with_irq(device, irq),
|
let mut n = 0;
|
||||||
None => input_server::server::InputServer::new(device),
|
for irq in irqs.into_iter().flatten() {
|
||||||
|
lines[n] = irq;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
let mut srv = match n > 0 && device::bind_irqs(&lines[..n]).is_ok() {
|
||||||
|
true => InputServer::with_irqs(device, irqs),
|
||||||
|
false => InputServer::new(device),
|
||||||
};
|
};
|
||||||
server_framework::serve(&mut srv, ep)
|
server_framework::serve(&mut srv, ep)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
libkos::entry!(main);
|
||||||
extern "C" fn main() {
|
|
||||||
|
fn main() {
|
||||||
storage_server::run();
|
storage_server::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ struct Node {
|
|||||||
next: Option<u16>,
|
next: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
const EMPTY: Self = Self {
|
||||||
|
live: false,
|
||||||
|
parent: None,
|
||||||
|
first_child: None,
|
||||||
|
prev: None,
|
||||||
|
next: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CapTree<const N: usize> {
|
pub struct CapTree<const N: usize> {
|
||||||
nodes: [Node; N],
|
nodes: [Node; N],
|
||||||
}
|
}
|
||||||
@@ -31,9 +41,9 @@ fn id(i: usize) -> Option<u16> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> CapTree<N> {
|
impl<const N: usize> CapTree<N> {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
nodes: [Node::default(); N],
|
nodes: [Node::EMPTY; N],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,14 +93,14 @@ impl Endpoint {
|
|||||||
pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 };
|
pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 };
|
||||||
|
|
||||||
pub struct EndpointTable {
|
pub struct EndpointTable {
|
||||||
endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
|
endpoints: [crate::slot::Slot<Endpoint>; MAX_ENDPOINTS],
|
||||||
generations: SlotGenerations<MAX_ENDPOINTS>,
|
generations: SlotGenerations<MAX_ENDPOINTS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EndpointTable {
|
impl EndpointTable {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
endpoints: [const { None }; MAX_ENDPOINTS],
|
endpoints: [const { crate::slot::Slot::Empty }; MAX_ENDPOINTS],
|
||||||
generations: SlotGenerations::new(),
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,9 +111,9 @@ impl EndpointTable {
|
|||||||
.endpoints
|
.endpoints
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?;
|
.find(|(i, s)| !s.is_full() && generations.is_usable(*i))?;
|
||||||
let id = EndpointId::from_raw(idx as u16);
|
let id = EndpointId::from_raw(idx as u16);
|
||||||
*slot = Some(Endpoint::new(id, owner));
|
slot.set(Endpoint::new(id, owner));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,13 +124,13 @@ impl EndpointTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &Endpoint> {
|
pub fn iter(&self) -> impl Iterator<Item = &Endpoint> {
|
||||||
self.endpoints.iter().flatten()
|
self.endpoints.iter().filter_map(crate::slot::Slot::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
|
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
|
||||||
self.endpoints
|
self.endpoints
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.filter_map(crate::slot::Slot::as_ref)
|
||||||
.filter(move |ep| ep.owner == owner)
|
.filter(move |ep| ep.owner == owner)
|
||||||
.map(|ep| ep.id)
|
.map(|ep| ep.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub struct WaitQueue<const N: usize = MAX_WAITERS> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> WaitQueue<N> {
|
impl<const N: usize> WaitQueue<N> {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
entries: [None; N],
|
entries: [None; N],
|
||||||
len: 0,
|
len: 0,
|
||||||
|
|||||||
@@ -6,21 +6,44 @@ use types::generation::Generation;
|
|||||||
#[path = "generations_tests.rs"]
|
#[path = "generations_tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
/// One slot's counter. A fresh one is all zero bytes (not retired,
|
||||||
|
/// generation 0), so tables of them cost nothing in the kernel image.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Counter {
|
||||||
|
retired: bool,
|
||||||
|
generation: Generation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Counter {
|
||||||
|
const FRESH: Self = Self {
|
||||||
|
retired: false,
|
||||||
|
generation: Generation::initial(),
|
||||||
|
};
|
||||||
|
const RETIRED: Self = Self {
|
||||||
|
retired: true,
|
||||||
|
generation: Generation::initial(),
|
||||||
|
};
|
||||||
|
|
||||||
|
fn get(self) -> Option<Generation> {
|
||||||
|
(!self.retired).then_some(self.generation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Bumped on every destroy or revoke so older capabilities stop matching; a slot whose counter runs out is retired.
|
/// Bumped on every destroy or revoke so older capabilities stop matching; a slot whose counter runs out is retired.
|
||||||
pub struct SlotGenerations<const N: usize> {
|
pub struct SlotGenerations<const N: usize> {
|
||||||
gens: [Option<Generation>; N],
|
gens: [Counter; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> SlotGenerations<N> {
|
impl<const N: usize> SlotGenerations<N> {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
gens: [Some(Generation::initial()); N],
|
gens: [Counter::FRESH; N],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current generation of `idx`, `None` if retired or out of range.
|
/// Current generation of `idx`, `None` if retired or out of range.
|
||||||
pub fn current(&self, idx: usize) -> Option<Generation> {
|
pub fn current(&self, idx: usize) -> Option<Generation> {
|
||||||
*self.gens.get(idx)?
|
self.gens.get(idx)?.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_usable(&self, idx: usize) -> bool {
|
pub fn is_usable(&self, idx: usize) -> bool {
|
||||||
@@ -30,7 +53,13 @@ impl<const N: usize> SlotGenerations<N> {
|
|||||||
/// Invalidates every capability minted for the current generation.
|
/// Invalidates every capability minted for the current generation.
|
||||||
pub fn advance(&mut self, idx: usize) {
|
pub fn advance(&mut self, idx: usize) {
|
||||||
if let Some(g) = self.gens.get_mut(idx) {
|
if let Some(g) = self.gens.get_mut(idx) {
|
||||||
*g = g.and_then(|g| g.next());
|
*g = match g.get().and_then(|g| g.next()) {
|
||||||
|
Some(generation) => Counter {
|
||||||
|
retired: false,
|
||||||
|
generation,
|
||||||
|
},
|
||||||
|
None => Counter::RETIRED,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +72,7 @@ impl<const N: usize> Default for SlotGenerations<N> {
|
|||||||
|
|
||||||
#[cfg(kani)]
|
#[cfg(kani)]
|
||||||
mod proofs {
|
mod proofs {
|
||||||
use super::SlotGenerations;
|
use super::{Counter, SlotGenerations};
|
||||||
use types::generation::Generation;
|
use types::generation::Generation;
|
||||||
|
|
||||||
/// However many times a slot is advanced, a generation observed earlier
|
/// However many times a slot is advanced, a generation observed earlier
|
||||||
@@ -53,9 +82,12 @@ mod proofs {
|
|||||||
fn an_observed_generation_never_returns() {
|
fn an_observed_generation_never_returns() {
|
||||||
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||||
g.gens[0] = if kani::any() {
|
g.gens[0] = if kani::any() {
|
||||||
Some(Generation::from_raw(kani::any()))
|
Counter {
|
||||||
|
retired: false,
|
||||||
|
generation: Generation::from_raw(kani::any()),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
Counter::RETIRED
|
||||||
};
|
};
|
||||||
let observed = g.current(0);
|
let observed = g.current(0);
|
||||||
let steps: u8 = kani::any();
|
let steps: u8 = kani::any();
|
||||||
@@ -73,7 +105,7 @@ mod proofs {
|
|||||||
#[kani::unwind(5)]
|
#[kani::unwind(5)]
|
||||||
fn retired_slots_stay_retired() {
|
fn retired_slots_stay_retired() {
|
||||||
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||||
g.gens[0] = None;
|
g.gens[0] = Counter::RETIRED;
|
||||||
let steps: u8 = kani::any();
|
let steps: u8 = kani::any();
|
||||||
kani::assume(steps <= 4);
|
kani::assume(steps <= 4);
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ fn out_of_range_slots_are_never_usable() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn exhausted_counter_retires_the_slot() {
|
fn exhausted_counter_retires_the_slot() {
|
||||||
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||||
g.gens[0] = Some(Generation::from_raw(u32::MAX));
|
g.gens[0] = super::Counter {
|
||||||
|
retired: false,
|
||||||
|
generation: Generation::from_raw(u32::MAX),
|
||||||
|
};
|
||||||
g.advance(0);
|
g.advance(0);
|
||||||
assert_eq!(g.current(0), None);
|
assert_eq!(g.current(0), None);
|
||||||
assert!(!g.is_usable(0));
|
assert!(!g.is_usable(0));
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub struct IrqRoutingTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IrqRoutingTable {
|
impl IrqRoutingTable {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
routes: [None; MAX_IRQS],
|
routes: [None; MAX_IRQS],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
// A kernel panic takes the whole system down, so kernel code may not
|
// A kernel panic takes the whole system down, so kernel code may not
|
||||||
// contain any way to panic. Tests and proofs are exempt.
|
// contain any way to panic. Tests and proofs are exempt.
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
@@ -30,5 +31,7 @@ pub mod notification;
|
|||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
|
pub mod slot;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
|
pub mod timer;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Physical frame ownership. The kernel's single source of truth for what
|
//! Physical frame ownership. The kernel's single source of truth for what
|
||||||
//! every frame of the user pool is used for and who is charged for it.
|
//! every frame of the user pool is used for and who is charged for it.
|
||||||
|
|
||||||
|
use crate::slot::Slot;
|
||||||
use types::{
|
use types::{
|
||||||
address::{PhysicalAddress, VirtualAddress},
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
id::{ProcessId, RegionId},
|
id::{ProcessId, RegionId},
|
||||||
@@ -18,7 +19,7 @@ mod proofs;
|
|||||||
pub const PAGE_SIZE: usize = 4096;
|
pub const PAGE_SIZE: usize = 4096;
|
||||||
|
|
||||||
/// Frames the kernel can track. The board's user pool must fit.
|
/// Frames the kernel can track. The board's user pool must fit.
|
||||||
pub const MAX_FRAMES: usize = if cfg!(kani) { 6 } else { 4096 };
|
pub const MAX_FRAMES: usize = if cfg!(kani) { 6 } else { 114_688 };
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum FrameUse {
|
pub enum FrameUse {
|
||||||
@@ -48,24 +49,35 @@ impl FrameUse {
|
|||||||
pub struct FrameTable<const N: usize = MAX_FRAMES> {
|
pub struct FrameTable<const N: usize = MAX_FRAMES> {
|
||||||
base: PhysicalAddress,
|
base: PhysicalAddress,
|
||||||
total: usize,
|
total: usize,
|
||||||
frames: [Option<FrameUse>; N],
|
frames: [Slot<FrameUse>; N],
|
||||||
free: usize,
|
free: usize,
|
||||||
/// Where the next allocation starts looking: just past the last one.
|
/// Where the next allocation starts looking: just past the last one.
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> FrameTable<N> {
|
impl<const N: usize> FrameTable<N> {
|
||||||
pub fn new(base: PhysicalAddress, total: usize) -> Self {
|
pub const fn new(base: PhysicalAddress, total: usize) -> Self {
|
||||||
let total = total.min(N);
|
let total = if total < N { total } else { N };
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
total,
|
total,
|
||||||
frames: [None; N],
|
frames: [Slot::Empty; N],
|
||||||
free: total,
|
free: total,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The pool, given at boot to a table made (in the kernel's static)
|
||||||
|
/// without one; only while nothing is allocated.
|
||||||
|
pub fn set_pool(&mut self, base: PhysicalAddress, total: usize) {
|
||||||
|
if self.free == self.total && self.live().iter().all(|f| !f.is_full()) {
|
||||||
|
self.base = base;
|
||||||
|
self.total = total.min(N);
|
||||||
|
self.free = self.total;
|
||||||
|
self.cursor = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn base(&self) -> PhysicalAddress {
|
pub fn base(&self) -> PhysicalAddress {
|
||||||
self.base
|
self.base
|
||||||
}
|
}
|
||||||
@@ -92,11 +104,11 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The frames that exist (`total` is clamped to `N` at construction).
|
/// The frames that exist (`total` is clamped to `N` at construction).
|
||||||
fn live(&self) -> &[Option<FrameUse>] {
|
fn live(&self) -> &[Slot<FrameUse>] {
|
||||||
self.frames.get(..self.total).unwrap_or(&[])
|
self.frames.get(..self.total).unwrap_or(&[])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn live_mut(&mut self) -> &mut [Option<FrameUse>] {
|
fn live_mut(&mut self) -> &mut [Slot<FrameUse>] {
|
||||||
self.frames.get_mut(..self.total).unwrap_or(&mut [])
|
self.frames.get_mut(..self.total).unwrap_or(&mut [])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +125,8 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
let start = self.cursor.min(total);
|
let start = self.cursor.min(total);
|
||||||
let idx = (start..total)
|
let idx = (start..total)
|
||||||
.chain(0..start)
|
.chain(0..start)
|
||||||
.find(|&i| self.frames.get(i).is_some_and(Option::is_none))?;
|
.find(|&i| self.frames.get(i).is_some_and(|f| !f.is_full()))?;
|
||||||
*self.frames.get_mut(idx)? = Some(usage);
|
self.frames.get_mut(idx)?.set(usage);
|
||||||
self.free -= 1;
|
self.free -= 1;
|
||||||
self.cursor = idx + 1;
|
self.cursor = idx + 1;
|
||||||
Some(self.address_of(idx))
|
Some(self.address_of(idx))
|
||||||
@@ -132,7 +144,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
let mut run = 0;
|
let mut run = 0;
|
||||||
let mut start = None;
|
let mut start = None;
|
||||||
for (idx, f) in self.live().iter().enumerate() {
|
for (idx, f) in self.live().iter().enumerate() {
|
||||||
if f.is_some() {
|
if f.is_full() {
|
||||||
run = 0;
|
run = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -145,7 +157,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
let start = start?;
|
let start = start?;
|
||||||
let run = self.live_mut().get_mut(start..start + count)?;
|
let run = self.live_mut().get_mut(start..start + count)?;
|
||||||
for (i, f) in run.iter_mut().enumerate() {
|
for (i, f) in run.iter_mut().enumerate() {
|
||||||
*f = Some(usage(i));
|
f.set(usage(i));
|
||||||
}
|
}
|
||||||
self.free -= count;
|
self.free -= count;
|
||||||
Some(self.address_of(start))
|
Some(self.address_of(start))
|
||||||
@@ -165,7 +177,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
match self
|
match self
|
||||||
.index_of(pa)
|
.index_of(pa)
|
||||||
.and_then(|i| self.frames.get_mut(i))
|
.and_then(|i| self.frames.get_mut(i))
|
||||||
.and_then(Option::as_mut)
|
.and_then(Slot::as_mut)
|
||||||
{
|
{
|
||||||
Some(slot) => {
|
Some(slot) => {
|
||||||
*slot = usage;
|
*slot = usage;
|
||||||
@@ -205,14 +217,17 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
|
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
|
||||||
self.live().iter().enumerate().find_map(|(idx, f)| match f {
|
self.live()
|
||||||
Some(FrameUse::User {
|
.iter()
|
||||||
owner: o,
|
.enumerate()
|
||||||
page: p,
|
.find_map(|(idx, f)| match f.as_ref() {
|
||||||
perms,
|
Some(FrameUse::User {
|
||||||
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)),
|
owner: o,
|
||||||
_ => None,
|
page: p,
|
||||||
})
|
perms,
|
||||||
|
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every allocated frame and its use.
|
/// Every allocated frame and its use.
|
||||||
@@ -231,7 +246,7 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
self.live()
|
self.live()
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(move |(idx, f)| match f {
|
.filter_map(move |(idx, f)| match f.as_ref() {
|
||||||
Some(FrameUse::User { owner: o, page, .. }) if *o == owner => Some((
|
Some(FrameUse::User { owner: o, page, .. }) if *o == owner => Some((
|
||||||
self.address_of(idx),
|
self.address_of(idx),
|
||||||
VirtualAddress::from_raw(*page as usize * PAGE_SIZE),
|
VirtualAddress::from_raw(*page as usize * PAGE_SIZE),
|
||||||
@@ -246,11 +261,11 @@ impl<const N: usize> FrameTable<N> {
|
|||||||
let mut freed = 0;
|
let mut freed = 0;
|
||||||
for slot in self.live_mut().iter_mut() {
|
for slot in self.live_mut().iter_mut() {
|
||||||
let owned = matches!(
|
let owned = matches!(
|
||||||
slot,
|
slot.as_ref(),
|
||||||
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner
|
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner
|
||||||
);
|
);
|
||||||
if owned {
|
if owned {
|
||||||
*slot = None;
|
*slot = Slot::Empty;
|
||||||
freed += 1;
|
freed += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,17 @@ fn any_use() -> FrameUse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn any_slot() -> Option<FrameUse> {
|
fn any_slot() -> Slot<FrameUse> {
|
||||||
if kani::any() { Some(any_use()) } else { None }
|
if kani::any() {
|
||||||
|
Slot::Full(any_use())
|
||||||
|
} else {
|
||||||
|
Slot::Empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn any_table() -> FrameTable<N> {
|
fn any_table() -> FrameTable<N> {
|
||||||
let frames = [any_slot(), any_slot(), any_slot(), any_slot()];
|
let frames = [any_slot(), any_slot(), any_slot(), any_slot()];
|
||||||
let free = frames.iter().filter(|f| f.is_none()).count();
|
let free = frames.iter().filter(|f| !f.is_full()).count();
|
||||||
FrameTable {
|
FrameTable {
|
||||||
base: PhysicalAddress::from_raw(BASE),
|
base: PhysicalAddress::from_raw(BASE),
|
||||||
total: N,
|
total: N,
|
||||||
@@ -53,7 +57,7 @@ fn any_table() -> FrameTable<N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn well_formed(t: &FrameTable<N>) -> bool {
|
fn well_formed(t: &FrameTable<N>) -> bool {
|
||||||
t.free == t.frames[..t.total].iter().filter(|f| f.is_none()).count()
|
t.free == t.frames[..t.total].iter().filter(|f| !f.is_full()).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn any_pa() -> PhysicalAddress {
|
fn any_pa() -> PhysicalAddress {
|
||||||
@@ -71,8 +75,8 @@ fn alloc_only_hands_out_free_frames() {
|
|||||||
Some(pa) => {
|
Some(pa) => {
|
||||||
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
||||||
assert!(idx < N);
|
assert!(idx < N);
|
||||||
assert!(before[idx].is_none());
|
assert!(!before[idx].is_full());
|
||||||
assert!(t.frames[idx] == Some(usage));
|
assert!(t.frames[idx] == Slot::Full(usage));
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
if i != idx {
|
if i != idx {
|
||||||
assert!(t.frames[i] == before[i]);
|
assert!(t.frames[i] == before[i]);
|
||||||
@@ -80,7 +84,7 @@ fn alloc_only_hands_out_free_frames() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
assert!(before.iter().all(Option::is_some));
|
assert!(before.iter().all(Slot::is_full));
|
||||||
assert!(t.frames == before);
|
assert!(t.frames == before);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,8 +101,8 @@ fn free_releases_exactly_one_allocated_frame_or_nothing() {
|
|||||||
Some(usage) => {
|
Some(usage) => {
|
||||||
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
||||||
assert!(pa.raw() % PAGE_SIZE == 0);
|
assert!(pa.raw() % PAGE_SIZE == 0);
|
||||||
assert!(before[idx] == Some(usage));
|
assert!(before[idx] == Slot::Full(usage));
|
||||||
assert!(t.frames[idx].is_none());
|
assert!(!t.frames[idx].is_full());
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
if i != idx {
|
if i != idx {
|
||||||
assert!(t.frames[i] == before[i]);
|
assert!(t.frames[i] == before[i]);
|
||||||
@@ -119,8 +123,8 @@ fn retag_never_allocates() {
|
|||||||
let usage = any_use();
|
let usage = any_use();
|
||||||
let changed = t.retag(pa, usage);
|
let changed = t.retag(pa, usage);
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
if before[i].is_none() {
|
if !before[i].is_full() {
|
||||||
assert!(t.frames[i].is_none());
|
assert!(!t.frames[i].is_full());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !changed {
|
if !changed {
|
||||||
@@ -138,11 +142,11 @@ fn free_owned_by_spares_everyone_else() {
|
|||||||
t.free_owned_by(owner);
|
t.free_owned_by(owner);
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
let was_owned = matches!(
|
let was_owned = matches!(
|
||||||
before[i],
|
before[i].as_ref(),
|
||||||
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if o == owner
|
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner
|
||||||
);
|
);
|
||||||
if was_owned {
|
if was_owned {
|
||||||
assert!(t.frames[i].is_none());
|
assert!(!t.frames[i].is_full());
|
||||||
} else {
|
} else {
|
||||||
assert!(t.frames[i] == before[i]);
|
assert!(t.frames[i] == before[i]);
|
||||||
}
|
}
|
||||||
@@ -162,7 +166,7 @@ fn find_user_only_reports_the_owners_mapping() {
|
|||||||
assert!(va.raw() % PAGE_SIZE == 0);
|
assert!(va.raw() % PAGE_SIZE == 0);
|
||||||
assert!(
|
assert!(
|
||||||
t.frames[idx]
|
t.frames[idx]
|
||||||
== Some(FrameUse::User {
|
== Slot::Full(FrameUse::User {
|
||||||
owner,
|
owner,
|
||||||
page: (va.raw() / PAGE_SIZE) as u32,
|
page: (va.raw() / PAGE_SIZE) as u32,
|
||||||
perms,
|
perms,
|
||||||
@@ -184,8 +188,8 @@ fn alloc_contiguous_only_claims_a_free_run() {
|
|||||||
assert!(count > 0 && start + count <= N);
|
assert!(count > 0 && start + count <= N);
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
if i >= start && i < start + count {
|
if i >= start && i < start + count {
|
||||||
assert!(before[i].is_none());
|
assert!(!before[i].is_full());
|
||||||
assert!(t.frames[i] == Some(FrameUse::Region { region }));
|
assert!(t.frames[i] == Slot::Full(FrameUse::Region { region }));
|
||||||
} else {
|
} else {
|
||||||
assert!(t.frames[i] == before[i]);
|
assert!(t.frames[i] == before[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ mod quota_tests;
|
|||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub const MAX_REGIONS: usize = if cfg!(kani) { 1 } else { 64 };
|
/// System-wide: the display and storage hold a dozen or more each.
|
||||||
|
pub const MAX_REGIONS: usize = if cfg!(kani) { 1 } else { 256 };
|
||||||
pub const MAX_REGION_MAPPINGS: usize = if cfg!(kani) { 2 } else { 4 };
|
pub const MAX_REGION_MAPPINGS: usize = if cfg!(kani) { 2 } else { 4 };
|
||||||
|
|
||||||
/// Where a region's frames come from.
|
/// Where a region's frames come from.
|
||||||
@@ -117,14 +118,14 @@ impl Region {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegionTable {
|
pub struct RegionTable {
|
||||||
regions: [Option<Region>; MAX_REGIONS],
|
regions: [crate::slot::Slot<Region>; MAX_REGIONS],
|
||||||
generations: SlotGenerations<MAX_REGIONS>,
|
generations: SlotGenerations<MAX_REGIONS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegionTable {
|
impl RegionTable {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
regions: [const { None }; MAX_REGIONS],
|
regions: [const { crate::slot::Slot::Empty }; MAX_REGIONS],
|
||||||
generations: SlotGenerations::new(),
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,7 @@ impl RegionTable {
|
|||||||
self.regions
|
self.regions
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.position(|(i, r)| r.is_none() && self.generations.is_usable(i))
|
.position(|(i, r)| !r.is_full() && self.generations.is_usable(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The id the next `insert` will return.
|
/// The id the next `insert` will return.
|
||||||
@@ -150,7 +151,7 @@ impl RegionTable {
|
|||||||
|
|
||||||
pub fn insert(&mut self, region: Region) -> Option<RegionId> {
|
pub fn insert(&mut self, region: Region) -> Option<RegionId> {
|
||||||
let idx = self.free_slot()?;
|
let idx = self.free_slot()?;
|
||||||
*self.regions.get_mut(idx)? = Some(region);
|
self.regions.get_mut(idx)?.set(region);
|
||||||
Some(RegionId::from_raw(idx as u16))
|
Some(RegionId::from_raw(idx as u16))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ pub const MAX_MESSAGE_WORDS: usize = 4;
|
|||||||
pub struct Message {
|
pub struct Message {
|
||||||
words: [usize; MAX_MESSAGE_WORDS],
|
words: [usize; MAX_MESSAGE_WORDS],
|
||||||
cap: Option<CapSlot>,
|
cap: Option<CapSlot>,
|
||||||
|
/// The receiver may pass `cap` on (see `syscall_abi::KEEP_DELEGATE`).
|
||||||
|
keep_delegate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
@@ -19,7 +21,11 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
||||||
Self { words, cap: None }
|
Self {
|
||||||
|
words,
|
||||||
|
cap: None,
|
||||||
|
keep_delegate: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_cap(mut self, slot: CapSlot) -> Self {
|
pub fn with_cap(mut self, slot: CapSlot) -> Self {
|
||||||
@@ -39,4 +45,13 @@ impl Message {
|
|||||||
pub fn cap(&self) -> Option<CapSlot> {
|
pub fn cap(&self) -> Option<CapSlot> {
|
||||||
self.cap
|
self.cap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn keeping_delegate(mut self) -> Self {
|
||||||
|
self.keep_delegate = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keeps_delegate(&self) -> bool {
|
||||||
|
self.keep_delegate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,14 +128,14 @@ impl Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct NotificationTable {
|
pub struct NotificationTable {
|
||||||
slots: [Option<Notification>; MAX_NOTIFICATIONS],
|
slots: [crate::slot::Slot<Notification>; MAX_NOTIFICATIONS],
|
||||||
generations: SlotGenerations<MAX_NOTIFICATIONS>,
|
generations: SlotGenerations<MAX_NOTIFICATIONS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NotificationTable {
|
impl NotificationTable {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
slots: [const { None }; MAX_NOTIFICATIONS],
|
slots: [const { crate::slot::Slot::Empty }; MAX_NOTIFICATIONS],
|
||||||
generations: SlotGenerations::new(),
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,9 +146,9 @@ impl NotificationTable {
|
|||||||
.slots
|
.slots
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?;
|
.find(|(i, s)| !s.is_full() && generations.is_usable(*i))?;
|
||||||
let id = NotificationId::from_raw(idx as u16);
|
let id = NotificationId::from_raw(idx as u16);
|
||||||
*slot = Some(Notification::new(id, owner));
|
slot.set(Notification::new(id, owner));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,13 +172,13 @@ impl NotificationTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &Notification> {
|
pub fn iter(&self) -> impl Iterator<Item = &Notification> {
|
||||||
self.slots.iter().flatten()
|
self.slots.iter().filter_map(crate::slot::Slot::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
|
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
|
||||||
self.slots
|
self.slots
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.filter_map(crate::slot::Slot::as_ref)
|
||||||
.filter(move |n| n.owner == owner)
|
.filter(move |n| n.owner == owner)
|
||||||
.map(|n| n.id)
|
.map(|n| n.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessTable {
|
pub struct ProcessTable {
|
||||||
processes: [Option<Process>; MAX_PROCESSES],
|
processes: [crate::slot::Slot<Process>; MAX_PROCESSES],
|
||||||
generations: SlotGenerations<MAX_PROCESSES>,
|
generations: SlotGenerations<MAX_PROCESSES>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessTable {
|
impl ProcessTable {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
processes: [const { None }; MAX_PROCESSES],
|
processes: [const { crate::slot::Slot::Empty }; MAX_PROCESSES],
|
||||||
generations: SlotGenerations::new(),
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,11 +103,11 @@ impl ProcessTable {
|
|||||||
.processes
|
.processes
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(i, s)| s.is_none() && generations.is_usable(*i))
|
.find(|(i, s)| !s.is_full() && generations.is_usable(*i))
|
||||||
.ok_or(KernelError::TooManyProcesses)?;
|
.ok_or(KernelError::TooManyProcesses)?;
|
||||||
|
|
||||||
let id = ProcessId::from_raw(idx as u16);
|
let id = ProcessId::from_raw(idx as u16);
|
||||||
*slot = Some(Process::new(id, capabilities, fault_handler));
|
*slot = crate::slot::Slot::Full(Process::new(id, capabilities, fault_handler));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,15 @@ pub struct MultiCoreScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MultiCoreScheduler {
|
impl MultiCoreScheduler {
|
||||||
pub fn new(num_cores: u8) -> Self {
|
pub const fn new(num_cores: u8) -> Self {
|
||||||
let cores = core::array::from_fn(|i| PerCoreScheduler::new(CoreId::from_raw(i as u8)));
|
// A core is numbered by its place in `cores`.
|
||||||
Self {
|
Self {
|
||||||
cores,
|
cores: [const { PerCoreScheduler::new() }; MAX_CORES],
|
||||||
num_cores: num_cores.min(MAX_CORES as u8),
|
num_cores: if num_cores < MAX_CORES as u8 {
|
||||||
|
num_cores
|
||||||
|
} else {
|
||||||
|
MAX_CORES as u8
|
||||||
|
},
|
||||||
placements: [const { None }; MAX_SCHEDULED_THREADS],
|
placements: [const { None }; MAX_SCHEDULED_THREADS],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +51,12 @@ impl MultiCoreScheduler {
|
|||||||
self.placements.get(thread.raw() as usize)?.as_ref()
|
self.placements.get(thread.raw() as usize)?.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How many cores to schedule on, once the platform knows; before any
|
||||||
|
/// thread is placed.
|
||||||
|
pub fn set_num_cores(&mut self, num_cores: u8) {
|
||||||
|
self.num_cores = num_cores.min(MAX_CORES as u8);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn num_cores(&self) -> u8 {
|
pub fn num_cores(&self) -> u8 {
|
||||||
self.num_cores
|
self.num_cores
|
||||||
}
|
}
|
||||||
@@ -60,8 +70,8 @@ impl MultiCoreScheduler {
|
|||||||
pub fn queued_on(&self, thread: ThreadId) -> Option<CoreId> {
|
pub fn queued_on(&self, thread: ThreadId) -> Option<CoreId> {
|
||||||
self.active()
|
self.active()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|c| c.is_queued(thread))
|
.position(|c| c.is_queued(thread))
|
||||||
.map(|c| c.core())
|
.map(|i| CoreId::from_raw(i as u8))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether `thread` waits in some core's run queue.
|
/// Whether `thread` waits in some core's run queue.
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
use super::priority_queue::PriorityQueue;
|
use super::priority_queue::PriorityQueue;
|
||||||
use types::bounded::Priority;
|
use types::bounded::Priority;
|
||||||
use types::id::{CoreId, ThreadId};
|
use types::id::ThreadId;
|
||||||
|
|
||||||
pub struct PerCoreScheduler {
|
pub struct PerCoreScheduler {
|
||||||
core: CoreId,
|
|
||||||
queue: PriorityQueue,
|
queue: PriorityQueue,
|
||||||
current: Option<ThreadId>,
|
current: Option<ThreadId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PerCoreScheduler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PerCoreScheduler {
|
impl PerCoreScheduler {
|
||||||
pub fn new(core: CoreId) -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
core,
|
|
||||||
queue: PriorityQueue::new(),
|
queue: PriorityQueue::new(),
|
||||||
current: None,
|
current: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn core(&self) -> CoreId {
|
|
||||||
self.core
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current(&self) -> Option<ThreadId> {
|
pub fn current(&self) -> Option<ThreadId> {
|
||||||
self.current
|
self.current
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ pub struct PriorityQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PriorityQueue {
|
impl PriorityQueue {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
lists: [ListEnds {
|
lists: [ListEnds {
|
||||||
head: None,
|
head: None,
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ fn drain_all_priorities() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_core_has_no_current() {
|
fn new_core_has_no_current() {
|
||||||
let core = PerCoreScheduler::new(CoreId::from_raw(0));
|
let core = PerCoreScheduler::new();
|
||||||
assert!(core.current().is_none());
|
assert!(core.current().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pick_next_sets_current() {
|
fn pick_next_sets_current() {
|
||||||
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
let mut core = PerCoreScheduler::new();
|
||||||
core.add(thread(1), prio(10));
|
core.add(thread(1), prio(10));
|
||||||
|
|
||||||
let picked = core.pick_next().unwrap();
|
let picked = core.pick_next().unwrap();
|
||||||
@@ -115,13 +115,13 @@ fn pick_next_sets_current() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pick_next_on_empty_returns_none() {
|
fn pick_next_on_empty_returns_none() {
|
||||||
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
let mut core = PerCoreScheduler::new();
|
||||||
assert!(core.pick_next().is_none());
|
assert!(core.pick_next().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clear_current() {
|
fn clear_current() {
|
||||||
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
let mut core = PerCoreScheduler::new();
|
||||||
core.add(thread(1), prio(10));
|
core.add(thread(1), prio(10));
|
||||||
core.pick_next();
|
core.pick_next();
|
||||||
|
|
||||||
|
|||||||
144
crates/domain/src/slot.rs
Normal file
144
crates/domain/src/slot.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
//! A table entry that is empty or holds a value, like `Option`, but whose
|
||||||
|
//! empty state is guaranteed to be all zero bytes. The kernel's tables are
|
||||||
|
//! one static; if every empty entry is zero, the whole static starts zero
|
||||||
|
//! and costs nothing in the kernel image (`.bss`). `Option` makes no such
|
||||||
|
//! promise (its `None` is whatever the compiler picks).
|
||||||
|
|
||||||
|
/// `#[repr(u8)]` fixes the layout: a tag byte, `Empty` being 0, then the
|
||||||
|
/// value (left uninitialised when empty).
|
||||||
|
#[cfg(not(kani))]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum Slot<T> {
|
||||||
|
#[default]
|
||||||
|
Empty = 0,
|
||||||
|
Full(T) = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For the proofs, the same behaviour over `Option`: only the byte layout
|
||||||
|
/// differs, which no proof depends on, and CBMC is far faster on `Option`
|
||||||
|
/// than on an enum with an uninitialised payload.
|
||||||
|
#[cfg(kani)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Slot<T>(Option<T>);
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
impl<T> Default for Slot<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
|
impl<T> Slot<T> {
|
||||||
|
pub const Empty: Self = Self(None);
|
||||||
|
|
||||||
|
pub const fn Full(value: T) -> Self {
|
||||||
|
Self(Some(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_full(&self) -> bool {
|
||||||
|
self.0.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn as_ref(&self) -> Option<&T> {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_mut(&mut self) -> Option<&mut T> {
|
||||||
|
self.0.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take(&mut self) -> Option<T> {
|
||||||
|
self.0.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, value: T) {
|
||||||
|
self.0 = Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
impl<T: Copy> Slot<T> {
|
||||||
|
pub const fn get(&self) -> Option<T> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
impl<T> From<Option<T>> for Slot<T> {
|
||||||
|
fn from(o: Option<T>) -> Self {
|
||||||
|
Self(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(kani))]
|
||||||
|
impl<T> Slot<T> {
|
||||||
|
pub const fn is_full(&self) -> bool {
|
||||||
|
matches!(self, Slot::Full(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn as_ref(&self) -> Option<&T> {
|
||||||
|
match self {
|
||||||
|
Slot::Full(v) => Some(v),
|
||||||
|
Slot::Empty => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_mut(&mut self) -> Option<&mut T> {
|
||||||
|
match self {
|
||||||
|
Slot::Full(v) => Some(v),
|
||||||
|
Slot::Empty => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value, leaving the slot empty.
|
||||||
|
pub fn take(&mut self) -> Option<T> {
|
||||||
|
match core::mem::replace(self, Slot::Empty) {
|
||||||
|
Slot::Full(v) => Some(v),
|
||||||
|
Slot::Empty => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, value: T) {
|
||||||
|
*self = Slot::Full(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(kani))]
|
||||||
|
impl<T: Copy> Slot<T> {
|
||||||
|
pub const fn get(&self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Slot::Full(v) => Some(*v),
|
||||||
|
Slot::Empty => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(kani))]
|
||||||
|
impl<T> From<Option<T>> for Slot<T> {
|
||||||
|
fn from(o: Option<T>) -> Self {
|
||||||
|
match o {
|
||||||
|
Some(v) => Slot::Full(v),
|
||||||
|
None => Slot::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_slot_behaves_like_an_option() {
|
||||||
|
let mut s: Slot<u32> = Slot::Empty;
|
||||||
|
assert!(!s.is_full() && s.get().is_none());
|
||||||
|
s.set(5);
|
||||||
|
*s.as_mut().unwrap() += 1;
|
||||||
|
assert_eq!(s.get(), Some(6));
|
||||||
|
assert_eq!(s.take(), Some(6));
|
||||||
|
assert_eq!(s, Slot::Empty);
|
||||||
|
assert_eq!(Slot::from(Some(1)), Slot::Full(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use types::{
|
use types::{
|
||||||
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||||
badge::Badge,
|
badge::Badge,
|
||||||
bounded::{CapSlot, PageCount},
|
bounded::{CapSlot, PageCount, SavedReply},
|
||||||
mapping::PagePerms,
|
mapping::PagePerms,
|
||||||
permission::PermissionSet,
|
permission::PermissionSet,
|
||||||
|
time::Ticks,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
@@ -91,6 +92,26 @@ pub enum Syscall {
|
|||||||
RegionAddress {
|
RegionAddress {
|
||||||
region: CapSlot,
|
region: CapSlot,
|
||||||
},
|
},
|
||||||
|
/// Signals `notification` when the process behind `process` dies, by
|
||||||
|
/// exit, fault or kill. Up to `timer::MAX_WATCHERS` per process.
|
||||||
|
WatchDeath {
|
||||||
|
process: CapSlot,
|
||||||
|
notification: CapSlot,
|
||||||
|
},
|
||||||
|
/// Signals `notification` once `GetTime` reaches `deadline`; replaces
|
||||||
|
/// the caller's timer, and a zero deadline cancels it.
|
||||||
|
SetTimer {
|
||||||
|
notification: CapSlot,
|
||||||
|
deadline: Ticks,
|
||||||
|
},
|
||||||
|
/// Keeps the caller this thread owes a reply to, to answer later
|
||||||
|
/// (`ReplySaved`) while it receives others; returns which it is.
|
||||||
|
SaveReply,
|
||||||
|
/// Answers a caller kept with `SaveReply`, once.
|
||||||
|
ReplySaved {
|
||||||
|
reply: SavedReply,
|
||||||
|
message: Message,
|
||||||
|
},
|
||||||
/// Removes a capability from the caller's own table.
|
/// Removes a capability from the caller's own table.
|
||||||
DropCap {
|
DropCap {
|
||||||
slot: CapSlot,
|
slot: CapSlot,
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
//! Saved register state of a thread.
|
//! Saved register state of a thread.
|
||||||
|
|
||||||
|
/// A thread's floating-point registers and their control word. A thread
|
||||||
|
/// has none until its first floating-point instruction (see
|
||||||
|
/// `SavedContext::enable_fp`): until then it cannot see another thread's.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct FpState {
|
||||||
|
pub registers: [u64; 32],
|
||||||
|
pub control: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FpState {
|
||||||
|
pub const ZERO: Self = Self {
|
||||||
|
registers: [0; 32],
|
||||||
|
control: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SavedContext {
|
pub struct SavedContext {
|
||||||
registers: [usize; 32],
|
registers: [usize; 32],
|
||||||
pc: usize,
|
pc: usize,
|
||||||
status: usize,
|
status: usize,
|
||||||
|
fp: FpState,
|
||||||
|
fp_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SavedContext {
|
impl SavedContext {
|
||||||
@@ -12,6 +30,8 @@ impl SavedContext {
|
|||||||
registers: [0; 32],
|
registers: [0; 32],
|
||||||
pc: 0,
|
pc: 0,
|
||||||
status: 0,
|
status: 0,
|
||||||
|
fp: FpState::ZERO,
|
||||||
|
fp_enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,4 +68,39 @@ impl SavedContext {
|
|||||||
pub fn registers_mut(&mut self) -> &mut [usize; 32] {
|
pub fn registers_mut(&mut self) -> &mut [usize; 32] {
|
||||||
&mut self.registers
|
&mut self.registers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Its floating-point registers, if it has used any.
|
||||||
|
pub fn fp(&self) -> Option<&FpState> {
|
||||||
|
self.fp_enabled.then_some(&self.fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fp_mut(&mut self) -> Option<&mut FpState> {
|
||||||
|
self.fp_enabled.then_some(&mut self.fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives it floating-point registers, all zero.
|
||||||
|
pub fn enable_fp(&mut self) -> &mut FpState {
|
||||||
|
self.fp = FpState::ZERO;
|
||||||
|
self.fp_enabled = true;
|
||||||
|
&mut self.fp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_thread_has_no_fp_until_it_is_given_zeroed_registers() {
|
||||||
|
let mut c = SavedContext::empty();
|
||||||
|
assert!(c.fp().is_none() && c.fp_mut().is_none());
|
||||||
|
c.enable_fp().registers[3] = 7;
|
||||||
|
assert_eq!(c.fp().unwrap().registers[3], 7);
|
||||||
|
c.enable_fp();
|
||||||
|
assert_eq!(
|
||||||
|
c.fp(),
|
||||||
|
Some(&FpState::ZERO),
|
||||||
|
"enabling again starts from zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::error::KernelError;
|
|||||||
mod context;
|
mod context;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
pub use context::SavedContext;
|
pub use context::{FpState, SavedContext};
|
||||||
pub use table::{MAX_THREADS, ThreadTable};
|
pub use table::{MAX_THREADS, ThreadTable};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ use crate::{error::KernelError, generations::SlotGenerations};
|
|||||||
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
||||||
|
|
||||||
pub struct ThreadTable {
|
pub struct ThreadTable {
|
||||||
threads: [Option<Thread>; MAX_THREADS],
|
threads: [crate::slot::Slot<Thread>; MAX_THREADS],
|
||||||
generations: SlotGenerations<MAX_THREADS>,
|
generations: SlotGenerations<MAX_THREADS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadTable {
|
impl ThreadTable {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
threads: [const { None }; MAX_THREADS],
|
threads: [const { crate::slot::Slot::Empty }; MAX_THREADS],
|
||||||
generations: SlotGenerations::new(),
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ impl ThreadTable {
|
|||||||
self.threads
|
self.threads
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
|
.position(|(i, t)| !t.is_full() && self.generations.is_usable(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(
|
pub fn spawn(
|
||||||
@@ -52,7 +52,7 @@ impl ThreadTable {
|
|||||||
*self
|
*self
|
||||||
.threads
|
.threads
|
||||||
.get_mut(idx)
|
.get_mut(idx)
|
||||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
|
.ok_or(KernelError::TooManyThreads)? = crate::slot::Slot::Full(Thread::new(
|
||||||
id,
|
id,
|
||||||
process,
|
process,
|
||||||
priority,
|
priority,
|
||||||
@@ -76,7 +76,7 @@ impl ThreadTable {
|
|||||||
*self
|
*self
|
||||||
.threads
|
.threads
|
||||||
.get_mut(idx)
|
.get_mut(idx)
|
||||||
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
|
.ok_or(KernelError::TooManyThreads)? = crate::slot::Slot::Full(Thread::with_context(
|
||||||
id,
|
id,
|
||||||
process,
|
process,
|
||||||
priority,
|
priority,
|
||||||
|
|||||||
70
crates/domain/src/timer.rs
Normal file
70
crates/domain/src/timer.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//! Events the kernel reports by signalling a notification: a process's
|
||||||
|
//! death, a deadline passing. One timer per process.
|
||||||
|
|
||||||
|
use types::{badge::Badge, generation::Generation, id::NotificationId, id::ProcessId, time::Ticks};
|
||||||
|
|
||||||
|
use crate::process::MAX_PROCESSES;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "timer_tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
/// A notification as a capability named it; once the notification is
|
||||||
|
/// destroyed (its generation moves on) the target is dead.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Target {
|
||||||
|
pub notification: NotificationId,
|
||||||
|
pub generation: Generation,
|
||||||
|
pub badge: Badge,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many may watch one process die: its parent and init, with room.
|
||||||
|
pub const MAX_WATCHERS: usize = 4;
|
||||||
|
|
||||||
|
pub struct Timers {
|
||||||
|
deadlines: [Option<(Ticks, Target)>; MAX_PROCESSES],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timers {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
deadlines: [None; MAX_PROCESSES],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces `owner`'s timer.
|
||||||
|
pub fn set(&mut self, owner: ProcessId, deadline: Ticks, target: Target) -> bool {
|
||||||
|
match self.deadlines.get_mut(owner.raw() as usize) {
|
||||||
|
Some(slot) => {
|
||||||
|
*slot = Some((deadline, target));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self, owner: ProcessId) {
|
||||||
|
if let Some(slot) = self.deadlines.get_mut(owner.raw() as usize) {
|
||||||
|
*slot = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns one timer whose deadline is at or before `now`.
|
||||||
|
pub fn take_expired(&mut self, now: Ticks) -> Option<Target> {
|
||||||
|
let slot = self
|
||||||
|
.deadlines
|
||||||
|
.iter_mut()
|
||||||
|
.find(|s| s.is_some_and(|(d, _)| d <= now))?;
|
||||||
|
slot.take().map(|(_, t)| t)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn earliest(&self) -> Option<Ticks> {
|
||||||
|
self.deadlines.iter().flatten().map(|(d, _)| *d).min()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Timers {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
37
crates/domain/src/timer_tests.rs
Normal file
37
crates/domain/src/timer_tests.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use super::{Target, Timers};
|
||||||
|
use types::{badge::Badge, generation::Generation, id::NotificationId, id::ProcessId, time::Ticks};
|
||||||
|
|
||||||
|
fn target(n: u16) -> Target {
|
||||||
|
Target {
|
||||||
|
notification: NotificationId::from_raw(n),
|
||||||
|
generation: Generation::initial(),
|
||||||
|
badge: Badge::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid(n: u16) -> ProcessId {
|
||||||
|
ProcessId::from_raw(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_expired_timers_fire_and_each_fires_once() {
|
||||||
|
let mut t = Timers::new();
|
||||||
|
t.set(pid(0), Ticks::from_raw(10), target(0));
|
||||||
|
t.set(pid(1), Ticks::from_raw(30), target(1));
|
||||||
|
assert_eq!(t.earliest(), Some(Ticks::from_raw(10)));
|
||||||
|
assert_eq!(t.take_expired(Ticks::from_raw(9)), None);
|
||||||
|
assert_eq!(t.take_expired(Ticks::from_raw(10)), Some(target(0)));
|
||||||
|
assert_eq!(t.take_expired(Ticks::from_raw(29)), None);
|
||||||
|
assert_eq!(t.earliest(), Some(Ticks::from_raw(30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn setting_again_replaces_and_clear_cancels() {
|
||||||
|
let mut t = Timers::new();
|
||||||
|
t.set(pid(2), Ticks::from_raw(10), target(0));
|
||||||
|
t.set(pid(2), Ticks::from_raw(50), target(1));
|
||||||
|
assert_eq!(t.take_expired(Ticks::from_raw(20)), None);
|
||||||
|
t.clear(pid(2));
|
||||||
|
assert_eq!(t.earliest(), None);
|
||||||
|
assert!(!t.set(pid(500), Ticks::from_raw(1), target(0)));
|
||||||
|
}
|
||||||
@@ -1,71 +1,68 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use hw_virtio::mmio::MmioAccess;
|
|
||||||
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
|
|
||||||
use hw_virtio::block::{self, DmaAddrs};
|
use hw_virtio::block::{self, DmaAddrs};
|
||||||
|
use hw_virtio::mmio::{MmioAccess, Registers};
|
||||||
|
use hw_virtio::queue::VirtioQueue;
|
||||||
use port_block_device::{BlockDevice, SectorId};
|
use port_block_device::{BlockDevice, SectorId};
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
|
||||||
|
use types::bounded::PageCount;
|
||||||
|
|
||||||
const MMIO_BASE: usize = 0x20000;
|
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
|
||||||
const VQUEUE_VA: usize = 0x21000;
|
/// each), clear of program images and heaps.
|
||||||
const REQ_HDR_VA: usize = 0x23000;
|
const MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D0_0000);
|
||||||
|
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D0_1000);
|
||||||
|
const REQ_HDR_VA: VirtualAddress = VirtualAddress::from_raw(0x01D0_3000);
|
||||||
const SECTOR_SIZE: usize = 512;
|
const SECTOR_SIZE: usize = 512;
|
||||||
|
|
||||||
struct UserMmio;
|
|
||||||
|
|
||||||
impl MmioAccess for UserMmio {
|
|
||||||
fn read(&self, offset: usize) -> u32 {
|
|
||||||
unsafe { core::ptr::read_volatile((MMIO_BASE + offset) as *const u32) }
|
|
||||||
}
|
|
||||||
fn write(&self, offset: usize, val: u32) {
|
|
||||||
unsafe { core::ptr::write_volatile((MMIO_BASE + offset) as *mut u32, val) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VirtioBlockDevice {
|
pub struct VirtioBlockDevice {
|
||||||
mmio: UserMmio,
|
mmio: Registers,
|
||||||
queue: VirtioQueue,
|
queue: VirtioQueue,
|
||||||
dma: DmaAddrs,
|
dma: DmaAddrs,
|
||||||
|
sectors: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioBlockDevice {
|
impl VirtioBlockDevice {
|
||||||
/// Takes over the virtio block device behind `mmio`. `None` if it is
|
/// Takes over the virtio block device behind `mmio`. `None` if it is
|
||||||
/// not one or any step fails.
|
/// not one or any step fails.
|
||||||
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
libkos::device::map_mmio(mmio, MMIO_BASE).ok()?;
|
let regs = Registers::new(libkos::device::map_mmio(mmio, MMIO_VA).ok()?);
|
||||||
let regs = UserMmio;
|
|
||||||
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
|
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
|
||||||
let hdr_pa = libkos::device::dma(1, REQ_HDR_VA).ok()?;
|
let req = libkos::device::dma(PageCount::new(1)?, REQ_HDR_VA).ok()?;
|
||||||
hw_virtio::mmio::init_legacy_device(®s, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
|
hw_virtio::mmio::init_legacy_device(®s, rings.physical, hw_virtio::queue::QUEUE_SIZE as u32);
|
||||||
|
|
||||||
// Header, status byte and one sector share the request page.
|
// Header, status byte and one sector share the request page.
|
||||||
|
let at = |offset: usize| PhysicalAddress::from_raw(req.physical.raw() + offset);
|
||||||
let dma = DmaAddrs {
|
let dma = DmaAddrs {
|
||||||
header_pa: PhysicalAddress::from_raw(hdr_pa),
|
header_pa: at(0),
|
||||||
header_va: VirtualAddress::from_raw(REQ_HDR_VA),
|
header: req.window.sub(ByteOffset::new(0), 16),
|
||||||
status_pa: PhysicalAddress::from_raw(hdr_pa + 16),
|
status_pa: at(16),
|
||||||
status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16),
|
status: req.window.sub(ByteOffset::new(16), 1),
|
||||||
data_pa: PhysicalAddress::from_raw(hdr_pa + 32),
|
data_pa: at(32),
|
||||||
data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32),
|
data: req.window.sub(ByteOffset::new(32), SECTOR_SIZE),
|
||||||
};
|
};
|
||||||
|
let capacity = regs.read(block::CONFIG_CAPACITY) as u64
|
||||||
|
| (regs.read(block::CONFIG_CAPACITY + 4) as u64) << 32;
|
||||||
Some(Self {
|
Some(Self {
|
||||||
mmio: UserMmio,
|
mmio: regs,
|
||||||
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)),
|
queue: VirtioQueue::new(rings.window),
|
||||||
dma,
|
dma,
|
||||||
|
sectors: usize::try_from(capacity).ok()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockDevice for VirtioBlockDevice {
|
impl BlockDevice for VirtioBlockDevice {
|
||||||
fn sector_size(&self) -> usize { SECTOR_SIZE }
|
fn sector_size(&self) -> usize { SECTOR_SIZE }
|
||||||
fn sector_count(&self) -> usize { 0xFFFF }
|
fn sector_count(&self) -> usize { self.sectors }
|
||||||
|
|
||||||
fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool {
|
fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool {
|
||||||
if buf.len() < SECTOR_SIZE { return false; }
|
if buf.len() < SECTOR_SIZE || sector.raw() as usize >= self.sectors { return false; }
|
||||||
let mut sector_buf = [0u8; SECTOR_SIZE];
|
let mut sector_buf = [0u8; SECTOR_SIZE];
|
||||||
if !block::read_sector(&self.mmio, &self.queue, &self.dma, sector.raw(), &mut sector_buf) {
|
if !block::read_sector(&self.mmio, &self.queue, &self.dma, sector.raw(), &mut sector_buf) {
|
||||||
return false;
|
return false;
|
||||||
@@ -74,8 +71,8 @@ impl BlockDevice for VirtioBlockDevice {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_sector(&mut self, _sector: SectorId, _buf: &[u8]) -> bool { false }
|
fn write_sector(&mut self, sector: SectorId, buf: &[u8]) -> bool {
|
||||||
|
if sector.raw() as usize >= self.sectors { return false; }
|
||||||
/// Writing is not implemented yet.
|
block::write_sector(&self.mmio, &self.queue, &self.dma, sector.raw(), buf)
|
||||||
fn writable(&self) -> bool { false }
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use port_framebuffer::Framebuffer;
|
use port_framebuffer::Framebuffer;
|
||||||
use types::display::{Color, Dimension, ScreenCoord};
|
use types::display::{Color, Dimension, ScreenCoord};
|
||||||
@@ -9,6 +10,7 @@ pub struct MockFramebuffer {
|
|||||||
pixels: [(ScreenCoord, ScreenCoord, Color); 4096],
|
pixels: [(ScreenCoord, ScreenCoord, Color); 4096],
|
||||||
pixel_count: usize,
|
pixel_count: usize,
|
||||||
flushed: bool,
|
flushed: bool,
|
||||||
|
scrolls: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockFramebuffer {
|
impl MockFramebuffer {
|
||||||
@@ -19,6 +21,7 @@ impl MockFramebuffer {
|
|||||||
pixels: [(ScreenCoord::new(0), ScreenCoord::new(0), Color::from_raw(0)); 4096],
|
pixels: [(ScreenCoord::new(0), ScreenCoord::new(0), Color::from_raw(0)); 4096],
|
||||||
pixel_count: 0,
|
pixel_count: 0,
|
||||||
flushed: false,
|
flushed: false,
|
||||||
|
scrolls: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +29,11 @@ impl MockFramebuffer {
|
|||||||
self.pixel_count
|
self.pixel_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How many times the screen was scrolled.
|
||||||
|
pub fn scrolls(&self) -> usize {
|
||||||
|
self.scrolls
|
||||||
|
}
|
||||||
|
|
||||||
pub fn was_flushed(&self) -> bool {
|
pub fn was_flushed(&self) -> bool {
|
||||||
self.flushed
|
self.flushed
|
||||||
}
|
}
|
||||||
@@ -61,4 +69,72 @@ impl Framebuffer for MockFramebuffer {
|
|||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
self.flushed = true;
|
self.flushed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_up(&mut self, _top: ScreenCoord, _height: Dimension, _by: Dimension, _fill: Color) {
|
||||||
|
self.scrolls += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A screen of `W` by `H` pixels that keeps what is drawn, for tests that
|
||||||
|
/// look at the picture rather than the calls.
|
||||||
|
pub struct GridFramebuffer<const W: usize, const H: usize> {
|
||||||
|
pixels: [[Color; W]; H],
|
||||||
|
pub flushes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const W: usize, const H: usize> GridFramebuffer<W, H> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { pixels: [[Color::from_raw(0); W]; H], flushes: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pixel at (x, y), `None` off the screen.
|
||||||
|
pub fn pixel(&self, x: usize, y: usize) -> Option<Color> {
|
||||||
|
self.pixels.get(y).and_then(|row| row.get(x)).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const W: usize, const H: usize> Default for GridFramebuffer<W, H> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const W: usize, const H: usize> Framebuffer for GridFramebuffer<W, H> {
|
||||||
|
fn width(&self) -> Dimension {
|
||||||
|
Dimension::new(W as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> Dimension {
|
||||||
|
Dimension::new(H as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
||||||
|
if let Some(px) = self.pixels.get_mut(y.raw() as usize).and_then(|r| r.get_mut(x.raw() as usize)) {
|
||||||
|
*px = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
|
||||||
|
for row in y.raw()..y.raw().saturating_add(h.raw()) {
|
||||||
|
for col in x.raw()..x.raw().saturating_add(w.raw()) {
|
||||||
|
self.write_pixel(ScreenCoord::new(col), ScreenCoord::new(row), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, color: Color) {
|
||||||
|
self.pixels = [[color; W]; H];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) {
|
||||||
|
self.flushes += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
|
||||||
|
let (top, height, by) = (top.raw() as usize, height.raw() as usize, by.raw() as usize);
|
||||||
|
let end = (top + height).min(H);
|
||||||
|
for y in top..end {
|
||||||
|
self.pixels[y] = if y + by < end { self.pixels[y + by] } else { [fill; W] };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,68 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use hw_virtio::mmio::MmioAccess;
|
|
||||||
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
|
|
||||||
use hw_virtio::gpu::{self, GpuDmaAddrs};
|
use hw_virtio::gpu::{self, GpuDmaAddrs};
|
||||||
|
use hw_virtio::mmio::{MmioAccess, Registers};
|
||||||
|
use hw_virtio::queue::VirtioQueue;
|
||||||
|
use libkos::Window;
|
||||||
use port_framebuffer::Framebuffer;
|
use port_framebuffer::Framebuffer;
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
|
||||||
|
use types::bounded::PageCount;
|
||||||
use types::display::{Color, Dimension, ScreenCoord};
|
use types::display::{Color, Dimension, ScreenCoord};
|
||||||
|
|
||||||
const GPU_MMIO_VA: usize = 0x20000;
|
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
|
||||||
const VQUEUE_VA: usize = 0x21000;
|
/// each), clear of program images and heaps.
|
||||||
const CMDBUF_VA: usize = 0x23000;
|
const GPU_MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D2_0000);
|
||||||
const FB_VA: usize = 0x100000;
|
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D2_1000);
|
||||||
|
const CMDBUF_VA: VirtualAddress = VirtualAddress::from_raw(0x01D2_3000);
|
||||||
|
const FB_VA: VirtualAddress = VirtualAddress::from_raw(0x100000);
|
||||||
const FB_BPP: usize = 4;
|
const FB_BPP: usize = 4;
|
||||||
|
/// Pixels converted and stored per run.
|
||||||
|
const RUN: usize = 64;
|
||||||
const RESOURCE_ID: u32 = 1;
|
const RESOURCE_ID: u32 = 1;
|
||||||
/// When the device will not say.
|
/// When the device will not say.
|
||||||
const DEFAULT_SIZE: (u16, u16) = (640, 480);
|
const DEFAULT_SIZE: (u16, u16) = (640, 480);
|
||||||
|
|
||||||
struct GpuMmio;
|
|
||||||
|
|
||||||
impl MmioAccess for GpuMmio {
|
|
||||||
fn read(&self, offset: usize) -> u32 {
|
|
||||||
unsafe { core::ptr::read_volatile((GPU_MMIO_VA + offset) as *const u32) }
|
|
||||||
}
|
|
||||||
fn write(&self, offset: usize, val: u32) {
|
|
||||||
unsafe { core::ptr::write_volatile((GPU_MMIO_VA + offset) as *mut u32, val) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VirtioFramebuffer {
|
pub struct VirtioFramebuffer {
|
||||||
mmio: GpuMmio,
|
mmio: Registers,
|
||||||
queue: VirtioQueue,
|
queue: VirtioQueue,
|
||||||
dma: GpuDmaAddrs,
|
dma: GpuDmaAddrs,
|
||||||
|
/// The pixels the device scans out, `stride` bytes a row.
|
||||||
|
pixels: Window,
|
||||||
width: Dimension,
|
width: Dimension,
|
||||||
height: Dimension,
|
height: Dimension,
|
||||||
stride: usize,
|
stride: usize,
|
||||||
|
/// What changed since the last flush, as corners: only that goes to
|
||||||
|
/// the host.
|
||||||
|
dirty: Option<(usize, usize, usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `c` as the screen stores it: the bytes R, G, B, then one ignored.
|
||||||
|
fn word(c: Color) -> u32 {
|
||||||
|
u32::from_le_bytes(c.to_rgba())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioFramebuffer {
|
impl VirtioFramebuffer {
|
||||||
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
|
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
|
||||||
/// scanout. `None` if it is not a GPU or any step fails.
|
/// scanout. `None` if it is not a GPU or any step fails.
|
||||||
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?;
|
let regs = Registers::new(libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?);
|
||||||
let regs = GpuMmio;
|
|
||||||
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
|
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
|
||||||
let cmd_pa = libkos::device::dma(1, CMDBUF_VA).ok()?;
|
let cmd = libkos::device::dma(PageCount::new(1)?, CMDBUF_VA).ok()?;
|
||||||
hw_virtio::mmio::init_legacy_device(®s, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
|
hw_virtio::mmio::init_legacy_device(®s, rings.physical, hw_virtio::queue::QUEUE_SIZE as u32);
|
||||||
|
|
||||||
let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA));
|
let queue = VirtioQueue::new(rings.window);
|
||||||
// Commands go out from the start of the page, responses come back
|
// Commands go out from the start of the page, responses come back
|
||||||
// in its second half.
|
// in its second half.
|
||||||
let dma = GpuDmaAddrs {
|
let dma = GpuDmaAddrs {
|
||||||
cmd_pa: PhysicalAddress::from_raw(cmd_pa),
|
cmd_pa: cmd.physical,
|
||||||
cmd_va: VirtualAddress::from_raw(CMDBUF_VA),
|
cmd: cmd.window,
|
||||||
resp_pa: PhysicalAddress::from_raw(cmd_pa + 512),
|
resp_pa: PhysicalAddress::from_raw(cmd.physical.raw() + gpu::RESPONSE.raw()),
|
||||||
};
|
};
|
||||||
let (w, h) = gpu::get_display_info(®s, &queue, &dma)
|
let (w, h) = gpu::get_display_info(®s, &queue, &dma)
|
||||||
.and_then(|(w, h)| Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?)))
|
.and_then(|(w, h)| Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?)))
|
||||||
@@ -65,55 +70,124 @@ impl VirtioFramebuffer {
|
|||||||
.unwrap_or(DEFAULT_SIZE);
|
.unwrap_or(DEFAULT_SIZE);
|
||||||
let (width, height) = (Dimension::new(w), Dimension::new(h));
|
let (width, height) = (Dimension::new(w), Dimension::new(h));
|
||||||
let fb_bytes = w as usize * h as usize * FB_BPP;
|
let fb_bytes = w as usize * h as usize * FB_BPP;
|
||||||
let fb_pages = u16::try_from(fb_bytes.div_ceil(4096)).ok()?;
|
let fb_pages = PageCount::new(u16::try_from(fb_bytes.div_ceil(4096)).ok()?)?;
|
||||||
let fb_pa = PhysicalAddress::from_raw(libkos::device::dma(fb_pages, FB_VA).ok()?);
|
let fb = libkos::device::dma(fb_pages, FB_VA).ok()?;
|
||||||
|
|
||||||
let ready = gpu::create_2d(®s, &queue, &dma, RESOURCE_ID, width, height)
|
let ready = gpu::create_2d(®s, &queue, &dma, RESOURCE_ID, width, height)
|
||||||
&& gpu::attach_backing(®s, &queue, &dma, RESOURCE_ID, fb_pa, fb_bytes as u32)
|
&& gpu::attach_backing(®s, &queue, &dma, RESOURCE_ID, fb.physical, fb_bytes as u32)
|
||||||
&& gpu::set_scanout(®s, &queue, &dma, 0, RESOURCE_ID, width, height);
|
&& gpu::set_scanout(®s, &queue, &dma, 0, RESOURCE_ID, width, height);
|
||||||
if !ready {
|
if !ready {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(Self {
|
Some(Self {
|
||||||
mmio: GpuMmio,
|
mmio: regs,
|
||||||
queue,
|
queue,
|
||||||
dma,
|
dma,
|
||||||
|
pixels: fb.window,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
stride: w as usize * FB_BPP,
|
stride: w as usize * FB_BPP,
|
||||||
|
dirty: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Framebuffer for VirtioFramebuffer {
|
impl VirtioFramebuffer {
|
||||||
fn width(&self) -> Dimension { self.width }
|
fn pixel(&self, x: usize, y: usize) -> ByteOffset {
|
||||||
fn height(&self) -> Dimension { self.height }
|
ByteOffset::new(y * self.stride + x * FB_BPP)
|
||||||
|
|
||||||
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
|
||||||
if x.raw() >= self.width.raw() || y.raw() >= self.height.raw() { return; }
|
|
||||||
let offset = (y.raw() as usize) * self.stride + (x.raw() as usize) * FB_BPP;
|
|
||||||
unsafe { core::ptr::write_volatile((FB_VA + offset) as *mut u32, color.raw()); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
|
/// Adds the rectangle from (x0, y0) up to (x1, y1) to what changed.
|
||||||
for dy in 0..h.raw() {
|
fn touch(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
|
||||||
for dx in 0..w.raw() {
|
if x0 >= x1 || y0 >= y1 {
|
||||||
self.write_pixel(ScreenCoord::new(x.raw() + dx), ScreenCoord::new(y.raw() + dy), color);
|
return;
|
||||||
|
}
|
||||||
|
self.dirty = Some(match self.dirty {
|
||||||
|
None => (x0, y0, x1, y1),
|
||||||
|
Some((a, b, c, d)) => (a.min(x0), b.min(y0), c.max(x1), d.max(y1)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Framebuffer for VirtioFramebuffer {
|
||||||
|
fn width(&self) -> Dimension {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> Dimension {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_rgba_row(&mut self, x: ScreenCoord, y: ScreenCoord, bytes: &[u8]) {
|
||||||
|
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
|
||||||
|
let (x, y) = (x.raw() as usize, y.raw() as usize);
|
||||||
|
if x >= w || y >= h {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let n = (bytes.len() / FB_BPP).min(w - x);
|
||||||
|
self.touch(x, y, x + n, y + 1);
|
||||||
|
self.pixels.copy_from(self.pixel(x, y), &bytes[..n * FB_BPP]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
||||||
|
self.write_row(x, y, &[color]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_row(&mut self, x: ScreenCoord, y: ScreenCoord, pixels: &[Color]) {
|
||||||
|
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
|
||||||
|
let (x, y) = (x.raw() as usize, y.raw() as usize);
|
||||||
|
if x >= w || y >= h {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let n = pixels.len().min(w - x);
|
||||||
|
self.touch(x, y, x + n, y + 1);
|
||||||
|
let mut run = [0u32; RUN];
|
||||||
|
let mut at = self.pixel(x, y);
|
||||||
|
for chunk in pixels[..n].chunks(RUN) {
|
||||||
|
for (slot, c) in run.iter_mut().zip(chunk) {
|
||||||
|
*slot = word(*c);
|
||||||
}
|
}
|
||||||
|
self.pixels.write_u32s(at, &run[..chunk.len()]);
|
||||||
|
at = at + chunk.len() * FB_BPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callers clip to the screen (the display server does).
|
||||||
|
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
|
||||||
|
let (fw, fh) = (self.width.raw() as usize, self.height.raw() as usize);
|
||||||
|
let (x, y) = (x.raw() as usize, y.raw() as usize);
|
||||||
|
let w = (w.raw() as usize).min(fw.saturating_sub(x));
|
||||||
|
let bottom = (y + h.raw() as usize).min(fh);
|
||||||
|
self.touch(x, y, x + w, bottom);
|
||||||
|
for row in y..bottom {
|
||||||
|
self.pixels.fill_u32(self.pixel(x, row), w, word(color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear(&mut self, color: Color) {
|
fn clear(&mut self, color: Color) {
|
||||||
let total = self.width.raw() as usize * self.height.raw() as usize;
|
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
|
||||||
for i in 0..total {
|
self.touch(0, 0, w, h);
|
||||||
unsafe { core::ptr::write_volatile((FB_VA as *mut u32).add(i), color.raw()); }
|
self.pixels.fill_u32(ByteOffset::ZERO, w * h, word(color));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
|
||||||
|
let fh = self.height.raw() as usize;
|
||||||
|
let top = top.raw() as usize;
|
||||||
|
let bottom = (top + height.raw() as usize).min(fh);
|
||||||
|
let by = (by.raw() as usize).min(bottom.saturating_sub(top));
|
||||||
|
let moved = bottom - top - by;
|
||||||
|
self.touch(0, top, self.width.raw() as usize, bottom);
|
||||||
|
self.pixels.copy_within(self.pixel(0, top + by), self.pixel(0, top), moved * self.stride);
|
||||||
|
let width = self.width.raw() as usize;
|
||||||
|
self.pixels.fill_u32(self.pixel(0, bottom - by), by * width, word(fill));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the host only what changed since the last flush.
|
||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
let w = self.width.raw() as u32;
|
let Some((x0, y0, x1, y1)) = self.dirty.take() else { return };
|
||||||
let h = self.height.raw() as u32;
|
let r = gpu::Rect { x: x0 as u32, y: y0 as u32, w: (x1 - x0) as u32, h: (y1 - y0) as u32 };
|
||||||
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
|
let offset = self.pixel(x0, y0).raw() as u64;
|
||||||
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, 0, 0, w, h);
|
gpu::transfer_to_host(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, r, offset);
|
||||||
|
gpu::resource_flush(&self.mmio, &self.queue, &self.dma, RESOURCE_ID, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,36 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use libkos::Window;
|
||||||
use port_input_device::{InputDevice, InputEvent, Key};
|
use port_input_device::{InputDevice, InputEvent, Key};
|
||||||
|
use types::address::{ByteOffset, VirtualAddress};
|
||||||
|
|
||||||
const UART_VA: usize = 0x20000;
|
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
|
||||||
const UART_IER_OFFSET: usize = 1;
|
/// each), clear of program images and heaps.
|
||||||
const UART_LSR_OFFSET: usize = 5;
|
const UART_VA: VirtualAddress = VirtualAddress::from_raw(0x01D4_0000);
|
||||||
|
const UART_RBR: ByteOffset = ByteOffset::new(0);
|
||||||
|
const UART_IER_OFFSET: ByteOffset = ByteOffset::new(1);
|
||||||
|
const UART_LSR_OFFSET: ByteOffset = ByteOffset::new(5);
|
||||||
/// IER bit: interrupt when a received byte is waiting (cleared by reading
|
/// IER bit: interrupt when a received byte is waiting (cleared by reading
|
||||||
/// it, so `acknowledge` has nothing to do).
|
/// it, so `acknowledge` has nothing to do).
|
||||||
const IER_RX_READY: u8 = 1;
|
const IER_RX_READY: u8 = 1;
|
||||||
|
|
||||||
pub struct UartInputDevice;
|
/// The UART's registers, one byte each.
|
||||||
|
pub struct UartInputDevice {
|
||||||
|
regs: Window,
|
||||||
|
}
|
||||||
|
|
||||||
impl UartInputDevice {
|
impl UartInputDevice {
|
||||||
/// Maps the 16550 UART whose region `mmio` names and enables its
|
/// Maps the 16550 UART whose region `mmio` names and enables its
|
||||||
/// receive interrupt.
|
/// receive interrupt.
|
||||||
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
libkos::device::map_mmio(mmio, UART_VA).ok()?;
|
let regs = libkos::device::map_mmio(mmio, UART_VA).ok()?;
|
||||||
// SAFETY: UART_VA now maps the UART's registers.
|
regs.write(UART_IER_OFFSET, IER_RX_READY);
|
||||||
unsafe { core::ptr::write_volatile((UART_VA + UART_IER_OFFSET) as *mut u8, IER_RX_READY) };
|
Some(Self { regs })
|
||||||
Some(Self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_byte(&self) -> Option<u8> {
|
fn read_byte(&self) -> Option<u8> {
|
||||||
let lsr = unsafe { core::ptr::read_volatile((UART_VA + UART_LSR_OFFSET) as *const u8) };
|
self.has_event().then(|| self.regs.read(UART_RBR))
|
||||||
if lsr & 1 != 0 {
|
|
||||||
Some(unsafe { core::ptr::read_volatile(UART_VA as *const u8) })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn byte_to_key(byte: u8) -> Key {
|
fn byte_to_key(byte: u8) -> Key {
|
||||||
@@ -50,7 +53,6 @@ impl InputDevice for UartInputDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn has_event(&self) -> bool {
|
fn has_event(&self) -> bool {
|
||||||
let lsr = unsafe { core::ptr::read_volatile((UART_VA + UART_LSR_OFFSET) as *const u8) };
|
self.regs.read::<u8>(UART_LSR_OFFSET) & 1 != 0
|
||||||
lsr & 1 != 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
crates/drivers/input/virtio/src/keymap.rs
Normal file
100
crates/drivers/input/virtio/src/keymap.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
//! Linux key codes (what virtio-input reports) to keys, US layout.
|
||||||
|
|
||||||
|
use port_input_device::Key;
|
||||||
|
|
||||||
|
pub const LEFT_SHIFT: u16 = 42;
|
||||||
|
pub const RIGHT_SHIFT: u16 = 54;
|
||||||
|
pub const LEFT_CTRL: u16 = 29;
|
||||||
|
pub const RIGHT_CTRL: u16 = 97;
|
||||||
|
pub const LEFT_ALT: u16 = 56;
|
||||||
|
pub const RIGHT_ALT: u16 = 100;
|
||||||
|
/// Alt+Tab is the display's window switch, the same key as F1
|
||||||
|
/// (`types::display::window::SWITCH_KEY`).
|
||||||
|
const SWITCH: Key = Key::Function(1);
|
||||||
|
|
||||||
|
/// What a key is with Alt held: only Alt+Tab means anything.
|
||||||
|
pub fn with_alt(key: Key) -> Option<Key> {
|
||||||
|
(key == Key::Tab).then_some(SWITCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A letter typed with Ctrl held is its control character, as on a
|
||||||
|
/// terminal: Ctrl+C is 3. Anything else is itself.
|
||||||
|
pub fn with_ctrl(key: Key) -> Key {
|
||||||
|
match key {
|
||||||
|
Key::Char(c) if c.is_ascii_alphabetic() => Key::Char(c.to_ascii_lowercase() & 0x1f),
|
||||||
|
k => k,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rows of the main block: the first key's code, then each key unshifted
|
||||||
|
/// and shifted.
|
||||||
|
const ROWS: [(u16, &[u8], &[u8]); 4] = [
|
||||||
|
(2, b"1234567890-=", b"!@#$%^&*()_+"),
|
||||||
|
(16, b"qwertyuiop[]", b"QWERTYUIOP{}"),
|
||||||
|
(30, b"asdfghjkl;'`", b"ASDFGHJKL:\"~"),
|
||||||
|
(44, b"zxcvbnm,./", b"ZXCVBNM<>?"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn key(code: u16, shift: bool) -> Option<Key> {
|
||||||
|
let named = match code {
|
||||||
|
1 => Some(Key::Escape),
|
||||||
|
14 => Some(Key::Backspace),
|
||||||
|
15 => Some(Key::Tab),
|
||||||
|
28 => Some(Key::Enter),
|
||||||
|
57 => Some(Key::Space),
|
||||||
|
103 => Some(Key::Up),
|
||||||
|
108 => Some(Key::Down),
|
||||||
|
105 => Some(Key::Left),
|
||||||
|
106 => Some(Key::Right),
|
||||||
|
43 => Some(Key::Char(if shift { b'|' } else { b'\\' })),
|
||||||
|
59..=68 => Some(Key::Function((code - 58) as u8)),
|
||||||
|
87 | 88 => Some(Key::Function((code - 76) as u8)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
named.or_else(|| {
|
||||||
|
ROWS.iter().find_map(|(first, plain, shifted)| {
|
||||||
|
let i = usize::from(code.checked_sub(*first)?);
|
||||||
|
let row = if shift { shifted } else { plain };
|
||||||
|
row.get(i).map(|c| Key::Char(*c))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alt_tab_switches_windows_and_other_alt_keys_are_nothing() {
|
||||||
|
assert_eq!(with_alt(Key::Tab), Some(Key::Function(1)));
|
||||||
|
assert_eq!(with_alt(Key::Char(b'a')), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_keys_are_numbered() {
|
||||||
|
assert_eq!(key(59, false), Some(Key::Function(1)));
|
||||||
|
assert_eq!(key(68, false), Some(Key::Function(10)));
|
||||||
|
assert_eq!(key(88, true), Some(Key::Function(12)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ctrl_letters_are_control_characters() {
|
||||||
|
assert_eq!(with_ctrl(Key::Char(b'c')), Key::Char(3));
|
||||||
|
assert_eq!(with_ctrl(Key::Char(b'C')), Key::Char(3));
|
||||||
|
assert_eq!(with_ctrl(Key::Char(b'1')), Key::Char(b'1'));
|
||||||
|
assert_eq!(with_ctrl(Key::Enter), Key::Enter);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn letters_digits_and_shift() {
|
||||||
|
assert_eq!(key(35, false), Some(Key::Char(b'h')));
|
||||||
|
assert_eq!(key(35, true), Some(Key::Char(b'H')));
|
||||||
|
assert_eq!(key(11, false), Some(Key::Char(b'0')));
|
||||||
|
assert_eq!(key(12, true), Some(Key::Char(b'_')));
|
||||||
|
assert_eq!(key(53, false), Some(Key::Char(b'/')));
|
||||||
|
assert_eq!(key(41, true), Some(Key::Char(b'~')));
|
||||||
|
assert_eq!(key(28, false), Some(Key::Enter));
|
||||||
|
assert_eq!(key(103, false), Some(Key::Up));
|
||||||
|
assert_eq!(key(29, false), None, "left ctrl is not a character");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,139 +1,126 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use hw_virtio::mmio::MmioAccess;
|
mod keymap;
|
||||||
use port_input_device::{InputDevice, InputEvent, Key};
|
mod pointer;
|
||||||
|
|
||||||
const INPUT_MMIO_VA: usize = 0x25000;
|
use core::sync::atomic::{Ordering, fence};
|
||||||
const VQUEUE_VA: usize = 0x26000;
|
use hw_virtio::mmio::{MmioAccess, Registers};
|
||||||
const EVBUF_VA: usize = 0x28000;
|
use libkos::Window;
|
||||||
|
use port_input_device::{InputDevice, InputEvent};
|
||||||
|
use types::address::{ByteOffset, PhysicalAddress, VirtualAddress};
|
||||||
|
use types::bounded::PageCount;
|
||||||
|
|
||||||
|
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
|
||||||
|
/// each), clear of program images and heaps; an input device takes 16 KiB
|
||||||
|
/// of it, the `n`th from here (a keyboard, a tablet).
|
||||||
|
const INPUT_VA: usize = 0x01D1_0000;
|
||||||
|
const INPUT_SPAN: usize = 0x4000;
|
||||||
|
/// Its registers, its queue's rings (two pages), its event buffers.
|
||||||
|
const MMIO_AT: usize = 0;
|
||||||
|
const VQUEUE_AT: usize = 0x1000;
|
||||||
|
const EVBUF_AT: usize = 0x3000;
|
||||||
|
/// How many may be driven at once in one program.
|
||||||
|
pub const MAX_INPUTS: usize = 4;
|
||||||
|
|
||||||
const QUEUE_SIZE: usize = 32;
|
const QUEUE_SIZE: usize = 32;
|
||||||
const EVENT_SIZE: usize = 8;
|
const EVENT_SIZE: usize = 8;
|
||||||
|
const DESC_SIZE: usize = 16;
|
||||||
|
/// Where the legacy layout puts the available and used rings.
|
||||||
|
const AVAIL: ByteOffset = ByteOffset::new(QUEUE_SIZE * DESC_SIZE);
|
||||||
|
const USED: ByteOffset = ByteOffset::new(4096);
|
||||||
|
|
||||||
const EV_KEY: u16 = 1;
|
const EV_KEY: u16 = 1;
|
||||||
const KEY_PRESS: u32 = 1;
|
const KEY_PRESS: u32 = 1;
|
||||||
|
|
||||||
struct MmioInput;
|
|
||||||
|
|
||||||
impl hw_virtio::mmio::MmioAccess for MmioInput {
|
|
||||||
fn read(&self, offset: usize) -> u32 {
|
|
||||||
unsafe { core::ptr::read_volatile((INPUT_MMIO_VA + offset) as *const u32) }
|
|
||||||
}
|
|
||||||
fn write(&self, offset: usize, val: u32) {
|
|
||||||
unsafe { core::ptr::write_volatile((INPUT_MMIO_VA + offset) as *mut u32, val) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VirtioInputDevice {
|
pub struct VirtioInputDevice {
|
||||||
|
regs: Registers,
|
||||||
|
/// The queue's rings, and the buffers the device writes events into.
|
||||||
|
rings: Window,
|
||||||
|
events: Window,
|
||||||
|
events_pa: PhysicalAddress,
|
||||||
last_used: u16,
|
last_used: u16,
|
||||||
next_avail: u16,
|
next_avail: u16,
|
||||||
evbuf_pa: usize,
|
shift: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
alt: bool,
|
||||||
|
/// Where a tablet's pointer is, between its reports.
|
||||||
|
pointer: pointer::Pointer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioInputDevice {
|
impl VirtioInputDevice {
|
||||||
/// Takes over the virtio input device behind `mmio`. `None` if it is
|
/// Takes over the virtio input device behind `mmio`, as this program's
|
||||||
/// not one or any step fails.
|
/// `n`th (each has addresses of its own). `None` if it is not one or
|
||||||
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
/// any step fails.
|
||||||
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?;
|
pub fn try_new(mmio: types::bounded::CapSlot, n: usize) -> Option<Self> {
|
||||||
let regs = MmioInput;
|
if n >= MAX_INPUTS {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let at = |offset: usize| VirtualAddress::from_raw(INPUT_VA + n * INPUT_SPAN + offset);
|
||||||
|
let regs = Registers::new(libkos::device::map_mmio(mmio, at(MMIO_AT)).ok()?);
|
||||||
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
|
let rings = libkos::device::dma(PageCount::new(2)?, at(VQUEUE_AT)).ok()?;
|
||||||
let evbuf_pa = libkos::device::dma(1, EVBUF_VA).ok()?;
|
let events = libkos::device::dma(PageCount::new(1)?, at(EVBUF_AT)).ok()?;
|
||||||
hw_virtio::mmio::init_legacy_device(®s, vq_pa, QUEUE_SIZE as u32);
|
hw_virtio::mmio::init_legacy_device(®s, rings.physical, QUEUE_SIZE as u32);
|
||||||
|
|
||||||
let mut dev = Self { last_used: 0, next_avail: 0, evbuf_pa };
|
let mut dev = Self {
|
||||||
|
regs,
|
||||||
|
rings: rings.window,
|
||||||
|
events: events.window,
|
||||||
|
events_pa: events.physical,
|
||||||
|
last_used: 0,
|
||||||
|
next_avail: 0,
|
||||||
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
pointer: pointer::Pointer::default(),
|
||||||
|
};
|
||||||
dev.fill_queue();
|
dev.fill_queue();
|
||||||
Some(dev)
|
Some(dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every buffer goes to the device, one event each.
|
||||||
fn fill_queue(&mut self) {
|
fn fill_queue(&mut self) {
|
||||||
let evbuf_phys = self.evbuf_pa;
|
|
||||||
let desc_base = VQUEUE_VA;
|
|
||||||
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
|
|
||||||
|
|
||||||
for i in 0..QUEUE_SIZE {
|
for i in 0..QUEUE_SIZE {
|
||||||
let desc = desc_base + i * 16;
|
let desc = ByteOffset::new(i * DESC_SIZE);
|
||||||
let buf_pa = evbuf_phys + i * EVENT_SIZE;
|
self.rings.write(desc, (self.events_pa.raw() + i * EVENT_SIZE) as u64);
|
||||||
unsafe {
|
self.rings.write(desc + 8, EVENT_SIZE as u32);
|
||||||
core::ptr::write_volatile(desc as *mut u64, buf_pa as u64);
|
self.rings.write(desc + 12, 2u16); // WRITE flag
|
||||||
core::ptr::write_volatile((desc + 8) as *mut u32, EVENT_SIZE as u32);
|
self.rings.write(desc + 14, 0u16); // no next
|
||||||
core::ptr::write_volatile((desc + 12) as *mut u16, 2); // WRITE flag
|
self.rings.write(AVAIL + 4 + i * 2, i as u16);
|
||||||
core::ptr::write_volatile((desc + 14) as *mut u16, 0); // no next
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let ring_entry = avail_base + 4 + (i % QUEUE_SIZE) * 2;
|
|
||||||
core::ptr::write_volatile(ring_entry as *mut u16, i as u16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
fence(Ordering::SeqCst);
|
||||||
unsafe {
|
self.rings.write(AVAIL + 2, QUEUE_SIZE as u16);
|
||||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
fence(Ordering::SeqCst);
|
||||||
core::ptr::write_volatile((avail_base + 2) as *mut u16, QUEUE_SIZE as u16);
|
self.regs.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
|
||||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mmio = MmioInput;
|
|
||||||
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
|
|
||||||
self.next_avail = QUEUE_SIZE as u16;
|
self.next_avail = QUEUE_SIZE as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resubmit(&mut self, desc_idx: u16) {
|
fn resubmit(&mut self, desc_idx: u16) {
|
||||||
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
|
|
||||||
let slot = self.next_avail as usize % QUEUE_SIZE;
|
let slot = self.next_avail as usize % QUEUE_SIZE;
|
||||||
unsafe {
|
self.rings.write(AVAIL + 4 + slot * 2, desc_idx);
|
||||||
core::ptr::write_volatile((avail_base + 4 + slot * 2) as *mut u16, desc_idx);
|
fence(Ordering::SeqCst);
|
||||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
self.next_avail = self.next_avail.wrapping_add(1);
|
||||||
self.next_avail = self.next_avail.wrapping_add(1);
|
self.rings.write(AVAIL + 2, self.next_avail);
|
||||||
core::ptr::write_volatile((avail_base + 2) as *mut u16, self.next_avail);
|
fence(Ordering::SeqCst);
|
||||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
self.regs.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
|
||||||
}
|
|
||||||
let mmio = MmioInput;
|
|
||||||
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_event(&self, desc_idx: u16) -> (u16, u16, u32) {
|
fn read_event(&self, desc_idx: u16) -> (u16, u16, u32) {
|
||||||
let buf_va = EVBUF_VA + (desc_idx as usize) * EVENT_SIZE;
|
let at = ByteOffset::new(desc_idx as usize % QUEUE_SIZE * EVENT_SIZE);
|
||||||
unsafe {
|
(self.events.read(at), self.events.read(at + 2), self.events.read(at + 4))
|
||||||
let ev_type = core::ptr::read_volatile(buf_va as *const u16);
|
|
||||||
let ev_code = core::ptr::read_volatile((buf_va + 2) as *const u16);
|
|
||||||
let ev_value = core::ptr::read_volatile((buf_va + 4) as *const u32);
|
|
||||||
(ev_type, ev_code, ev_value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn used_idx(&self) -> u16 {
|
fn used_idx(&self) -> u16 {
|
||||||
let used_base = VQUEUE_VA + 4096;
|
self.rings.read(USED + 2)
|
||||||
unsafe { core::ptr::read_volatile((used_base + 2) as *const u16) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn used_entry_id(&self, idx: u16) -> u16 {
|
fn used_entry_id(&self, idx: u16) -> u16 {
|
||||||
let used_base = VQUEUE_VA + 4096;
|
self.rings.read::<u32>(USED + 4 + (idx as usize % QUEUE_SIZE) * 8) as u16
|
||||||
let entry = used_base + 4 + (idx as usize % QUEUE_SIZE) * 8;
|
|
||||||
unsafe { core::ptr::read_volatile(entry as *const u32) as u16 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn linux_keycode_to_key(code: u16) -> Option<Key> {
|
|
||||||
match code {
|
|
||||||
17 => Some(Key::Up), // KEY_W
|
|
||||||
31 => Some(Key::Down), // KEY_S
|
|
||||||
30 => Some(Key::Left), // KEY_A
|
|
||||||
32 => Some(Key::Right), // KEY_D
|
|
||||||
103 => Some(Key::Up), // KEY_UP
|
|
||||||
108 => Some(Key::Down), // KEY_DOWN
|
|
||||||
105 => Some(Key::Left), // KEY_LEFT
|
|
||||||
106 => Some(Key::Right),// KEY_RIGHT
|
|
||||||
28 => Some(Key::Enter), // KEY_ENTER
|
|
||||||
1 => Some(Key::Escape), // KEY_ESC
|
|
||||||
57 => Some(Key::Space), // KEY_SPACE
|
|
||||||
14 => Some(Key::Backspace), // KEY_BACKSPACE
|
|
||||||
15 => Some(Key::Tab), // KEY_TAB
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +138,25 @@ impl InputDevice for VirtioInputDevice {
|
|||||||
let (ev_type, ev_code, ev_value) = self.read_event(desc_idx);
|
let (ev_type, ev_code, ev_value) = self.read_event(desc_idx);
|
||||||
self.resubmit(desc_idx);
|
self.resubmit(desc_idx);
|
||||||
|
|
||||||
|
if let Some(event) = self.pointer.event(ev_type, ev_code, ev_value) {
|
||||||
|
return Some(event);
|
||||||
|
}
|
||||||
|
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_SHIFT | keymap::RIGHT_SHIFT) {
|
||||||
|
self.shift = ev_value != 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_CTRL | keymap::RIGHT_CTRL) {
|
||||||
|
self.ctrl = ev_value != 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ev_type == EV_KEY && matches!(ev_code, keymap::LEFT_ALT | keymap::RIGHT_ALT) {
|
||||||
|
self.alt = ev_value != 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ev_type == EV_KEY {
|
if ev_type == EV_KEY {
|
||||||
if let Some(key) = linux_keycode_to_key(ev_code) {
|
let key = keymap::key(ev_code, self.shift).map(|k| if self.ctrl { keymap::with_ctrl(k) } else { k });
|
||||||
|
let key = if self.alt { key.and_then(keymap::with_alt) } else { key };
|
||||||
|
if let Some(key) = key {
|
||||||
if ev_value == KEY_PRESS {
|
if ev_value == KEY_PRESS {
|
||||||
return Some(InputEvent::KeyDown(key));
|
return Some(InputEvent::KeyDown(key));
|
||||||
} else if ev_value == 0 {
|
} else if ev_value == 0 {
|
||||||
@@ -169,8 +173,7 @@ impl InputDevice for VirtioInputDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn acknowledge(&mut self) {
|
fn acknowledge(&mut self) {
|
||||||
let regs = MmioInput;
|
let status = self.regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
|
||||||
let status = regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
|
self.regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
|
||||||
regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
crates/drivers/input/virtio/src/pointer.rs
Normal file
79
crates/drivers/input/virtio/src/pointer.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
//! A tablet's events (Linux codes, as virtio-input reports them) to
|
||||||
|
//! pointer events: absolute axes collect until the report's sync, then a
|
||||||
|
//! move; buttons press and let go where the pointer is. Positions are
|
||||||
|
//! fractions of the screen, 0 to 65535 each way.
|
||||||
|
|
||||||
|
use port_input_device::InputEvent;
|
||||||
|
|
||||||
|
pub const EV_SYN: u16 = 0;
|
||||||
|
pub const EV_ABS: u16 = 3;
|
||||||
|
const ABS_X: u16 = 0;
|
||||||
|
const ABS_Y: u16 = 1;
|
||||||
|
const BTN_LEFT: u16 = 0x110;
|
||||||
|
const BTN_RIGHT: u16 = 0x111;
|
||||||
|
/// QEMU's tablet reports 0 to this on each axis.
|
||||||
|
const ABS_MAX: u32 = 0x7fff;
|
||||||
|
|
||||||
|
/// Button ids in pointer events.
|
||||||
|
pub const LEFT: u8 = 0;
|
||||||
|
pub const RIGHT: u8 = 1;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Pointer {
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
/// An axis changed since the last sync.
|
||||||
|
moved: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scaled(v: u32) -> u16 {
|
||||||
|
(v.min(ABS_MAX) * 0xffff / ABS_MAX) as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pointer {
|
||||||
|
/// What one event of `kind`, `code`, `value` means, if anything yet.
|
||||||
|
pub fn event(&mut self, kind: u16, code: u16, value: u32) -> Option<InputEvent> {
|
||||||
|
let (x, y) = (self.x, self.y);
|
||||||
|
match (kind, code) {
|
||||||
|
(EV_ABS, ABS_X) => {
|
||||||
|
self.x = scaled(value);
|
||||||
|
self.moved = true;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
(EV_ABS, ABS_Y) => {
|
||||||
|
self.y = scaled(value);
|
||||||
|
self.moved = true;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
(EV_SYN, _) if self.moved => {
|
||||||
|
self.moved = false;
|
||||||
|
Some(InputEvent::PointerMove { id: LEFT, x, y })
|
||||||
|
}
|
||||||
|
(super::EV_KEY, BTN_LEFT | BTN_RIGHT) => {
|
||||||
|
let id = if code == BTN_LEFT { LEFT } else { RIGHT };
|
||||||
|
match value {
|
||||||
|
0 => Some(InputEvent::PointerUp { id, x, y }),
|
||||||
|
1 => Some(InputEvent::PointerDown { id, x, y }),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn axes_move_on_sync_and_buttons_press_where_it_is() {
|
||||||
|
let mut p = Pointer::default();
|
||||||
|
assert_eq!(p.event(EV_ABS, ABS_X, 0x7fff), None);
|
||||||
|
assert_eq!(p.event(EV_ABS, ABS_Y, 0x4000), None);
|
||||||
|
assert_eq!(p.event(EV_SYN, 0, 0), Some(InputEvent::PointerMove { id: LEFT, x: 0xffff, y: 0x8000 }));
|
||||||
|
assert_eq!(p.event(EV_SYN, 0, 0), None, "nothing moved since");
|
||||||
|
assert_eq!(p.event(super::super::EV_KEY, BTN_LEFT, 1), Some(InputEvent::PointerDown { id: LEFT, x: 0xffff, y: 0x8000 }));
|
||||||
|
assert_eq!(p.event(super::super::EV_KEY, BTN_RIGHT, 0), Some(InputEvent::PointerUp { id: RIGHT, x: 0xffff, y: 0x8000 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
10
crates/drivers/rng/virtio/Cargo.toml
Normal file
10
crates/drivers/rng/virtio/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "driver-rng-virtio"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
types = { workspace = true }
|
||||||
|
libkos = { workspace = true }
|
||||||
|
hw-virtio = { workspace = true }
|
||||||
|
port-entropy = { workspace = true }
|
||||||
76
crates/drivers/rng/virtio/src/lib.rs
Normal file
76
crates/drivers/rng/virtio/src/lib.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
//! virtio-rng (virtio spec 5.4): the device fills a buffer with entropy.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use hw_virtio::mmio::{MmioAccess, Registers};
|
||||||
|
use hw_virtio::queue::{Descriptor, VirtioQueue};
|
||||||
|
use libkos::device::Dma;
|
||||||
|
use port_entropy::Entropy;
|
||||||
|
use types::address::{ByteOffset, VirtualAddress};
|
||||||
|
use types::bounded::PageCount;
|
||||||
|
|
||||||
|
/// Drivers map their devices in the device window (0x01D0_0000 up, 64 KiB
|
||||||
|
/// each), clear of program images and heaps.
|
||||||
|
const MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x01D3_0000);
|
||||||
|
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x01D3_1000);
|
||||||
|
const BUFFER_VA: VirtualAddress = VirtualAddress::from_raw(0x01D3_3000);
|
||||||
|
/// Asked for at most this much at a time.
|
||||||
|
const CHUNK: usize = 256;
|
||||||
|
|
||||||
|
pub struct VirtioRng {
|
||||||
|
regs: Registers,
|
||||||
|
queue: VirtioQueue,
|
||||||
|
buffer: Dma,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtioRng {
|
||||||
|
/// Takes over the virtio-rng device behind `mmio`; `None` if it is not
|
||||||
|
/// one or any step fails.
|
||||||
|
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
|
let regs = Registers::new(libkos::device::map_mmio(mmio, MMIO_VA).ok()?);
|
||||||
|
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|
||||||
|
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_RNG
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
|
||||||
|
let buffer = libkos::device::dma(PageCount::new(1)?, BUFFER_VA).ok()?;
|
||||||
|
hw_virtio::mmio::init_legacy_device(
|
||||||
|
®s,
|
||||||
|
rings.physical,
|
||||||
|
hw_virtio::queue::QUEUE_SIZE as u32,
|
||||||
|
);
|
||||||
|
Some(Self {
|
||||||
|
regs,
|
||||||
|
queue: VirtioQueue::new(rings.window),
|
||||||
|
buffer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entropy for VirtioRng {
|
||||||
|
fn fill(&mut self, buf: &mut [u8]) -> bool {
|
||||||
|
let mut done = 0;
|
||||||
|
while done < buf.len() {
|
||||||
|
let want = (buf.len() - done).min(CHUNK);
|
||||||
|
let desc = [Descriptor {
|
||||||
|
addr: self.buffer.physical.raw() as u64,
|
||||||
|
len: want as u32,
|
||||||
|
writable: true,
|
||||||
|
}];
|
||||||
|
let got = match self.queue.submit_written(&self.regs, &desc) {
|
||||||
|
Some(n) if n > 0 => (n as usize).min(want),
|
||||||
|
// The device gave nothing (or never answered): no randomness.
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
self.buffer
|
||||||
|
.window
|
||||||
|
.copy_to(ByteOffset::ZERO, &mut buf[done..done + got]);
|
||||||
|
done += got;
|
||||||
|
}
|
||||||
|
// What was handed out is not left lying in the buffer.
|
||||||
|
self.buffer.window.fill_u32(ByteOffset::ZERO, CHUNK / 4, 0);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
10
crates/filecrypt/Cargo.toml
Normal file
10
crates/filecrypt/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "filecrypt"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chacha20poly1305 = { workspace = true }
|
||||||
|
chacha20 = { workspace = true }
|
||||||
|
blake2 = { workspace = true }
|
||||||
|
zeroize = { workspace = true }
|
||||||
39
crates/filecrypt/src/drbg.rs
Normal file
39
crates/filecrypt/src/drbg.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//! Randomness from a seed: ChaCha20's keystream, re-keyed after every
|
||||||
|
//! request (fast key erasure), so what came out before cannot be worked
|
||||||
|
//! back from the state. The seed must be fresh each boot (the gatekeeper
|
||||||
|
//! draws it from the hardware), or nonces would repeat.
|
||||||
|
|
||||||
|
use chacha20::ChaCha20;
|
||||||
|
use chacha20::cipher::{KeyIvInit, StreamCipher};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{KEY_SIZE, Random};
|
||||||
|
|
||||||
|
pub struct Drbg {
|
||||||
|
key: [u8; KEY_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drbg {
|
||||||
|
pub fn new(seed: [u8; KEY_SIZE]) -> Self {
|
||||||
|
Self { key: seed }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Random for Drbg {
|
||||||
|
fn fill(&mut self, buf: &mut [u8]) {
|
||||||
|
let mut stream = ChaCha20::new((&self.key).into(), &[0u8; 12].into());
|
||||||
|
// The next key first, then the output.
|
||||||
|
let mut next = [0u8; KEY_SIZE];
|
||||||
|
stream.apply_keystream(&mut next);
|
||||||
|
buf.fill(0);
|
||||||
|
stream.apply_keystream(buf);
|
||||||
|
self.key = next;
|
||||||
|
next.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Drbg {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.key.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
186
crates/filecrypt/src/lib.rs
Normal file
186
crates/filecrypt/src/lib.rs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
//! How a file in an encrypted area looks on disk (see the vision's
|
||||||
|
//! Encryption note): a header with the file's salt, then its contents in
|
||||||
|
//! chunks, each sealed with XChaCha20-Poly1305 under a fresh random nonce,
|
||||||
|
//! bound to its place in the file and to whether it is the last one.
|
||||||
|
//! Changing, swapping or cutting chunks off is detected; restoring an old
|
||||||
|
//! copy of a whole file is not. Pure: no I/O.
|
||||||
|
//!
|
||||||
|
//! Keys: the master key → an area's key (`area_key`, keyed BLAKE2s over
|
||||||
|
//! the area's name) → each file's key (over its salt).
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
mod drbg;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub use drbg::Drbg;
|
||||||
|
|
||||||
|
use blake2::Blake2sMac256;
|
||||||
|
use blake2::digest::Mac;
|
||||||
|
use chacha20poly1305::aead::{AeadInPlace, KeyInit};
|
||||||
|
use chacha20poly1305::{Tag, XChaCha20Poly1305, XNonce};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
pub const KEY_SIZE: usize = 32;
|
||||||
|
const MAGIC: &[u8; 8] = b"KOSFILE1";
|
||||||
|
const SALT_SIZE: usize = 24;
|
||||||
|
/// The header: magic and salt.
|
||||||
|
pub const HEADER: usize = 8 + SALT_SIZE;
|
||||||
|
const NONCE: usize = 24;
|
||||||
|
const TAG: usize = 16;
|
||||||
|
/// What a chunk adds to what it holds.
|
||||||
|
pub const OVERHEAD: usize = NONCE + TAG;
|
||||||
|
/// A sealed chunk on disk, the last one possibly shorter.
|
||||||
|
pub const CHUNK: usize = 4096;
|
||||||
|
/// What a full chunk holds.
|
||||||
|
pub const PLAIN_CHUNK: usize = CHUNK - OVERHEAD;
|
||||||
|
|
||||||
|
/// A key, wiped from memory when dropped.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Key(pub [u8; KEY_SIZE]);
|
||||||
|
|
||||||
|
impl Drop for Key {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mac(key: &[u8; KEY_SIZE], parts: &[&[u8]]) -> Key {
|
||||||
|
let mut m =
|
||||||
|
<Blake2sMac256 as KeyInit>::new_from_slice(key).expect("BLAKE2s takes a 32-byte key");
|
||||||
|
for p in parts {
|
||||||
|
m.update(p);
|
||||||
|
}
|
||||||
|
Key(m.finalize().into_bytes().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The key of the area named `area` (`home`, `data/kos.notes`, ...):
|
||||||
|
/// each app's data is noise under another app's key.
|
||||||
|
pub fn area_key(master: &Key, area: &[u8]) -> Key {
|
||||||
|
mac(&master.0, &[b"kos/storage/v1/area/", area])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Randomness for salts and nonces.
|
||||||
|
pub trait Random {
|
||||||
|
fn fill(&mut self, buf: &mut [u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A file's header: its salt, which with its area's key makes its key.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Header {
|
||||||
|
salt: [u8; SALT_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
pub fn new(rng: &mut impl Random) -> Self {
|
||||||
|
let mut salt = [0u8; SALT_SIZE];
|
||||||
|
rng.fill(&mut salt);
|
||||||
|
Self { salt }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(&self) -> [u8; HEADER] {
|
||||||
|
let mut out = [0u8; HEADER];
|
||||||
|
out[..8].copy_from_slice(MAGIC);
|
||||||
|
out[8..].copy_from_slice(&self.salt);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `None` unless it begins an encrypted file (a plaintext file, say).
|
||||||
|
pub fn decode(bytes: &[u8]) -> Option<Self> {
|
||||||
|
let bytes = bytes.get(..HEADER)?;
|
||||||
|
if &bytes[..8] != MAGIC {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut salt = [0u8; SALT_SIZE];
|
||||||
|
salt.copy_from_slice(&bytes[8..]);
|
||||||
|
Some(Self { salt })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_key(&self, area: &Key) -> Key {
|
||||||
|
mac(&area.0, &[b"kos/storage/v1/file/", &self.salt])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many plaintext bytes a file holding `disk` bytes has; `None` if no
|
||||||
|
/// encrypted file is that long (a chunk cut short).
|
||||||
|
pub fn plain_len(disk: usize) -> Option<usize> {
|
||||||
|
let body = disk.checked_sub(HEADER)?;
|
||||||
|
let (full, rest) = (body / CHUNK, body % CHUNK);
|
||||||
|
match rest {
|
||||||
|
// The last chunk is full.
|
||||||
|
0 if full > 0 => Some(full * PLAIN_CHUNK),
|
||||||
|
r if r >= OVERHEAD => Some(full * PLAIN_CHUNK + r - OVERHEAD),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many chunks hold `plain` bytes: always at least one, so even an
|
||||||
|
/// empty file has a last chunk to say it is complete.
|
||||||
|
pub fn chunks(plain: usize) -> usize {
|
||||||
|
plain.div_ceil(PLAIN_CHUNK).max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a file holding `plain` bytes takes on disk.
|
||||||
|
pub fn disk_len(plain: usize) -> usize {
|
||||||
|
HEADER + plain + chunks(plain) * OVERHEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where chunk `index` starts on disk.
|
||||||
|
pub fn chunk_offset(index: usize) -> usize {
|
||||||
|
HEADER + index * CHUNK
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Binds a chunk to its place, and to being the last or not.
|
||||||
|
fn associated(index: u64, last: bool) -> [u8; 9] {
|
||||||
|
let mut ad = [0u8; 9];
|
||||||
|
ad[..8].copy_from_slice(&index.to_le_bytes());
|
||||||
|
ad[8] = u8::from(last);
|
||||||
|
ad
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seals `plain` (at most `PLAIN_CHUNK` bytes) as chunk `index` into `out`
|
||||||
|
/// (room for `plain.len() + OVERHEAD`); its length.
|
||||||
|
pub fn seal(
|
||||||
|
key: &Key,
|
||||||
|
index: u64,
|
||||||
|
last: bool,
|
||||||
|
plain: &[u8],
|
||||||
|
out: &mut [u8],
|
||||||
|
rng: &mut impl Random,
|
||||||
|
) -> usize {
|
||||||
|
let n = plain.len().min(PLAIN_CHUNK);
|
||||||
|
let (nonce, rest) = out.split_at_mut(NONCE);
|
||||||
|
rng.fill(nonce);
|
||||||
|
let (body, tag) = rest.split_at_mut(n);
|
||||||
|
body.copy_from_slice(&plain[..n]);
|
||||||
|
let t = XChaCha20Poly1305::new((&key.0).into())
|
||||||
|
.encrypt_in_place_detached(XNonce::from_slice(nonce), &associated(index, last), body)
|
||||||
|
.expect("a chunk is far below XChaCha20-Poly1305's limits");
|
||||||
|
tag[..TAG].copy_from_slice(&t);
|
||||||
|
n + OVERHEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens chunk `index` (`sealed` as read from disk) into `out`; its length,
|
||||||
|
/// or `None` if it is not that chunk of that file, unaltered.
|
||||||
|
pub fn open(key: &Key, index: u64, last: bool, sealed: &[u8], out: &mut [u8]) -> Option<usize> {
|
||||||
|
let n = sealed.len().checked_sub(OVERHEAD)?;
|
||||||
|
let (nonce, rest) = sealed.split_at(NONCE);
|
||||||
|
let (body, tag) = rest.split_at(n);
|
||||||
|
let out = out.get_mut(..n)?;
|
||||||
|
out.copy_from_slice(body);
|
||||||
|
let ok = XChaCha20Poly1305::new((&key.0).into())
|
||||||
|
.decrypt_in_place_detached(
|
||||||
|
XNonce::from_slice(nonce),
|
||||||
|
&associated(index, last),
|
||||||
|
out,
|
||||||
|
Tag::from_slice(tag),
|
||||||
|
)
|
||||||
|
.is_ok();
|
||||||
|
if !ok {
|
||||||
|
out.zeroize();
|
||||||
|
}
|
||||||
|
ok.then_some(n)
|
||||||
|
}
|
||||||
130
crates/filecrypt/src/tests.rs
Normal file
130
crates/filecrypt/src/tests.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
extern crate std;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
|
fn master() -> Key {
|
||||||
|
Key([7; KEY_SIZE])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rng() -> Drbg {
|
||||||
|
Drbg::new([1; KEY_SIZE])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sealed(key: &Key, index: u64, last: bool, plain: &[u8]) -> std::vec::Vec<u8> {
|
||||||
|
let mut out = vec![0u8; plain.len() + OVERHEAD];
|
||||||
|
let n = seal(key, index, last, plain, &mut out, &mut rng());
|
||||||
|
out.truncate(n);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_chunk_opens_only_as_itself() {
|
||||||
|
let key = Header::new(&mut rng()).file_key(&area_key(&master(), b"home"));
|
||||||
|
let chunk = sealed(&key, 3, true, b"hello, k-os");
|
||||||
|
let mut out = [0u8; PLAIN_CHUNK];
|
||||||
|
assert_eq!(open(&key, 3, true, &chunk, &mut out), Some(11));
|
||||||
|
assert_eq!(&out[..11], b"hello, k-os");
|
||||||
|
assert_eq!(
|
||||||
|
open(&key, 4, true, &chunk, &mut out),
|
||||||
|
None,
|
||||||
|
"moved elsewhere in the file"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
open(&key, 3, false, &chunk, &mut out),
|
||||||
|
None,
|
||||||
|
"no longer the last: something was cut off or added"
|
||||||
|
);
|
||||||
|
let other = Header::new(&mut Drbg::new([2; KEY_SIZE])).file_key(&area_key(&master(), b"home"));
|
||||||
|
assert_eq!(
|
||||||
|
open(&other, 3, true, &chunk, &mut out),
|
||||||
|
None,
|
||||||
|
"another file's key"
|
||||||
|
);
|
||||||
|
for at in [0, 23, 24, 30, chunk.len() - 1] {
|
||||||
|
let mut bent = chunk.clone();
|
||||||
|
bent[at] ^= 1;
|
||||||
|
assert_eq!(
|
||||||
|
open(&key, 3, true, &bent, &mut out),
|
||||||
|
None,
|
||||||
|
"byte {at} changed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_apps_area_key_opens_nothing_of_anothers() {
|
||||||
|
let header = Header::new(&mut rng());
|
||||||
|
let notes = header.file_key(&area_key(&master(), b"data/kos.notes"));
|
||||||
|
let chat = header.file_key(&area_key(&master(), b"data/kos.chat"));
|
||||||
|
let chunk = sealed(¬es, 0, true, b"private");
|
||||||
|
let mut out = [0u8; 16];
|
||||||
|
assert_eq!(open(&chat, 0, true, &chunk, &mut out), None);
|
||||||
|
let other_master = header.file_key(&area_key(&Key([8; KEY_SIZE]), b"data/kos.notes"));
|
||||||
|
assert_eq!(
|
||||||
|
open(&other_master, 0, true, &chunk, &mut out),
|
||||||
|
None,
|
||||||
|
"another master key (a wiped device)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_seal_takes_a_fresh_nonce() {
|
||||||
|
let key = Key([3; KEY_SIZE]);
|
||||||
|
let mut r = rng();
|
||||||
|
let (mut a, mut b) = ([0u8; 64], [0u8; 64]);
|
||||||
|
seal(&key, 0, true, b"same", &mut a, &mut r);
|
||||||
|
seal(&key, 0, true, b"same", &mut b, &mut r);
|
||||||
|
assert_ne!(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headers_mark_encrypted_files() {
|
||||||
|
let h = Header::new(&mut rng());
|
||||||
|
assert_eq!(Header::decode(&h.encode()), Some(h));
|
||||||
|
assert_eq!(
|
||||||
|
Header::decode(b"Welcome to k-os. This is a plaintext file."),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(Header::decode(b"KOSFILE"), None, "too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sizes_round_trip_across_chunk_boundaries() {
|
||||||
|
for plain in [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
PLAIN_CHUNK - 1,
|
||||||
|
PLAIN_CHUNK,
|
||||||
|
PLAIN_CHUNK + 1,
|
||||||
|
3 * PLAIN_CHUNK,
|
||||||
|
3 * PLAIN_CHUNK + 7,
|
||||||
|
] {
|
||||||
|
let disk = disk_len(plain);
|
||||||
|
assert_eq!(plain_len(disk), Some(plain), "{plain}");
|
||||||
|
}
|
||||||
|
assert_eq!(chunks(0), 1, "an empty file still has its last chunk");
|
||||||
|
for cut in [0, HEADER, HEADER + OVERHEAD - 1, HEADER + CHUNK + 5] {
|
||||||
|
assert_eq!(plain_len(cut), None, "{cut} bytes is no encrypted file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_drbg_never_repeats_and_depends_on_its_seed() {
|
||||||
|
let mut a = Drbg::new([1; KEY_SIZE]);
|
||||||
|
let (mut x, mut y) = ([0u8; 32], [0u8; 32]);
|
||||||
|
a.fill(&mut x);
|
||||||
|
a.fill(&mut y);
|
||||||
|
assert_ne!(x, y);
|
||||||
|
let mut b = Drbg::new([1; KEY_SIZE]);
|
||||||
|
let mut z = [0u8; 32];
|
||||||
|
b.fill(&mut z);
|
||||||
|
assert_eq!(
|
||||||
|
x, z,
|
||||||
|
"the same seed, the same stream (so the seed must be fresh)"
|
||||||
|
);
|
||||||
|
let mut c = Drbg::new([2; KEY_SIZE]);
|
||||||
|
c.fill(&mut z);
|
||||||
|
assert_ne!(x, z);
|
||||||
|
}
|
||||||
@@ -5,3 +5,4 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
libkos = { workspace = true }
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
use crate::mmio::MmioAccess;
|
use crate::mmio::MmioAccess;
|
||||||
use crate::queue::{Descriptor, VirtioQueue};
|
use crate::queue::{Descriptor, VirtioQueue};
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use libkos::Window;
|
||||||
|
use types::address::{ByteOffset, PhysicalAddress};
|
||||||
|
|
||||||
const SECTOR_SIZE: usize = 512;
|
const SECTOR_SIZE: usize = 512;
|
||||||
|
|
||||||
#[repr(C)]
|
/// One request's memory: the header (type, reserved, sector: 16 bytes),
|
||||||
pub struct BlockReqHeader {
|
/// the status byte the device writes back, and one sector of data.
|
||||||
pub req_type: u32,
|
|
||||||
pub reserved: u32,
|
|
||||||
pub sector: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DmaAddrs {
|
pub struct DmaAddrs {
|
||||||
pub header_pa: PhysicalAddress,
|
pub header_pa: PhysicalAddress,
|
||||||
pub header_va: VirtualAddress,
|
pub header: Window,
|
||||||
pub status_pa: PhysicalAddress,
|
pub status_pa: PhysicalAddress,
|
||||||
pub status_va: VirtualAddress,
|
pub status: Window,
|
||||||
pub data_pa: PhysicalAddress,
|
pub data_pa: PhysicalAddress,
|
||||||
pub data_va: VirtualAddress,
|
pub data: Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEADER_TYPE: ByteOffset = ByteOffset::new(0);
|
||||||
|
const HEADER_RESERVED: ByteOffset = ByteOffset::new(4);
|
||||||
|
const HEADER_SECTOR: ByteOffset = ByteOffset::new(8);
|
||||||
|
|
||||||
|
fn put_header(dma: &DmaAddrs, req_type: u32, sector: u32) {
|
||||||
|
dma.header.write(HEADER_TYPE, req_type);
|
||||||
|
dma.header.write(HEADER_RESERVED, 0u32);
|
||||||
|
dma.header.write(HEADER_SECTOR, sector as u64);
|
||||||
|
dma.status.write(ByteOffset::ZERO, 0xFFu8);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_sector(
|
pub fn read_sector(
|
||||||
@@ -29,13 +36,7 @@ pub fn read_sector(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
if buf.len() < SECTOR_SIZE { return false; }
|
if buf.len() < SECTOR_SIZE { return false; }
|
||||||
|
|
||||||
unsafe {
|
put_header(dma, 0, sector);
|
||||||
let hdr = dma.header_va.raw() as *mut BlockReqHeader;
|
|
||||||
core::ptr::write_volatile(&raw mut (*hdr).req_type, 0);
|
|
||||||
core::ptr::write_volatile(&raw mut (*hdr).reserved, 0);
|
|
||||||
core::ptr::write_volatile(&raw mut (*hdr).sector, sector as u64);
|
|
||||||
core::ptr::write_volatile(dma.status_va.raw() as *mut u8, 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
let descs = [
|
let descs = [
|
||||||
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
|
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
|
||||||
@@ -45,18 +46,35 @@ pub fn read_sector(
|
|||||||
|
|
||||||
if !queue.submit(mmio, &descs) { return false; }
|
if !queue.submit(mmio, &descs) { return false; }
|
||||||
|
|
||||||
let status = unsafe { core::ptr::read_volatile(dma.status_va.raw() as *const u8) };
|
if dma.status.read::<u8>(ByteOffset::ZERO) != 0 { return false; }
|
||||||
if status != 0 { return false; }
|
dma.data.copy_to(ByteOffset::ZERO, &mut buf[..SECTOR_SIZE]);
|
||||||
|
|
||||||
if dma.data_va.raw() != buf.as_ptr() as usize {
|
|
||||||
unsafe {
|
|
||||||
core::ptr::copy_nonoverlapping(
|
|
||||||
dma.data_va.raw() as *const u8,
|
|
||||||
buf.as_mut_ptr(),
|
|
||||||
SECTOR_SIZE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request types (virtio spec 5.2.6).
|
||||||
|
const T_OUT: u32 = 1;
|
||||||
|
/// Device configuration: the disk's size in 512-byte sectors (le64).
|
||||||
|
pub const CONFIG_CAPACITY: ByteOffset = ByteOffset::new(0x100);
|
||||||
|
|
||||||
|
/// Writes one sector from `buf`; true once the device reports success.
|
||||||
|
pub fn write_sector(
|
||||||
|
mmio: &impl MmioAccess,
|
||||||
|
queue: &VirtioQueue,
|
||||||
|
dma: &DmaAddrs,
|
||||||
|
sector: u32,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> bool {
|
||||||
|
if buf.len() < SECTOR_SIZE { return false; }
|
||||||
|
|
||||||
|
dma.data.copy_from(ByteOffset::ZERO, &buf[..SECTOR_SIZE]);
|
||||||
|
put_header(dma, T_OUT, sector);
|
||||||
|
|
||||||
|
// The device reads the data this time; only the status is written back.
|
||||||
|
let descs = [
|
||||||
|
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
|
||||||
|
Descriptor { addr: dma.data_pa.raw() as u64, len: SECTOR_SIZE as u32, writable: false },
|
||||||
|
Descriptor { addr: dma.status_pa.raw() as u64, len: 1, writable: true },
|
||||||
|
];
|
||||||
|
if !queue.submit(mmio, &descs) { return false; }
|
||||||
|
dma.status.read::<u8>(ByteOffset::ZERO) == 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,103 +1,112 @@
|
|||||||
use crate::mmio::MmioAccess;
|
use crate::mmio::MmioAccess;
|
||||||
use crate::queue::{Descriptor, VirtioQueue};
|
use crate::queue::{Descriptor, VirtioQueue};
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use libkos::Window;
|
||||||
|
use types::address::{ByteOffset, PhysicalAddress};
|
||||||
use types::display::Dimension;
|
use types::display::Dimension;
|
||||||
|
|
||||||
|
/// The command page: commands go out from its start, responses come back
|
||||||
|
/// at `RESPONSE`.
|
||||||
pub struct GpuDmaAddrs {
|
pub struct GpuDmaAddrs {
|
||||||
pub cmd_pa: PhysicalAddress,
|
pub cmd_pa: PhysicalAddress,
|
||||||
pub cmd_va: VirtualAddress,
|
pub cmd: Window,
|
||||||
pub resp_pa: PhysicalAddress,
|
pub resp_pa: PhysicalAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const RESPONSE: ByteOffset = ByteOffset::new(512);
|
||||||
|
|
||||||
|
/// Command fields (virtio spec 5.7.6.7): a 24-byte header, then the body.
|
||||||
|
const TYPE: ByteOffset = ByteOffset::new(0);
|
||||||
|
const BODY: ByteOffset = ByteOffset::new(24);
|
||||||
|
/// Most bodies start with a rectangle: x, y, width, height.
|
||||||
|
const RECT_X: ByteOffset = ByteOffset::new(24);
|
||||||
|
const RECT_Y: ByteOffset = ByteOffset::new(28);
|
||||||
|
const RECT_W: ByteOffset = ByteOffset::new(32);
|
||||||
|
const RECT_H: ByteOffset = ByteOffset::new(36);
|
||||||
|
/// The 24 bytes of header and 16 of rectangle in a display-info response.
|
||||||
|
const DISPLAY_W: ByteOffset = ByteOffset::new(24 + 8);
|
||||||
|
const DISPLAY_H: ByteOffset = ByteOffset::new(24 + 12);
|
||||||
|
|
||||||
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
|
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
|
||||||
write_cmd_header(dma.cmd_va, 0x0101);
|
write_cmd_header(&dma.cmd, 0x0101);
|
||||||
unsafe {
|
// Resource id, format (134: R8G8B8X8, a window's own byte order, so
|
||||||
let p = dma.cmd_va.raw() as *mut u8;
|
// its rows are copied as they are), width, height.
|
||||||
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
|
dma.cmd.write(BODY, resource_id);
|
||||||
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
|
dma.cmd.write(BODY + 4, 134u32);
|
||||||
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
|
dma.cmd.write(BODY + 8, width.raw() as u32);
|
||||||
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
|
dma.cmd.write(BODY + 12, height.raw() as u32);
|
||||||
}
|
|
||||||
submit_cmd(mmio, queue, dma, 40)
|
submit_cmd(mmio, queue, dma, 40)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) -> bool {
|
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) -> bool {
|
||||||
write_cmd_header(dma.cmd_va, 0x0106);
|
write_cmd_header(&dma.cmd, 0x0106);
|
||||||
unsafe {
|
// Resource id, one memory entry: its address and length.
|
||||||
let p = dma.cmd_va.raw() as *mut u8;
|
dma.cmd.write(BODY, resource_id);
|
||||||
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
|
dma.cmd.write(BODY + 4, 1u32);
|
||||||
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
|
dma.cmd.write(BODY + 8, fb_pa.raw() as u64);
|
||||||
core::ptr::write_volatile(p.add(32) as *mut u64, fb_pa.raw() as u64);
|
dma.cmd.write(BODY + 16, fb_size);
|
||||||
core::ptr::write_volatile(p.add(40) as *mut u32, fb_size);
|
|
||||||
}
|
|
||||||
submit_cmd(mmio, queue, dma, 48)
|
submit_cmd(mmio, queue, dma, 48)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, scanout_id: u32, resource_id: u32, width: Dimension, height: Dimension) -> bool {
|
pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, scanout_id: u32, resource_id: u32, width: Dimension, height: Dimension) -> bool {
|
||||||
write_cmd_header(dma.cmd_va, 0x0103);
|
write_cmd_header(&dma.cmd, 0x0103);
|
||||||
unsafe {
|
// The rectangle (from 0, 0), then scanout and resource ids.
|
||||||
let p = dma.cmd_va.raw() as *mut u8;
|
dma.cmd.write(RECT_W, width.raw() as u32);
|
||||||
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
|
dma.cmd.write(RECT_H, height.raw() as u32);
|
||||||
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
|
dma.cmd.write(BODY + 16, scanout_id);
|
||||||
core::ptr::write_volatile(p.add(40) as *mut u32, scanout_id);
|
dma.cmd.write(BODY + 20, resource_id);
|
||||||
core::ptr::write_volatile(p.add(44) as *mut u32, resource_id);
|
|
||||||
}
|
|
||||||
submit_cmd(mmio, queue, dma, 48)
|
submit_cmd(mmio, queue, dma, 48)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
|
/// A rectangle of a resource, in pixels.
|
||||||
write_cmd_header(dma.cmd_va, 0x0105);
|
#[derive(Clone, Copy, Debug)]
|
||||||
unsafe {
|
pub struct Rect {
|
||||||
let p = dma.cmd_va.raw() as *mut u8;
|
pub x: u32,
|
||||||
core::ptr::write_volatile(p.add(24) as *mut u32, x);
|
pub y: u32,
|
||||||
core::ptr::write_volatile(p.add(28) as *mut u32, y);
|
pub w: u32,
|
||||||
core::ptr::write_volatile(p.add(32) as *mut u32, w);
|
pub h: u32,
|
||||||
core::ptr::write_volatile(p.add(36) as *mut u32, h);
|
}
|
||||||
core::ptr::write_volatile(p.add(40) as *mut u64, 0);
|
|
||||||
core::ptr::write_volatile(p.add(48) as *mut u32, resource_id);
|
/// Copies `rect` of the backing to the host; `offset` is where its top
|
||||||
}
|
/// left pixel is in the backing, in bytes.
|
||||||
|
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, rect: Rect, offset: u64) -> bool {
|
||||||
|
write_cmd_header(&dma.cmd, 0x0105);
|
||||||
|
put_rect(&dma.cmd, rect);
|
||||||
|
// Offset into the resource's memory, then the resource id.
|
||||||
|
dma.cmd.write(BODY + 16, offset);
|
||||||
|
dma.cmd.write(BODY + 24, resource_id);
|
||||||
submit_cmd(mmio, queue, dma, 56)
|
submit_cmd(mmio, queue, dma, 56)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
|
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, rect: Rect) -> bool {
|
||||||
write_cmd_header(dma.cmd_va, 0x0104);
|
write_cmd_header(&dma.cmd, 0x0104);
|
||||||
unsafe {
|
put_rect(&dma.cmd, rect);
|
||||||
let p = dma.cmd_va.raw() as *mut u8;
|
dma.cmd.write(BODY + 16, resource_id);
|
||||||
core::ptr::write_volatile(p.add(24) as *mut u32, x);
|
|
||||||
core::ptr::write_volatile(p.add(28) as *mut u32, y);
|
|
||||||
core::ptr::write_volatile(p.add(32) as *mut u32, w);
|
|
||||||
core::ptr::write_volatile(p.add(36) as *mut u32, h);
|
|
||||||
core::ptr::write_volatile(p.add(40) as *mut u32, resource_id);
|
|
||||||
}
|
|
||||||
submit_cmd(mmio, queue, dma, 48)
|
submit_cmd(mmio, queue, dma, 48)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_cmd_header(cmd_va: VirtualAddress, cmd_type: u32) {
|
fn write_cmd_header(cmd: &Window, cmd_type: u32) {
|
||||||
unsafe {
|
cmd.fill_u32(ByteOffset::ZERO, 128, 0);
|
||||||
let p = cmd_va.raw() as *mut u8;
|
cmd.write(TYPE, cmd_type);
|
||||||
// Volatile so the device sees the zeros.
|
}
|
||||||
for i in 0..128 {
|
|
||||||
core::ptr::write_volatile((p as *mut u32).add(i), 0);
|
fn put_rect(cmd: &Window, r: Rect) {
|
||||||
}
|
cmd.write(RECT_X, r.x);
|
||||||
core::ptr::write_volatile(p as *mut u32, cmd_type);
|
cmd.write(RECT_Y, r.y);
|
||||||
}
|
cmd.write(RECT_W, r.w);
|
||||||
|
cmd.write(RECT_H, r.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_display_info(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs) -> Option<(u32, u32)> {
|
pub fn get_display_info(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs) -> Option<(u32, u32)> {
|
||||||
write_cmd_header(dma.cmd_va, 0x0100);
|
write_cmd_header(&dma.cmd, 0x0100);
|
||||||
let descs = [
|
let descs = [
|
||||||
Descriptor { addr: dma.cmd_pa.raw() as u64, len: 24, writable: false },
|
Descriptor { addr: dma.cmd_pa.raw() as u64, len: 24, writable: false },
|
||||||
Descriptor { addr: dma.resp_pa.raw() as u64, len: 408, writable: true },
|
Descriptor { addr: dma.resp_pa.raw() as u64, len: 408, writable: true },
|
||||||
];
|
];
|
||||||
if !queue.submit(mmio, &descs) { return None; }
|
if !queue.submit(mmio, &descs) { return None; }
|
||||||
unsafe {
|
if dma.cmd.read::<u32>(RESPONSE) != 0x1101 { return None; }
|
||||||
let resp_va = dma.cmd_va.raw() + 512;
|
let w: u32 = dma.cmd.read(RESPONSE + DISPLAY_W.raw());
|
||||||
let resp_type = core::ptr::read_volatile(resp_va as *const u32);
|
let h: u32 = dma.cmd.read(RESPONSE + DISPLAY_H.raw());
|
||||||
if resp_type != 0x1101 { return None; }
|
if w > 0 && h > 0 { Some((w, h)) } else { None }
|
||||||
let w = core::ptr::read_volatile((resp_va + 24 + 8) as *const u32);
|
|
||||||
let h = core::ptr::read_volatile((resp_va + 24 + 12) as *const u32);
|
|
||||||
if w > 0 && h > 0 { Some((w, h)) } else { None }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) -> bool {
|
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) -> bool {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user