From 0e003c35a45a996d6eb0ca358e5faf8d8126fcd7 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 9 Aug 2023 14:21:52 -0700 Subject: [PATCH] platform(x86_64): initial x86_64 bringup (#216) Depends on #219. This branch adds an initial implementation of an x86_64 platform target for mnemOS, using the [`mycelium` x86 HAL][hal]. Currently, I haven't implemented drivers for the UART, keyboard, or emb-display service (using the framebuffer as the display), so we don't have SerMux, Forth, trace-proto, or anything else interesting. However, what we *do* have is a basic bootable image with a timer, allocator, and rudimentary kernel run loop, and --- as you can see here --- it works: ![image](https://github.com/tosc-rs/mnemos/assets/2796466/8b6785cd-9a66-4735-8b03-02b8a191016f) I've also added new `just` commands for building x86_64 images and running them in QEMU for development. [hal]: https://github.com/hawkw/mycelium/tree/main/hal-x86_64 --- .cargo/config.toml | 14 +- Cargo.lock | 324 +++++++++++++++++- Cargo.toml | 2 + justfile | 17 +- platforms/README.md | 8 + platforms/x86_64/README.md | 74 ++++ platforms/x86_64/bootloader/Cargo.toml | 21 ++ platforms/x86_64/bootloader/build.rs | 24 ++ platforms/x86_64/bootloader/src/main.rs | 85 +++++ platforms/x86_64/core/Cargo.toml | 55 +++ platforms/x86_64/core/src/acpi.rs | 109 ++++++ platforms/x86_64/core/src/allocator.rs | 91 +++++ .../core/src/bin/bootloader/bootinfo.rs | 87 +++++ .../core/src/bin/bootloader/framebuf.rs | 99 ++++++ .../x86_64/core/src/bin/bootloader/main.rs | 132 +++++++ platforms/x86_64/core/src/drivers/framebuf.rs | 162 +++++++++ platforms/x86_64/core/src/drivers/mod.rs | 1 + platforms/x86_64/core/src/interrupt.rs | 202 +++++++++++ platforms/x86_64/core/src/lib.rs | 111 ++++++ platforms/x86_64/core/src/trace.rs | 157 +++++++++ rust-toolchain.toml | 1 + source/kernel/src/lib.rs | 6 +- source/kernel/src/serial_trace.rs | 8 +- 23 files changed, 1767 insertions(+), 23 deletions(-) create mode 100644 platforms/x86_64/README.md create mode 100644 platforms/x86_64/bootloader/Cargo.toml create mode 100644 platforms/x86_64/bootloader/build.rs create mode 100644 platforms/x86_64/bootloader/src/main.rs create mode 100644 platforms/x86_64/core/Cargo.toml create mode 100644 platforms/x86_64/core/src/acpi.rs create mode 100644 platforms/x86_64/core/src/allocator.rs create mode 100644 platforms/x86_64/core/src/bin/bootloader/bootinfo.rs create mode 100644 platforms/x86_64/core/src/bin/bootloader/framebuf.rs create mode 100644 platforms/x86_64/core/src/bin/bootloader/main.rs create mode 100644 platforms/x86_64/core/src/drivers/framebuf.rs create mode 100644 platforms/x86_64/core/src/drivers/mod.rs create mode 100644 platforms/x86_64/core/src/interrupt.rs create mode 100644 platforms/x86_64/core/src/lib.rs create mode 100644 platforms/x86_64/core/src/trace.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index b96f4c50..00084371 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,4 +9,16 @@ forth3 = "run --bin f3repl --release --" rustflags = ["--cfg", "tokio_unstable"] [target.riscv32imac-unknown-none-elf] -runner = "espflash flash --monitor" \ No newline at end of file +runner = "espflash flash --monitor" + +[unstable] +# Enables Cargo artifact dependencies. +# +# This allows a crate to depend on a specific binary artifact from another +# crate. This is used as part of the build process for `x86_64` targets using +# `rust-osdev/bootloader`, where a target crate depends on the kernel binary +# artifact from the kernel core crate and links it with the bootloader binary in +# a `build.rs` script. +# +# See: https://doc.rust-lang.org/cargo/reference/unstable.html#artifact-dependencies +bindeps = true diff --git a/Cargo.lock b/Cargo.lock index 58fd10bf..a25c7280 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,17 @@ dependencies = [ "mach 0.1.2", ] +[[package]] +name = "acpi" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654f48ab3178632ea535be1765073b990895cb62f70a7e5671975d7150c26d15" +dependencies = [ + "bit_field", + "log", + "rsdp", +] + [[package]] name = "addr2line" version = "0.20.0" @@ -454,6 +465,18 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blocking" version = "1.3.1" @@ -469,6 +492,40 @@ dependencies = [ "log", ] +[[package]] +name = "bootloader" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e092090f6e2b68cee8dc291ec60e4706f6b0c5cd90b666a5248a20f30591ccb0" +dependencies = [ + "anyhow", + "async-process", + "bootloader-boot-config", + "fatfs", + "futures", + "futures-concurrency", + "gpt", + "llvm-tools", + "mbrman", + "serde_json", + "tempfile", +] + +[[package]] +name = "bootloader-boot-config" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a36c0df7b26b44254828ece99ed72aa01695cc37f007a97afe8aee29feb26e3e" +dependencies = [ + "serde", +] + +[[package]] +name = "bootloader_api" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f843888771a490c3ad246e58bf25e62da70d2c653d74a3eca92acbab599e02" + [[package]] name = "bumpalo" version = "3.13.0" @@ -708,6 +765,15 @@ dependencies = [ "tracing 0.1.37", ] +[[package]] +name = "cordyceps" +version = "0.3.2" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "loom", + "tracing 0.1.37", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -724,6 +790,21 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32fast" version = "1.3.2" @@ -1138,6 +1219,17 @@ dependencies = [ "instant", ] +[[package]] +name = "fatfs" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05669f8e7e2d7badc545c513710f0eba09c2fbef683eb859fd79c46c355048e0" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "log", +] + [[package]] name = "flate2" version = "1.0.26" @@ -1212,6 +1304,12 @@ dependencies = [ "gcd", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -1237,6 +1335,17 @@ dependencies = [ "futures-sink", ] +[[package]] +name = "futures-concurrency" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b726119e6cd29cf120724495b2085e1ed3d17821ea17b86de54576d1aa565f5e" +dependencies = [ + "bitvec", + "futures-core", + "pin-project", +] + [[package]] name = "futures-core" version = "0.3.28" @@ -1531,6 +1640,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gpt" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" +dependencies = [ + "bitflags 2.3.3", + "crc", + "log", + "uuid 1.3.4", +] + [[package]] name = "h2" version = "0.3.20" @@ -1550,6 +1671,31 @@ dependencies = [ "tracing 0.1.37", ] +[[package]] +name = "hal-core" +version = "0.1.0" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "embedded-graphics-core", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium)", + "tracing 0.2.0", +] + +[[package]] +name = "hal-x86_64" +version = "0.1.0" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "acpi", + "hal-core", + "mycelium-trace", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium)", + "mycotest", + "raw-cpuid", + "tracing 0.2.0", + "volatile", +] + [[package]] name = "hash32" version = "0.2.1" @@ -1934,6 +2080,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" + [[package]] name = "lock_api" version = "0.4.10" @@ -1998,9 +2150,9 @@ name = "maitake" version = "0.1.0" source = "git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064#101a4abaa19afdd131b334a16d92c9fb4909c064" dependencies = [ - "cordyceps", - "mycelium-bitfield", - "mycelium-util", + "cordyceps 0.3.2 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", + "mycelium-bitfield 0.1.3 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", "pin-project", "portable-atomic", "tracing 0.1.37", @@ -2021,6 +2173,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +[[package]] +name = "mbrman" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c487024623ae38584610237dd1be8932bb2b324474b23c37a25f9fbe6bf5e9e" +dependencies = [ + "bincode", + "bitvec", + "serde", + "serde-big-array", + "thiserror", +] + [[package]] name = "melpo-config" version = "0.1.0" @@ -2206,8 +2371,8 @@ dependencies = [ "mnemos-abi", "mnemos-alloc", "mnemos-trace-proto", - "mycelium-bitfield", - "mycelium-util", + "mycelium-bitfield 0.1.3 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", "portable-atomic", "postcard 1.0.6", "profont", @@ -2235,7 +2400,7 @@ dependencies = [ name = "mnemos-alloc" version = "0.1.0" dependencies = [ - "cordyceps", + "cordyceps 0.3.2 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", "heapless", "linked_list_allocator", "maitake", @@ -2248,7 +2413,7 @@ dependencies = [ "bbq10kbd", "futures", "mnemos", - "mycelium-bitfield", + "mycelium-bitfield 0.1.3 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", "tracing 0.1.37", "uuid 1.3.4", ] @@ -2312,7 +2477,7 @@ dependencies = [ name = "mnemos-std" version = "0.1.0" dependencies = [ - "cordyceps", + "cordyceps 0.3.2 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", "futures-util", "heapless", "maitake", @@ -2330,6 +2495,33 @@ dependencies = [ "tracing-serde-structured", ] +[[package]] +name = "mnemos-x86_64-bootloader" +version = "0.1.0" +dependencies = [ + "bootloader", + "clap 4.3.5", + "mnemos-x86_64-core", + "ovmf-prebuilt", +] + +[[package]] +name = "mnemos-x86_64-core" +version = "0.1.0" +dependencies = [ + "acpi", + "bootloader_api", + "embedded-graphics", + "futures", + "hal-core", + "hal-x86_64", + "mnemos", + "mycelium-alloc", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium)", + "profont", + "tracing 0.1.37", +] + [[package]] name = "modality-ingest-client" version = "0.1.1" @@ -2345,22 +2537,68 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "mycelium-alloc" +version = "0.1.0" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "hal-core", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium)", + "tracing 0.2.0", +] + [[package]] name = "mycelium-bitfield" version = "0.1.3" source = "git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064#101a4abaa19afdd131b334a16d92c9fb4909c064" +[[package]] +name = "mycelium-bitfield" +version = "0.1.3" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" + +[[package]] +name = "mycelium-trace" +version = "0.1.0" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "embedded-graphics", + "hal-core", + "mycelium-util 0.1.0 (git+https://github.com/hawkw/mycelium)", + "tracing 0.2.0", + "tracing-core 0.2.0", +] + [[package]] name = "mycelium-util" version = "0.1.0" source = "git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064#101a4abaa19afdd131b334a16d92c9fb4909c064" dependencies = [ - "cordyceps", + "cordyceps 0.3.2 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", + "loom", + "mycelium-bitfield 0.1.3 (git+https://github.com/hawkw/mycelium.git?rev=101a4abaa19afdd131b334a16d92c9fb4909c064)", + "tracing 0.2.0", +] + +[[package]] +name = "mycelium-util" +version = "0.1.0" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "cordyceps 0.3.2 (git+https://github.com/hawkw/mycelium)", "loom", - "mycelium-bitfield", + "mycelium-bitfield 0.1.3 (git+https://github.com/hawkw/mycelium)", "tracing 0.2.0", ] +[[package]] +name = "mycotest" +version = "0.1.0" +source = "git+https://github.com/hawkw/mycelium#1f125194902cd4970b72eab0aa1d85d1b6ec1489" +dependencies = [ + "tracing 0.1.37", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -2573,6 +2811,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "ovmf-prebuilt" +version = "0.1.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa50141d081512ab30fd9e7e7692476866df5098b028536ad6680212e717fa8d" + [[package]] name = "owo-colors" version = "3.5.0" @@ -2873,6 +3117,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd7a31eed1591dcbc95d92ad7161908e72f4677f8fabf2a32ca49b4237cbf211" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -3018,6 +3268,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rayon" version = "1.7.0" @@ -3170,6 +3429,15 @@ dependencies = [ "regex", ] +[[package]] +name = "rsdp" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d3add2fc55ef37511bcf81a08ee7a09eff07b23aae38b06a29024a38c604b1" +dependencies = [ + "log", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3312,6 +3580,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3323f09a748af288c3dc2474ea6803ee81f118321775bffa3ac8f7e65c5e90e7" +dependencies = [ + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.5.0" @@ -3589,6 +3866,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.6.0" @@ -3916,7 +4199,7 @@ dependencies = [ [[package]] name = "tracing" version = "0.2.0" -source = "git+https://github.com/tokio-rs/tracing#27f688efb72316a26f3ec1f952c82626692c08ff" +source = "git+https://github.com/tokio-rs/tracing#ef201ab3a9134c25b3d344a2df9c3f9ce74db88f" dependencies = [ "pin-project-lite", "tracing-attributes 0.2.0", @@ -3937,7 +4220,7 @@ dependencies = [ [[package]] name = "tracing-attributes" version = "0.2.0" -source = "git+https://github.com/tokio-rs/tracing#27f688efb72316a26f3ec1f952c82626692c08ff" +source = "git+https://github.com/tokio-rs/tracing#ef201ab3a9134c25b3d344a2df9c3f9ce74db88f" dependencies = [ "proc-macro2", "quote", @@ -3957,7 +4240,7 @@ dependencies = [ [[package]] name = "tracing-core" version = "0.2.0" -source = "git+https://github.com/tokio-rs/tracing#27f688efb72316a26f3ec1f952c82626692c08ff" +source = "git+https://github.com/tokio-rs/tracing#ef201ab3a9134c25b3d344a2df9c3f9ce74db88f" [[package]] name = "tracing-log" @@ -4165,6 +4448,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -4397,3 +4686,12 @@ checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" dependencies = [ "memchr", ] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml index eba7c93f..b9033df7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "platforms/esp32c3-buddy", "platforms/melpomene", "platforms/melpomene/melpo-config", + "platforms/x86_64/*", "platforms/pomelo", ] # By default, run cargo commands without a specific package against everything @@ -51,6 +52,7 @@ default-members = [ "platforms/beepy", "platforms/melpomene", "platforms/melpomene/melpo-config", + "platforms/x86_64/core", ] # this isn't actually a crate exclude = ["source/notes"] diff --git a/justfile b/justfile index 708c282e..4a59c5b4 100644 --- a/justfile +++ b/justfile @@ -29,6 +29,8 @@ _d1_pkg := "mnemos-d1" _espbuddy_pkg := "mnemos-esp32c3-buddy" +_x86_bootloader_pkg := "mnemos-x86_64-bootloader" + # arguments to pass to all RustDoc invocations _rustdoc := _cargo + " doc --no-deps --all-features" @@ -42,7 +44,7 @@ default: @just --list # check all crates, across workspaces -check: && (check-crate _d1_pkg) (check-crate _espbuddy_pkg) +check: && (check-crate _d1_pkg) (check-crate _espbuddy_pkg) (check-crate _x86_bootloader_pkg) {{ _cargo }} check \ --lib --bins --examples --tests --benches \ {{ _fmt }} @@ -55,7 +57,7 @@ check-crate crate: {{ _fmt }} # run Clippy checks for all crates, across workspaces. -clippy: && (clippy-crate _d1_pkg) (clippy-crate _espbuddy_pkg) +clippy: && (clippy-crate _d1_pkg) (clippy-crate _espbuddy_pkg) (clippy-crate _x86_bootloader_pkg) {{ _cargo }} clippy \ --lib --bins --examples --tests --benches --all-features \ {{ _fmt }} @@ -76,6 +78,7 @@ fmt: {{ _cargo }} fmt {{ _cargo }} fmt --package {{ _d1_pkg }} {{ _cargo }} fmt --package {{ _espbuddy_pkg }} + {{ _cargo }} fmt --package {{ _x86_bootloader_pkg }} # build a Mnemos binary for the Allwinner D1 build-d1 board='mq-pro': (_get-cargo-command "objcopy" "cargo-binutils") @@ -110,6 +113,14 @@ flash-c3 board *espflash-args: (_get-cargo-command "espflash" "cargo-espflash") --bin {{ board }} \ {{ espflash-args }} +# build a bootable x86_64 disk image, using rust-osdev/bootloader. +build-x86 *args='': + {{ _cargo }} build --package {{ _x86_bootloader_pkg }} {{ args }} + +# run an x86_64 MnemOS image in QEMU +run-x86 *args='': + {{ _cargo }} run -p {{ _x86_bootloader_pkg }} -- {{ args }} + # run crowtty (a host serial multiplexer, log viewer, and pseudo-keyboard) crowtty *FLAGS: @{{ _cargo }} run --release --bin crowtty -- {{ FLAGS }} @@ -119,7 +130,7 @@ melpomene *FLAGS: @{{ _cargo }} run --release --bin melpomene -- {{ FLAGS }} # build all RustDoc documentation -all-docs *FLAGS: (docs FLAGS) (docs "-p " + _d1_pkg + FLAGS) (docs "-p " + _d1_pkg + FLAGS) +all-docs *FLAGS: (docs FLAGS) (docs "-p " + _d1_pkg + FLAGS) (docs "-p " + _espbuddy_pkg + FLAGS) (docs "-p " + _x86_bootloader_pkg + FLAGS) # run RustDoc docs *FLAGS: diff --git a/platforms/README.md b/platforms/README.md index 3f87274b..6334c975 100644 --- a/platforms/README.md +++ b/platforms/README.md @@ -11,6 +11,10 @@ This directory contains code for running MnemOS on the supported hardware and si * [`esp32c3-buddy/`] - MnemOS ESP32-C3 WiFi Buddy firmware * [`melpomene/`] - Melpomene is a desktop simulator for MnemOS development * [`pomelo/`] - Pomelo is a web/wasm simulator for MnemOS development +* [`x86_64`] - MnemOS for x86_64/amd64 CPUs + - [`x86_64/bootloader/] - Target for building a bootable kernel image using + [`rust-osdev/bootloader`] as the bootloader. + - [`x86_64/core/`] - MnemOS core kernel for x86_64 [`allwinner-d1/`]: ./allwinner-d1/ [`allwinner-d1/boards/`]: ./allwinner-d1/boards/ @@ -18,6 +22,10 @@ This directory contains code for running MnemOS on the supported hardware and si [`esp32c3-buddy/`]: ./esp32c3-buddy/ [`melpomene/`]: ./melpomene [`pomelo/`]: ./pomelo +[`x86_64/`]: ./x86_64 +[`x86_64/bootloader/`]: ./x86_64/bootloader/ +[`x86_64/core/`]: ./x86_64/core/ +[`rust-osdev/bootloader`]: https://github.com/rust-osdev/bootloader ## License diff --git a/platforms/x86_64/README.md b/platforms/x86_64/README.md new file mode 100644 index 00000000..9e61a1ce --- /dev/null +++ b/platforms/x86_64/README.md @@ -0,0 +1,74 @@ +# MnemOS x86_64 + +This directory contains the MnemOS platform implementation for x86_64/amd64 +CPUs. + + +## Folder Layout + +* [`bootloader/`] - Target for building a bootable image using + [`rust-osdev/bootloader`] as the bootloader +* [`core/`] - MnemOS core kernel for x86_64 + +[`bootloader/`]: ./bootloader/ +[`core/`]: ./core/ + +## Supported Bootloaders + +Currently, [`rust-osdev/bootloader`] is the only supported bootloader. A MnemOS +image using this bootloader is built by the [`bootloader/`] crate in this +directory.[`rust-osdev/bootloader`] can be used to build both BIOS and UEFI +images. + +## Getting started with MnemOS on the ESP32-C3 + +### Building + +To build x86_64 boot images, either run +`cargo build -p mnemos-x86_64-bootloader`, or use the `just build-x86` +[`just` recipe][just]. + +After running this command, BIOS and UEFI boot image files (named +`mnemos-x86_64-bios.img` and `mnemos-x86_64-uefi.img`, respectively) will be +output to the build script's [Cargo `$OUT_DIR`][outdir]. By default, the +`$OUT_DIR` is `target/{debug, +release}/build/mnemos-x86_64-bootloader-{hash}/out`. + +### Running in QEMU + +To run MnemOS in [QEMU], either run `cargo run -p mnemos-x86_64-bootloader` or +use the `just run-x86` [`just` recipe][just]. + +> [!IMPORTANT] +> +> In order to run either of these commands, a [`qemu-system-x86_64`][QEMU] +> binary must beinstalled. + +MnemOS can boot using either legacy [BIOS] or [UEFI] (using [`ovmf-prebuilt`]). +The `--boot` argument can be passed to `just run-x86` to determine which boot +method is used: + +```console +$ just run-x86 --boot uefi # boots using UEFI +$ just run-x86 --boot bios # boots using legacy BIOS +``` + +UEFI-based boot is the default if no argument is passed. + +Additional command-line arguments can be passed to configure the behavior of the +bootimage builder. Run `just run-x86 --help` to list them. + +[QEMU]: https://www.qemu.org +[just]: ./../../../justfile +[`rust-osdev/bootloader`]: https://github.com/rust-osdev/bootloader +[BIOS]: https://en.wikipedia.org/wiki/BIOS +[UEFI]: https://en.wikipedia.org/wiki/UEFI +[`ovmf-prebuilt`]: https://github.com/rust-osdev/ovmf-prebuilt +[outdir]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts + +## License + +[MIT] + [Apache 2.0]. + +[MIT]: ./LICENSE-MIT +[Apache 2.0]: ./LICENSE-APACHE diff --git a/platforms/x86_64/bootloader/Cargo.toml b/platforms/x86_64/bootloader/Cargo.toml new file mode 100644 index 00000000..4bc64289 --- /dev/null +++ b/platforms/x86_64/bootloader/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mnemos-x86_64-bootloader" +version = "0.1.0" +edition = "2021" + +[dependencies] +# used for UEFI booting in QEMU +ovmf-prebuilt = "0.1.0-alpha.1" +# used for the QEMU runner +clap = { version = "4", features = ["derive", "env"] } + +[build-dependencies] +bootloader = "0.11" + +# the actual MnemOS kernel binary +[build-dependencies.mnemos] +package = "mnemos-x86_64-core" +path = "../core" +artifact = "bin:bootloader" +target = "x86_64-unknown-none" +features = ["bootloader_api"] diff --git a/platforms/x86_64/bootloader/build.rs b/platforms/x86_64/bootloader/build.rs new file mode 100644 index 00000000..9d9605dc --- /dev/null +++ b/platforms/x86_64/bootloader/build.rs @@ -0,0 +1,24 @@ +use std::path::PathBuf; + +fn main() { + // set by cargo, build scripts should use this directory for output files + let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + // set by cargo's artifact dependency feature, see + // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies + let kernel = PathBuf::from(std::env::var_os("CARGO_BIN_FILE_MNEMOS_bootloader").unwrap()); + + let uefi_path = out_dir.join("mnemos-x86_64-uefi.img"); + bootloader::UefiBoot::new(&kernel) + .create_disk_image(&uefi_path) + .unwrap(); + + // create a BIOS disk image + let bios_path = out_dir.join("mnemos-x86_64-bios.img"); + bootloader::BiosBoot::new(&kernel) + .create_disk_image(&bios_path) + .unwrap(); + + // pass the disk image paths as env variables to the `main.rs` + println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display()); + println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display()); +} diff --git a/platforms/x86_64/bootloader/src/main.rs b/platforms/x86_64/bootloader/src/main.rs new file mode 100644 index 00000000..20c5aa8c --- /dev/null +++ b/platforms/x86_64/bootloader/src/main.rs @@ -0,0 +1,85 @@ +use clap::Parser; +use std::{fmt, path::PathBuf}; + +/// Boots a MnemOS x86_64 kernel using QEMU. +#[derive(Parser, Debug)] +struct Args { + /// Path to the UEFI disk image. + /// + /// This environment variable is set by the build script and typically does + /// not need to be set manually. + #[clap(long, env = "UEFI_PATH")] + uefi_path: PathBuf, + + /// Path to the BIOS disk image. + /// + /// This environment variable is set by the build script and typically does + /// not need to be set manually. + #[clap(long, env = "BIOS_PATH")] + bios_path: PathBuf, + + /// Path to the QEMU x86_64 executable. + /// + /// Generally, this does not need to be overridden, unless the QEMU binary + /// has a non-standard name or is not on the PATH. + #[clap(long, default_value = "qemu-system-x86_64")] + qemu: PathBuf, + + /// Whether to boot using UEFI or BIOS. + #[clap(long, default_value_t = BootMode::Uefi)] + boot: BootMode, + + /// Extra arguments passed directly to the QEMU command. + #[arg(last = true)] + qemu_args: Vec, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, clap::ValueEnum)] +enum BootMode { + Uefi, + Bios, +} + +fn main() { + let Args { + uefi_path, + bios_path, + boot, + qemu, + qemu_args, + } = Args::parse(); + + let mut cmd = std::process::Command::new(qemu); + match boot { + BootMode::Uefi => { + let uefi_path = uefi_path.display(); + println!("booting using UEFI: {uefi_path}"); + + cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); + cmd.arg("-drive") + .arg(format!("format=raw,file={uefi_path}")); + } + BootMode::Bios => { + let bios_path = bios_path.display(); + println!("booting using BIOS: {bios_path}"); + cmd.arg("-drive") + .arg(format!("format=raw,file={bios_path}")); + } + } + + if !qemu_args.is_empty() { + cmd.args(&qemu_args); + } + + let mut child = cmd.spawn().unwrap(); + child.wait().unwrap(); +} + +impl fmt::Display for BootMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Uefi => f.write_str("uefi"), + Self::Bios => f.write_str("bios"), + } + } +} diff --git a/platforms/x86_64/core/Cargo.toml b/platforms/x86_64/core/Cargo.toml new file mode 100644 index 00000000..621c8dd2 --- /dev/null +++ b/platforms/x86_64/core/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "mnemos-x86_64-core" +version = "0.1.0" +edition = "2021" + +[lib] +test = false +bench = false + +[[bin]] +name = "bootloader" +test = false +bench = false +required-features = ["bootloader_api"] + +[features] + +[dependencies] +acpi = "4.1.1" +# NOTE FOR FUTURE ELIZAS WHO ARE MESSING WITH THIS: the bootloader crate's build +# script is not that good, and breaks if you put this in `cfg(...).dependencies` +# instead of normal [dependencies]. don't move this. +bootloader_api = { version = "0.11", optional = true } +embedded-graphics = "0.7.1" +profont = "0.6.1" + +# kernel +[dependencies.mnemos] +path = "../../../source/kernel" +default-features = false +features = ["serial-trace"] + +[dependencies.tracing] +version = "0.1.37" +features = ["attributes"] +default-features = false + +[dependencies.hal-core] +git = "https://github.com/hawkw/mycelium" +default-features = false + +[dependencies.hal-x86_64] +git = "https://github.com/hawkw/mycelium" + +[dependencies.mycelium-util] +git = "https://github.com/hawkw/mycelium" + +[dependencies.mycelium-alloc] +git = "https://github.com/hawkw/mycelium" +features = ["buddy", "bump"] + +[dependencies.futures] +version = "0.3.21" +features = ["async-await"] +default-features = false \ No newline at end of file diff --git a/platforms/x86_64/core/src/acpi.rs b/platforms/x86_64/core/src/acpi.rs new file mode 100644 index 00000000..ce210538 --- /dev/null +++ b/platforms/x86_64/core/src/acpi.rs @@ -0,0 +1,109 @@ +pub use acpi::{AcpiError, AcpiHandler, AcpiTables}; +use core::{fmt, ptr::NonNull}; +use hal_core::{Address, PAddr}; +use hal_x86_64::mm; + +#[derive(Debug)] +pub enum Error { + Acpi(AcpiError), + Other(&'static str), +} + +pub(super) fn acpi_tables( + rsdp_addr: PAddr, +) -> Result, AcpiError> { + tracing::info!("trying to parse ACPI tables from RSDP..."); + let tables = unsafe { AcpiTables::from_rsdp(IdentityMappedAcpiHandler, rsdp_addr.as_usize()) }?; + tracing::info!("found ACPI tables!"); + Ok(tables) +} + +#[tracing::instrument(err, skip(platform))] +pub fn bringup_smp(platform: &acpi::PlatformInfo) -> Result<(), Error> { + use acpi::platform::{self, interrupt::InterruptModel}; + + tracing::info!(?platform.power_profile); + + let apic = match platform.interrupt_model { + acpi::InterruptModel::Apic(ref apic) => { + tracing::info!("APIC interrupt model detected"); + apic + } + InterruptModel::Unknown => { + return Err(Error::Other( + "MADT does not indicate support for APIC interrupt model!", + )); + } + ref model => { + tracing::warn!(?model, "unknown interrupt model detected"); + return Err(Error::Other( + "MADT does not indicate support for APIC interrupt model!", + )); + } + }; + + tracing::debug!(?apic); + + let platform::ProcessorInfo { + ref application_processors, + ref boot_processor, + } = platform + .processor_info + .as_ref() + .ok_or(Error::Other("no processor information found in MADT!"))?; + tracing::info!("boot processor seems normalish"); + tracing::debug!(?boot_processor); + tracing::info!( + "found {} application processors", + application_processors.len() + ); + tracing::debug!(?application_processors); + tracing::warn!("not starting app processors (SMP support isn't done yet)"); + + Ok(()) +} + +#[derive(Clone)] +pub(super) struct IdentityMappedAcpiHandler; + +impl AcpiHandler for IdentityMappedAcpiHandler { + unsafe fn map_physical_region( + &self, + physical_address: usize, + size: usize, + ) -> acpi::PhysicalMapping { + let paddr = PAddr::from_u64(physical_address as u64); + let vaddr = mm::kernel_vaddr_of(paddr); + let vptr = + NonNull::new(vaddr.as_ptr()).expect("virtual address for ACPI region is not null"); + // we have identity mapped all physical memory, so we don't actually + // have to map any pages --- just tell the ACPI crate that it can use + // this region. + acpi::PhysicalMapping::new(physical_address, vptr, size, size, Self) + } + + fn unmap_physical_region(_region: &acpi::PhysicalMapping) { + // we don't need to unmap anything, since we didn't map anything. :) + } +} + +// === impl Error === + +impl From for Error { + fn from(inner: AcpiError) -> Self { + Self::Acpi(inner) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + // format the ACPI error using its `fmt::Debug` implementation, + // since the ACPI crate doesn't implement `fmt::Display` for its + // errors. + // TODO(eliza): add a `Display` impl upstream... + Self::Acpi(inner) => write!(f, "ACPI error: {inner:?}"), + Self::Other(msg) => fmt::Display::fmt(msg, f), + } + } +} diff --git a/platforms/x86_64/core/src/allocator.rs b/platforms/x86_64/core/src/allocator.rs new file mode 100644 index 00000000..b7107093 --- /dev/null +++ b/platforms/x86_64/core/src/allocator.rs @@ -0,0 +1,91 @@ +use core::{ + alloc::{GlobalAlloc, Layout}, + ptr::NonNull, +}; +use hal_core::{mem, BootInfo, VAddr}; +use kernel::mnemos_alloc::heap::{MnemosAlloc, UnderlyingAllocator}; +use mycelium_alloc::{buddy, bump}; + +/// 1k is enough for anyone. +pub const BUMP_SIZE: usize = 1024; + +/// 32 free lists is enough for anyone. +const FREE_LISTS: usize = 32; + +const MIN_HEAP_SIZE: usize = 32; + +#[derive(Debug)] +pub struct Heap(()); + +#[global_allocator] +pub static AHEAP: MnemosAlloc = MnemosAlloc::new(); + +pub(crate) static HEAP: buddy::Alloc = buddy::Alloc::new(MIN_HEAP_SIZE); +static BUMP: bump::Alloc = bump::Alloc::new(); + +pub(crate) fn init(bootinfo: &impl BootInfo, vm_offset: VAddr) { + HEAP.set_vm_offset(vm_offset); + + let mut regions = 0; + let mut free_regions = 0; + let mut free_bytes = 0; + + for region in bootinfo.memory_map() { + let kind = region.kind(); + let size = region.size(); + tracing::info!( + " {:>10?} {:>15?} {:>15?} B", + region.base_addr(), + kind, + size, + ); + regions += 1; + if region.kind() == mem::RegionKind::FREE { + free_regions += 1; + free_bytes += size; + if unsafe { HEAP.add_region(region) }.is_err() { + tracing::warn!("bad region"); + } + } + } + + assert!( + free_regions > 0, + "no free memory regions found (out of {regions} total)" + ); + + tracing::info!( + "found {} memory regions, {} free regions ({} bytes)", + regions, + free_regions, + free_bytes, + ); +} + +impl UnderlyingAllocator for Heap { + const INIT: Self = Self(()); + unsafe fn init(&self, _: NonNull, _: usize) { + unimplemented!() + } + + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // first, try to allocate from the real heap. + let ptr = HEAP.alloc(layout); + + if ptr.is_null() { + // heap is uninitialized, fall back to the bump region. + return BUMP.alloc(layout); + } + + ptr + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // if this is in the bump region, just leak it. + if BUMP.owns(ptr) { + return; + } + + HEAP.dealloc(ptr, layout); + } +} diff --git a/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs b/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs new file mode 100644 index 00000000..9fdcb13a --- /dev/null +++ b/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs @@ -0,0 +1,87 @@ +use crate::framebuf; +use bootloader_api::info; +use hal_core::{boot::BootInfo, mem, PAddr, VAddr}; +use hal_x86_64::{mm, vga}; + +#[derive(Debug)] +pub struct BootloaderApiBootInfo { + inner: &'static info::BootInfo, + has_framebuffer: bool, +} + +type MemRegionIter = core::slice::Iter<'static, info::MemoryRegion>; + +impl BootInfo for BootloaderApiBootInfo { + type MemoryMap = core::iter::Map mem::Region>; + + type Writer = vga::Writer; + + type Framebuffer = framebuf::FramebufWriter; + + /// Returns the boot info's memory map. + fn memory_map(&self) -> Self::MemoryMap { + fn convert_region_kind(kind: info::MemoryRegionKind) -> mem::RegionKind { + match kind { + info::MemoryRegionKind::Usable => mem::RegionKind::FREE, + // TODO(eliza): make known + info::MemoryRegionKind::UnknownUefi(_) => mem::RegionKind::UNKNOWN, + info::MemoryRegionKind::UnknownBios(_) => mem::RegionKind::UNKNOWN, + info::MemoryRegionKind::Bootloader => mem::RegionKind::BOOT, + _ => mem::RegionKind::UNKNOWN, + } + } + + fn convert_region(region: &info::MemoryRegion) -> mem::Region { + let start = PAddr::from_u64(region.start); + let size = { + let end = PAddr::from_u64(region.end).offset(1); + assert!(start < end, "bad memory range from boot_info!"); + let size = start.difference(end); + assert!(size >= 0); + size as usize + 1 + }; + let kind = convert_region_kind(region.kind); + mem::Region::new(start, size, kind) + } + self.inner.memory_regions[..].iter().map(convert_region) + } + + fn writer(&self) -> Self::Writer { + unimplemented!() + } + + fn framebuffer(&self) -> Option { + if !self.has_framebuffer { + return None; + } + + Some(unsafe { framebuf::mk_framebuf() }) + } + + fn bootloader_name(&self) -> &str { + "rust-osdev/bootloader" + } + + fn init_paging(&self) { + mm::init_paging(self.vm_offset()) + } +} + +impl BootloaderApiBootInfo { + fn vm_offset(&self) -> VAddr { + VAddr::from_u64( + self.inner + .physical_memory_offset + .into_option() + .expect("haha wtf"), + ) + } + + pub(super) fn from_bootloader(inner: &'static mut info::BootInfo) -> Self { + let has_framebuffer = framebuf::init(inner); + Self { + inner, + has_framebuffer, + } + } +} diff --git a/platforms/x86_64/core/src/bin/bootloader/framebuf.rs b/platforms/x86_64/core/src/bin/bootloader/framebuf.rs new file mode 100644 index 00000000..97324d49 --- /dev/null +++ b/platforms/x86_64/core/src/bin/bootloader/framebuf.rs @@ -0,0 +1,99 @@ +// TODO(eliza): eventually, turn this into a nice mnemOS-style driver task... +use bootloader_api::{info, BootInfo}; +use core::{ + mem, + ops::{Deref, DerefMut}, +}; +use hal_x86_64::framebuffer::{self, Framebuffer}; +use mycelium_util::sync::{spin, InitOnce}; + +#[derive(Debug)] +pub struct FramebufGuard(spin::MutexGuard<'static, info::FrameBuffer>); +pub type FramebufWriter = Framebuffer<'static, FramebufGuard>; + +/// Locks the framebuffer and returns a [`FramebufWriter`]. +/// +/// # Safety +/// +/// In release mode, this function *assumes* the frame buffer has been +/// initialized by [`init`]. If this is ever called before [`init`] has been +/// called and returned `true`, this may read uninitialized memory! +pub(super) unsafe fn mk_framebuf() -> FramebufWriter { + let (cfg, buf) = unsafe { + // Safety: we can reasonably assume this will only be called + // after `arch_entry`, so if we've failed to initialize the + // framebuffer...things have gone horribly wrong... + FRAMEBUFFER.get_unchecked() + }; + Framebuffer::new(cfg, FramebufGuard(buf.lock())) +} + +/// Forcibly unlock the framebuffer mutex. +/// +/// # Safety +/// +/// This forcibly unlocks a potentially-locked mutex, violating mutual +/// exclusion! This should only be called in conditions where no other CPU core +/// will *ever* attempt to access the framebuffer again (such as while oopsing). +pub(super) unsafe fn force_unlock() { + if let Some((_, fb)) = FRAMEBUFFER.try_get() { + fb.force_unlock(); + } +} + +/// Try to initialize the framebuffer based on the provided [`BootInfo`]. +/// +/// Returns `true` if the framebuffer is available, or `false` if there is no +/// framebuffer enabled. +/// +/// If the framebuffer has already been initialized, this does nothing. +pub(super) fn init(bootinfo: &mut BootInfo) -> bool { + use info::Optional; + // Has the framebuffer already been initialized? + if FRAMEBUFFER.try_get().is_some() { + return true; + } + + // Okay, try to initialize the framebuffer + let Optional::Some(framebuffer) = mem::replace(&mut bootinfo.framebuffer, Optional::None) + else { + // The boot info does not contain a framebuffer configuration. Nothing + // for us to do! + return false; + }; + + let info = framebuffer.info(); + let cfg = framebuffer::Config { + height: info.height, + width: info.width, + px_bytes: info.bytes_per_pixel, + line_len: info.stride, + px_kind: match info.pixel_format { + info::PixelFormat::Rgb => framebuffer::PixelKind::Rgb, + info::PixelFormat::Bgr => framebuffer::PixelKind::Bgr, + info::PixelFormat::U8 => framebuffer::PixelKind::Gray, + x => unimplemented!("hahaha wtf, found a weird pixel format: {:?}", x), + }, + }; + FRAMEBUFFER.init((cfg, spin::Mutex::new(framebuffer))); + true +} + +static FRAMEBUFFER: InitOnce<(framebuffer::Config, spin::Mutex)> = + InitOnce::uninitialized(); + +impl Deref for FramebufGuard { + type Target = [u8]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.0.buffer() + } +} + +impl DerefMut for FramebufGuard { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.buffer_mut() + } +} diff --git a/platforms/x86_64/core/src/bin/bootloader/main.rs b/platforms/x86_64/core/src/bin/bootloader/main.rs new file mode 100644 index 00000000..28b28cc1 --- /dev/null +++ b/platforms/x86_64/core/src/bin/bootloader/main.rs @@ -0,0 +1,132 @@ +#![no_std] +#![no_main] +#![feature(panic_info_message)] + +#[cfg(not(feature = "bootloader_api"))] +compile_error!( + "building the `mnemos-x86_64-bootloader` binary requires the \ + 'bootloader_api' Cargo feature to be enabled", +); +extern crate alloc; + +use bootloader_api::config::{BootloaderConfig, Mapping}; +use hal_core::{ + framebuffer::{Draw, RgbColor}, + PAddr, VAddr, +}; +use hal_x86_64::cpu; +mod bootinfo; +mod framebuf; + +pub static BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::Dynamic); + // the kernel is mapped into the higher half of the virtual address space. + config.mappings.dynamic_range_start = Some(0xFFFF_8000_0000_0000); + config.mappings.page_table_recursive = Some(Mapping::Dynamic); + + config +}; + +bootloader_api::entry_point!(kernel_start, config = &BOOTLOADER_CONFIG); + +pub fn kernel_start(info: &'static mut bootloader_api::BootInfo) -> ! { + unsafe { + cpu::intrinsics::cli(); + } + + let cfg = { + let phys_offset = info + .physical_memory_offset + .into_option() + // TODO(eliza): does `None` here mean "physical mem is identity + // mapped" or "we don't know where the physical mem is mapped"? + // check the bootloader docs... + .unwrap_or(0); + mnemos_x86_64_core::PlatformConfig { + rsdp_addr: info.rsdp_addr.into_option().map(PAddr::from_u64), + physical_mem_offset: VAddr::from_u64(phys_offset), + } + }; + let bootinfo = bootinfo::BootloaderApiBootInfo::from_bootloader(info); + + let subscriber = { + let framebuf = (|| unsafe { framebuf::mk_framebuf() }) as fn() -> _; + framebuf().fill(RgbColor::BLACK); + mnemos_x86_64_core::trace::TraceSubscriber::new(framebuf) + }; + tracing::subscriber::set_global_default(subscriber) + .expect("tracing subscriber should not have already been set!"); + + let k = mnemos_x86_64_core::init(&bootinfo, cfg); + mnemos_x86_64_core::run(&bootinfo, k) +} + +#[cold] +#[cfg_attr(target_os = "none", panic_handler)] +fn panic(panic: &core::panic::PanicInfo<'_>) -> ! { + use core::fmt::Write; + use embedded_graphics::{ + mono_font::MonoTextStyleBuilder, + pixelcolor::{Rgb888, RgbColor as _}, + prelude::*, + }; + use mnemos_x86_64_core::drivers::framebuf::TextWriter; + + // /!\ disable all interrupts, unlock everything to prevent deadlock /!\ + // + // Safety: it is okay to do this because we are panicking and everything + // is going to die anyway. + unsafe { + // disable all interrupts. + cpu::intrinsics::cli(); + + // TODO(eliza): claim serial + + // unlock the frame buffer + framebuf::force_unlock(); + } + + let mut framebuf = unsafe { framebuf::mk_framebuf() }; + + let mut writer = { + let font = &profont::PROFONT_12_POINT; + let char_height = font.character_size.height; + // write the panic message at the bottom of the framebuffer, so that we + // don't clobber any existing text preceeding the panic (useful for + // debugging). + let point = { + let height_px = framebuf.height() as u32; + let last_line = (height_px - char_height - 10) as i32; + Point::new(10, last_line) + }; + + // scroll the framebuffer up by one line of text to make space for the + // panic message. + framebuf.scroll_vert(char_height as isize); + + let style = MonoTextStyleBuilder::new() + .font(font) + .text_color(Rgb888::WHITE) + .background_color(Rgb888::RED) + .build(); + TextWriter::new(&mut framebuf, style, point) + }; + + // write the panic message + let _ = writer.write_str("mnemOS panicked"); + if let Some(message) = panic.message() { + let _ = writeln!(&mut writer, ":\n {message}"); + } else if let Some(payload) = panic.payload().downcast_ref::<&'static str>() { + let _ = writeln!(&mut writer, ":\n {payload}"); + } else if let Some(payload) = panic.payload().downcast_ref::() { + let _ = writeln!(&mut writer, ":\n {payload}"); + } + + if let Some(location) = panic.location() { + let _ = writeln!(&mut writer, " at {location}"); + } + + // ...and die! + cpu::halt(); +} diff --git a/platforms/x86_64/core/src/drivers/framebuf.rs b/platforms/x86_64/core/src/drivers/framebuf.rs new file mode 100644 index 00000000..5abebc7b --- /dev/null +++ b/platforms/x86_64/core/src/drivers/framebuf.rs @@ -0,0 +1,162 @@ +use core::fmt; +use embedded_graphics::{ + draw_target::DrawTarget, + geometry::Point, + mono_font::MonoTextStyle, + pixelcolor::PixelColor, + text::{self, Text}, + Drawable, +}; +use hal_core::framebuffer::{self, Draw}; + +// TODO(eliza): add emb_display_service implementation! + +#[derive(Debug)] +pub struct TextWriter<'style, 'target, D, C> { + target: framebuffer::DrawTarget<&'target mut D>, + width_px: u32, + start_x: i32, + point: Point, + style: MonoTextStyle<'style, C>, + last_line: i32, +} + +impl<'style, 'target, D, C> TextWriter<'style, 'target, D, C> +where + framebuffer::DrawTarget<&'target mut D>: DrawTarget, + D: Draw, + C: PixelColor, +{ + pub fn new(target: &'target mut D, style: MonoTextStyle<'style, C>, point: Point) -> Self { + let width_px = target.width() as u32; + let height_px = target.height() as u32; + let last_line = { + let char_height = style.font.character_size.height; + (height_px - char_height - 10) as i32 + }; + Self { + target: target.as_draw_target(), + start_x: point.x, + last_line, + width_px, + style, + point, + } + } + + pub fn next_point(&self) -> Point { + self.point + } + + pub fn set_style(&mut self, style: MonoTextStyle<'style, C>) { + self.style = style; + } + + // fn len_to_px(&self, len: usize) -> u32 { + // len as u32 * self.style.font.character_size.width + // } + + fn px_to_len(&self, px: u32) -> usize { + (px / self.style.font.character_size.width) as usize + } + + fn newline(&mut self) { + if self.point.y > self.last_line { + let ydiff = self.point.y - self.last_line; + self.target.inner_mut().scroll_vert(ydiff as isize); + self.point = Point { + y: self.last_line, + x: 10, + }; + } + + // if we have reached the bottom of the screen, we'll need to scroll + // previous framebuffer contents up to make room for new line(s) of + // text. + self.point.y = self.point.y + self.style.font.character_size.height as i32; + self.point.x = self.start_x; + } +} + +impl<'style, 'target, D, C> fmt::Write for TextWriter<'style, 'target, D, C> +where + framebuffer::DrawTarget<&'target mut D>: DrawTarget, + D: Draw, + C: PixelColor, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + // for a couple of reasons, we don't trust the `embedded-graphics` crate + // to handle newlines for us: + // + // 1. it currently only actually produces a carriage return when a + // newline character appears in the *middle* of a string. this means + // that strings beginning or ending with newlines (and strings that + // are *just* newlines) won't advance the write position the way we'd + // expect them to. so, we have to do that part ourself --- it turns + // out that most `fmt::Debug`/`fmt::Display` implementations will + // write a bunch of strings that begin or end with `\n`. + // 2. when we reach the bottom of the screen, we want to scroll the + // previous text up to make room for a new line of text. + // `embedded-graphics` doesn't implement this behav'tior. because we + // want to scroll every time we encounter a newline if we have + // reached the bottom of the screen, this means we have to implement + // *all* newline handling ourselves. + // + // TODO(eliza): currently, our newline handling doesn't honor + // configurable line height. all lines are simply a single character + // high. if we want to do something nicer about line height, we'd have + // to implement that here... + for mut line in s.split_inclusive('\n') { + while !line.is_empty() { + // does this line begin with a newline? + if line.starts_with('\n') { + line = &line[1..]; + self.newline(); + } + + // does this chunk end with a newline? it might not, if: + // (a) it's the last chunk in a string where newlines only occur in + // the beginning/middle. + // (b) the string being written has no newlines (so + // `split_inclusive` will only yield a single chunk) + let mut has_newline = line.ends_with('\n'); + if has_newline { + // if there's a trailing newline, trim it off --- no sense + // making the `embedded-graphics` crate draw an extra character + // it will essentially nop for. + line = &line[..line.len() - 1]; + } + + let mut chunk = line; + + // if the line is longer than the remaining space on the current + // line, wrap the line. + let rem = self.px_to_len(self.width_px - (self.point.x as u32)); + if line.len() > rem { + let (curr, next) = line.split_at(rem as usize); + line = next; + chunk = curr; + has_newline = true; + } else { + line = ""; + } + + // if this line is now empty, it was *just* a newline character, + // so all we have to do is advance the write position. + if !chunk.is_empty() { + self.point = + Text::with_alignment(chunk, self.point, self.style, text::Alignment::Left) + .draw(&mut self.target) + .map_err(|_| fmt::Error)? + }; + + if has_newline { + // carriage return + self.newline(); + } + } + } + + Ok(()) + } +} diff --git a/platforms/x86_64/core/src/drivers/mod.rs b/platforms/x86_64/core/src/drivers/mod.rs new file mode 100644 index 00000000..280dddf1 --- /dev/null +++ b/platforms/x86_64/core/src/drivers/mod.rs @@ -0,0 +1 @@ +pub mod framebuf; diff --git a/platforms/x86_64/core/src/interrupt.rs b/platforms/x86_64/core/src/interrupt.rs new file mode 100644 index 00000000..b3af7e1c --- /dev/null +++ b/platforms/x86_64/core/src/interrupt.rs @@ -0,0 +1,202 @@ +use core::{ + ptr, + sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, +}; +use hal_core::{interrupt, VAddr}; +pub use hal_x86_64::interrupt::*; +use hal_x86_64::{ + cpu::{intrinsics, Ring}, + segment::{self, Gdt}, + task, +}; +use kernel::maitake::time; +use mycelium_util::{fmt, sync}; + +#[tracing::instrument] +pub fn enable_exceptions() { + init_gdt(); + tracing::info!("GDT initialized!"); + + Controller::init::(); + tracing::info!("IDT initialized!"); +} + +#[tracing::instrument(skip(acpi, timer))] +pub fn enable_hardware_interrupts( + acpi: Option<&acpi::InterruptModel>, + timer: &'static time::Timer, +) { + // no way to have an atomic `*const` ptr lol :| + let timer = timer as *const _ as *mut _; + let _timer = TIMER.swap(timer, Ordering::Release); + debug_assert_eq!(_timer, ptr::null_mut()); + + let controller = Controller::enable_hardware_interrupts(acpi, &crate::allocator::HEAP); + controller + .start_periodic_timer(TIMER_INTERVAL) + .expect("10ms should be a reasonable interval for the PIT or local APIC timer..."); + tracing::info!(granularity = ?TIMER_INTERVAL, "global timer initialized") +} + +/// Wait for an interrupt in a spin loop. +/// +/// This is distinct from `core::hint::spin_loop`, as it is intended +/// specifically for waiting for an interrupt, rather than progress from another +/// thread. This should be called on each iteration of a loop that waits on a condition +/// set by an interrupt handler. +/// +/// This function will execute one [`intrinsics::sti`] instruction to enable interrupts +/// followed by one [`intrinsics::hlt`] instruction to halt the CPU. +#[inline(always)] +pub(crate) fn wait_for_interrupt() { + unsafe { + intrinsics::sti(); + intrinsics::hlt(); + } +} + +// TODO(eliza): put this somewhere good. +type StackFrame = [u8; 4096]; + +// chosen by fair dice roll, guaranteed to be random +const DOUBLE_FAULT_STACK_SIZE: usize = 8; + +/// Stack used by ISRs during a double fault. +/// +/// /!\ EXTREMELY SERIOUS WARNING: this has to be `static mut` or else it +/// will go in `.bss` and we'll all die or something. +static mut DOUBLE_FAULT_STACK: [StackFrame; DOUBLE_FAULT_STACK_SIZE] = + [[0; 4096]; DOUBLE_FAULT_STACK_SIZE]; + +static TSS: sync::Lazy = sync::Lazy::new(|| { + tracing::trace!("initializing TSS.."); + let mut tss = task::StateSegment::empty(); + tss.interrupt_stacks[Idt::DOUBLE_FAULT_IST_OFFSET] = unsafe { + // safety: asdf + VAddr::of(&DOUBLE_FAULT_STACK).offset(DOUBLE_FAULT_STACK_SIZE as i32) + }; + tracing::debug!(?tss, "TSS initialized"); + tss +}); + +pub(crate) static GDT: sync::InitOnce = sync::InitOnce::uninitialized(); + +pub const TIMER_INTERVAL: time::Duration = time::Duration::from_millis(10); +static TIMER: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +static TEST_INTERRUPT_WAS_FIRED: AtomicUsize = AtomicUsize::new(0); + +pub(crate) struct InterruptHandlers; + +/// Forcibly unlock the IOs we write to in an oops (VGA buffer and COM1 serial +/// port) to prevent deadlocks if the oops occured while either was locked. +/// +/// # Safety +/// +/// /!\ only call this when oopsing!!! /!\ +impl hal_core::interrupt::Handlers for InterruptHandlers { + fn page_fault(cx: C) + where + C: interrupt::Context + hal_core::interrupt::ctx::PageFault, + { + let fault_vaddr = cx.fault_vaddr(); + let code = cx.display_error_code(); + + // TODO: add a nice fault handler + panic!("page fault at {fault_vaddr:?}\n{code}"); + } + + fn code_fault(cx: C) + where + C: interrupt::Context + interrupt::ctx::CodeFault, + { + // TODO: add a nice fault handler + let _fault = match cx.details() { + Some(deets) => panic!("code fault {}: \n{deets}", cx.fault_kind()), + None => panic!("code fault {}!", cx.fault_kind()), + }; + } + + fn double_fault(_cx: C) + where + C: hal_core::interrupt::Context, + { + // TODO: add a nice fault handler + panic!("double fault"); + } + + fn timer_tick() { + if let Some(timer) = ptr::NonNull::new(TIMER.load(Ordering::Acquire)) { + unsafe { timer.as_ref() }.advance_ticks(1); + } + } + + fn ps2_keyboard(scancode: u8) { + // TODO(eliza): add a keyboard driver + tracing::info!(scancode, "keyoard interrupt!!!"); + } + + fn test_interrupt(cx: C) + where + C: hal_core::interrupt::ctx::Context, + { + let fired = TEST_INTERRUPT_WAS_FIRED.fetch_add(1, Ordering::Release) + 1; + tracing::info!(registers = ?cx.registers(), fired, "lol im in ur test interrupt"); + } +} + +#[inline] +#[tracing::instrument(level = tracing::Level::DEBUG)] +pub(super) fn init_gdt() { + tracing::trace!("initializing GDT..."); + let mut gdt = Gdt::new(); + + // add one kernel code segment + let code_segment = segment::Descriptor::code().with_ring(Ring::Ring0); + let code_selector = gdt.add_segment(code_segment); + tracing::debug!( + descriptor = ?fmt::alt(code_segment), + selector = ?fmt::alt(code_selector), + "added code segment" + ); + + // add the TSS. + + let tss = segment::SystemDescriptor::tss(&TSS); + let tss_selector = gdt.add_sys_segment(tss); + tracing::debug!( + tss.descriptor = ?fmt::alt(tss), + tss.selector = ?fmt::alt(tss_selector), + "added TSS" + ); + + // all done! long mode barely uses this thing lol. + GDT.init(gdt); + + // load the GDT + let gdt = GDT.get(); + tracing::debug!(GDT = ?gdt, "GDT initialized"); + gdt.load(); + + tracing::trace!("GDT loaded"); + + // set new segment selectors + let code_selector = segment::Selector::current_cs(); + tracing::trace!(code_selector = ?fmt::alt(code_selector)); + unsafe { + // set the code segment selector + code_selector.set_cs(); + + // in protected mode and long mode, the code segment, stack segment, + // data segment, and extra segment must all have base address 0 and + // limit `2^64`, since actual segmentation is not used in those modes. + // therefore, we must zero the SS, DS, and ES registers. + segment::Selector::null().set_ss(); + segment::Selector::null().set_ds(); + segment::Selector::null().set_es(); + + task::StateSegment::load_tss(tss_selector); + } + + tracing::debug!("segment selectors set"); +} diff --git a/platforms/x86_64/core/src/lib.rs b/platforms/x86_64/core/src/lib.rs new file mode 100644 index 00000000..b66622f7 --- /dev/null +++ b/platforms/x86_64/core/src/lib.rs @@ -0,0 +1,111 @@ +#![no_std] +extern crate alloc; + +use core::time::Duration; +use hal_core::{boot::BootInfo, PAddr, VAddr}; +use hal_x86_64::cpu::local::GsLocalData; +pub use hal_x86_64::cpu::{local::LocalKey, wait_for_interrupt}; +use kernel::{mnemos_alloc::containers::Box, Kernel, KernelSettings}; + +pub mod acpi; +pub mod allocator; +pub mod drivers; +pub mod interrupt; +pub mod trace; + +#[derive(Debug)] +pub struct PlatformConfig { + pub rsdp_addr: Option, + pub physical_mem_offset: VAddr, +} + +pub fn init(bootinfo: &impl BootInfo, cfg: PlatformConfig) -> &'static Kernel { + interrupt::enable_exceptions(); + bootinfo.init_paging(); + allocator::init(bootinfo, cfg.physical_mem_offset); + + let k = { + let settings = KernelSettings { + max_drivers: 64, // we are a big x86 system with lots of RAM, this can probably be an even bigger number! + timer_granularity: interrupt::TIMER_INTERVAL, + }; + + unsafe { + Box::into_raw(Kernel::new(settings).expect("cannot initialize kernel")) + .as_ref() + .unwrap() + } + }; + + init_acpi(k, cfg.rsdp_addr); + // TODO: PCI? + + // init boot processor's core-local data + GsLocalData::init(); + tracing::info!("set up the boot processor's local data"); + + // TODO: spawn drivers (UART, keyboard, ...) + k.initialize(async { + loop { + k.timer().sleep(Duration::from_secs(5)).await; + tracing::info!("help im trapped in an x86_64 operating system kernel!"); + } + }) + .unwrap(); + + k +} + +pub fn run(bootinfo: &impl BootInfo, kernel: &'static Kernel) -> ! { + let _ = bootinfo; + tracing::info!("started kernel run loop\n--------------------\n"); + kernel.set_global_timer().unwrap(); + + // TODO(eliza): this currently uses a periodic timer, rather than a + // freewheeling timer like other MnemOS kernels. The periodic timer is + // somewhat less efficient, as it results in us being woken every 10ms + // regardless of what timeouts are pending. If we used a freewheeling timer + // instead, we could sleep until a task is actually ready. + // + // However, this would require some upstream changes to the mycelium HAL to + // better support freewheeling timers. For now, the simpler periodic timer + // runloop works fine, I guess... + loop { + // drive the task scheduler + let tick = kernel.tick(); + + // turn the timer wheel if it wasn't turned recently and no one else is + // holding a lock, ensuring any pending timer ticks are consumed. + let turn = kernel.timer().force_advance_ticks(0); + + // if there are no woken tasks, wait for an interrupt. otherwise, + // continue ticking. + let has_remaining = tick.has_remaining || turn.has_remaining(); + if !has_remaining { + interrupt::wait_for_interrupt(); + } + } +} + +fn init_acpi(k: &'static Kernel, rsdp_addr: Option) { + if let Some(rsdp) = rsdp_addr { + let acpi = acpi::acpi_tables(rsdp); + let platform_info = acpi.and_then(|acpi| acpi.platform_info()); + match platform_info { + Ok(platform) => { + tracing::debug!("found ACPI platform info"); + interrupt::enable_hardware_interrupts(Some(&platform.interrupt_model), k.timer()); + acpi::bringup_smp(&platform) + .expect("failed to bring up application processors! this is bad news!"); + return; + } + Err(error) => tracing::warn!(?error, "missing ACPI platform info"), + } + } else { + // TODO(eliza): try using MP Table to bringup application processors? + tracing::warn!("no RSDP from bootloader, skipping SMP bringup"); + } + + // no ACPI + interrupt::enable_hardware_interrupts(None, k.timer()) +} diff --git a/platforms/x86_64/core/src/trace.rs b/platforms/x86_64/core/src/trace.rs new file mode 100644 index 00000000..7aa00970 --- /dev/null +++ b/platforms/x86_64/core/src/trace.rs @@ -0,0 +1,157 @@ +use crate::drivers::framebuf::TextWriter; +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, + sync::atomic::{AtomicU64, Ordering}, +}; +use embedded_graphics::{ + draw_target::DrawTarget, + geometry::Point, + mono_font::{MonoTextStyle, MonoTextStyleBuilder}, + pixelcolor::{Rgb888, RgbColor}, +}; +use hal_core::framebuffer; +use hal_x86_64::framebuffer::Framebuffer; +use kernel::{ + serial_trace::SerialSubscriber, + tracing::{ + level_filters::LevelFilter, span, subscriber::Interest, Event, Metadata, Subscriber, + }, +}; +use mycelium_util::sync::InitOnce; + +static SERIAL: InitOnce = InitOnce::uninitialized(); + +pub struct TraceSubscriber +where + F: Deref + DerefMut + 'static, +{ + framebuf: fn() -> Framebuffer<'static, F>, + point: AtomicU64, + _f: PhantomData, +} + +#[inline] +fn with_serial(f: impl FnOnce(&SerialSubscriber) -> T) -> Option { + SERIAL.try_get().map(f) +} + +impl TraceSubscriber +where + F: Deref + DerefMut + 'static, +{ + pub fn new(framebuf: fn() -> Framebuffer<'static, F>) -> Self { + Self { + framebuf, + point: AtomicU64::new(pack_point(Point { x: 10, y: 10 })), + _f: PhantomData, + } + } +} + +fn style(color: Rgb888) -> MonoTextStyle<'static, Rgb888> { + MonoTextStyleBuilder::new() + .font(&profont::PROFONT_12_POINT) + .text_color(color) + .build() +} + +impl Subscriber for TraceSubscriber +where + F: Deref + DerefMut + 'static, + for<'a> framebuffer::DrawTarget<&'a mut Framebuffer<'static, F>>: DrawTarget, // jesus christ... +{ + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + with_serial(|serial| serial.enabled(metadata)).unwrap_or(!metadata.is_span()) + } + + fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { + with_serial(|serial| serial.register_callsite(metadata)).unwrap_or_else(Interest::sometimes) + } + + fn max_level_hint(&self) -> Option { + with_serial(Subscriber::max_level_hint).unwrap_or(None) + } + + fn new_span(&self, span: &span::Attributes<'_>) -> span::Id { + with_serial(|serial| serial.new_span(span)) + .expect("spans are not enabled before serial comes up") + } + + fn record(&self, _: &span::Id, _: &span::Record<'_>) { + // todo!("eliza") + } + + fn enter(&self, span: &span::Id) { + with_serial(|serial| serial.enter(span)); + } + + fn exit(&self, span: &span::Id) { + with_serial(|serial| serial.enter(span)); + } + + fn record_follows_from(&self, _: &span::Id, _: &span::Id) { + // nop for now + } + + fn event(&self, event: &Event<'_>) { + use core::fmt::Write; + + if with_serial(|serial| serial.event(event)).is_none() { + let point = unpack_point(self.point.load(Ordering::Acquire)); + let mut framebuf = (self.framebuf)(); + let meta = event.metadata(); + let (lvl_color, lvl_str) = match *meta.level() { + tracing::Level::TRACE => (Rgb888::BLUE, "TRCE"), + tracing::Level::DEBUG => (Rgb888::CYAN, "DBUG"), + tracing::Level::INFO => (Rgb888::GREEN, "INFO"), + tracing::Level::WARN => (Rgb888::YELLOW, "WARN"), + tracing::Level::ERROR => (Rgb888::RED, "ERR!"), + }; + + // write the level in the per-level color. + let mut writer = TextWriter::new(&mut framebuf, style(lvl_color), point); + writer.write_str(lvl_str).unwrap(); + + writer.set_style(style(Rgb888::new(128, 128, 128))); + write!(&mut writer, " {}:", meta.target()).unwrap(); + + writer.set_style(style(Rgb888::WHITE)); + + event.record( + &mut (|field: &tracing::field::Field, value: &'_ (dyn core::fmt::Debug + '_)| { + if field.name() == "message" { + write!(&mut writer, " {value:?}").unwrap(); + } else { + write!(&mut writer, " {field}={value:?}").unwrap(); + } + }) as &mut dyn tracing::field::Visit, + ); + writeln!(&mut writer, "").unwrap(); + + self.point + .store(pack_point(writer.next_point()), Ordering::Release); + } + } + + fn clone_span(&self, span: &span::Id) -> span::Id { + with_serial(|serial| serial.clone_span(span)) + .expect("spans are not enabled until serial is enabled") + } + + fn try_close(&self, span: span::Id) -> bool { + with_serial(move |serial| serial.try_close(span)) + .expect("spans are not enabled until serial is enabled") + } +} + +const fn pack_point(Point { x, y }: Point) -> u64 { + (x as u64) << 32 | y as u64 +} + +const fn unpack_point(u: u64) -> Point { + const Y_MASK: u64 = u32::MAX as u64; + let x = (u >> 32) as i32; + let y = (u & Y_MASK) as i32; + Point { x, y } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b69c3ca8..c463aee9 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -12,4 +12,5 @@ targets = [ "riscv64imac-unknown-none-elf", # ESP32-C3 "riscv32imac-unknown-none-elf", + "x86_64-unknown-none", ] diff --git a/source/kernel/src/lib.rs b/source/kernel/src/lib.rs index 732a5ff1..f6df50bb 100644 --- a/source/kernel/src/lib.rs +++ b/source/kernel/src/lib.rs @@ -333,7 +333,11 @@ impl Kernel { #[cfg(feature = "serial-trace")] if settings.sermux_trace.enabled { - crate::serial_trace::SerialSubscriber::start(self, settings.sermux_trace).await; + let subscriber = + crate::serial_trace::SerialSubscriber::start(self, settings.sermux_trace) + .await; + tracing::subscriber::set_global_default(subscriber) + .expect("default tracing subscriber already set!"); } if settings.sermux_loopback.enabled { diff --git a/source/kernel/src/serial_trace.rs b/source/kernel/src/serial_trace.rs index 022f4073..7ce0e6aa 100644 --- a/source/kernel/src/serial_trace.rs +++ b/source/kernel/src/serial_trace.rs @@ -49,7 +49,7 @@ static SHARED: Shared = Shared { // === impl SerialSubscriber === impl SerialSubscriber { - pub async fn start(k: &'static crate::Kernel, settings: SerialTraceSettings) { + pub async fn start(k: &'static crate::Kernel, settings: SerialTraceSettings) -> Self { SHARED .max_level .store(level_to_u8(settings.initial_level), Ordering::Release); @@ -69,12 +69,10 @@ impl SerialSubscriber { shared: &SHARED, }; - // set the default tracing collector - tracing::subscriber::set_global_default(subscriber) - .expect("failed to set global default subscriber"); - // spawn a worker to read from the channel and write to the serial port. k.spawn(Self::worker(&SHARED, rx, isr_rx, port, k)).await; + + subscriber } /// Serialize a `TraceEvent`, returning `true` if the event was correctly serialized.