├── .gitignore ├── src ├── arch │ ├── aarch64 │ │ ├── serial.rs │ │ ├── drivers │ │ │ ├── qemu_serial.rs │ │ │ ├── mod.rs │ │ │ └── xlnx_serial.rs │ │ ├── paging.rs │ │ ├── link.ld │ │ ├── console.rs │ │ ├── entry.s │ │ ├── entry.rs │ │ └── mod.rs │ ├── riscv64 │ │ ├── link.ld │ │ ├── console.rs │ │ ├── start.rs │ │ ├── address_range.rs │ │ └── mod.rs │ ├── mod.rs │ └── x86_64 │ │ ├── console.rs │ │ ├── link_fc.ld │ │ ├── link.ld │ │ ├── physicalmem.rs │ │ ├── mod.rs │ │ ├── paging.rs │ │ ├── entry_fc.s │ │ ├── multiboot.rs │ │ ├── entry.s │ │ └── firecracker.rs ├── os │ ├── mod.rs │ ├── none │ │ ├── console.rs │ │ ├── mod.rs │ │ └── allocator │ │ │ ├── bootstrap.rs │ │ │ └── mod.rs │ └── uefi │ │ ├── console.rs │ │ ├── allocator.rs │ │ └── mod.rs ├── main.rs ├── bump_allocator.rs ├── log.rs ├── macros.rs └── fdt.rs ├── xtask ├── .gitignore ├── Cargo.toml └── src │ ├── ci │ ├── firecracker_vm_config.json │ ├── mod.rs │ ├── firecracker.rs │ └── qemu.rs │ ├── clippy.rs │ ├── object.rs │ ├── main.rs │ ├── flags.rs │ ├── artifact.rs │ ├── cargo_build.rs │ ├── build.rs │ └── target.rs ├── .cargo └── config.toml ├── .git-blame-ignore-revs ├── .gitattributes ├── rustfmt.toml ├── data ├── x86_64 │ ├── hello_c │ ├── hello_world │ └── hello_world-microvm ├── aarch64 │ └── hello_world ├── riscv64 │ └── hello_world └── aarch64_be │ └── hello_world ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── ci.yml ├── CITATION.cff ├── .vscode └── settings.json ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /src/arch/aarch64/serial.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /xtask/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Upgrade to Rust 2024 style edition 2 | 19e5470538e15e7c5a1bed185ff7327cf60bfb8a 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png filter=lfs diff=lfs merge=lfs -text 2 | data/** filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | hard_tabs = true 3 | imports_granularity = "Module" 4 | -------------------------------------------------------------------------------- /data/x86_64/hello_c: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:36a3eb9896b5b9ee21b505c35425d15258f2261629d9328435def2182816af87 3 | size 1927456 4 | -------------------------------------------------------------------------------- /data/aarch64/hello_world: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:815814403c76f928d318e534ff4976c33af55476ebaf2a2bd6cde6e0b41e1654 3 | size 709736 4 | -------------------------------------------------------------------------------- /data/riscv64/hello_world: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:34f70ff158cf076bada7f3fac232b9be29c50c50bdbcc20b95bc0938bfc1b945 3 | size 1061848 4 | -------------------------------------------------------------------------------- /data/x86_64/hello_world: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c709c04809bb537634bbac6d3ffaccd30f14143bb7a237907548f0d255825b26 3 | size 897784 4 | -------------------------------------------------------------------------------- /data/aarch64_be/hello_world: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:652a3617267de4e2dc79533188dfb35e165ca4324dd354b9ed9c434de2f6320b 3 | size 801232 4 | -------------------------------------------------------------------------------- /data/x86_64/hello_world-microvm: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b3d942b440d94663700ad7c59fa46c2890f6630ca8fbc78a34dba350bad7055c 3 | size 867440 4 | -------------------------------------------------------------------------------- /src/os/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(target_os = "none")] { 3 | mod none; 4 | pub use self::none::*; 5 | } else if #[cfg(target_os = "uefi")] { 6 | mod uefi; 7 | pub use self::uefi::*; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | clap = { version = "4", features = ["derive"] } 9 | llvm-tools = "0.1" 10 | ovmf-prebuilt = "0.2" 11 | sysinfo = "0.37" 12 | xshell = "0.2" 13 | -------------------------------------------------------------------------------- /src/arch/riscv64/link.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | loader_start = ADDR (.text.start); 3 | 4 | .text.start 0x80200000 : { *(.text._start) } 5 | .text : { *(.text.*) } 6 | .rodata : { *(.rodata.*) } 7 | .data : { *(.data.*) } 8 | .bss : { *(.bss.*) } 9 | 10 | loader_end = .; 11 | } 12 | -------------------------------------------------------------------------------- /src/arch/riscv64/console.rs: -------------------------------------------------------------------------------- 1 | use sbi_rt::Physical; 2 | use sptr::Strict; 3 | 4 | #[derive(Default)] 5 | pub struct Console(()); 6 | 7 | impl Console { 8 | pub fn write_bytes(&mut self, bytes: &[u8]) { 9 | sbi_rt::console_write(Physical::new(bytes.len(), bytes.as_ptr().expose_addr(), 0)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /xtask/src/ci/firecracker_vm_config.json: -------------------------------------------------------------------------------- 1 | {{ 2 | "boot-source": {{ 3 | "kernel_image_path": "{kernel_image_path}", 4 | "initrd_path": "{initrd_path}", 5 | "boot_args": "" 6 | }}, 7 | "drives": [], 8 | "machine-config": {{ 9 | "vcpu_count": 1, 10 | "mem_size_mib": 256, 11 | "smt": false 12 | }} 13 | }} 14 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(target_arch = "aarch64")] { 3 | mod aarch64; 4 | pub use self::aarch64::*; 5 | } else if #[cfg(target_arch = "riscv64")] { 6 | mod riscv64; 7 | pub use self::riscv64::*; 8 | } else if #[cfg(all(target_arch = "x86_64"))] { 9 | mod x86_64; 10 | pub use self::x86_64::*; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | timezone: "Europe/Berlin" 11 | 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | timezone: "Europe/Berlin" 17 | -------------------------------------------------------------------------------- /src/arch/x86_64/console.rs: -------------------------------------------------------------------------------- 1 | use uart_16550::SerialPort; 2 | 3 | pub struct Console { 4 | serial_port: SerialPort, 5 | } 6 | 7 | impl Console { 8 | pub fn write_bytes(&mut self, bytes: &[u8]) { 9 | for byte in bytes.iter().copied() { 10 | self.serial_port.send(byte); 11 | } 12 | } 13 | } 14 | 15 | impl Default for Console { 16 | fn default() -> Self { 17 | let mut serial_port = unsafe { SerialPort::new(0x3F8) }; 18 | serial_port.init(); 19 | Self { serial_port } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Kröning" 5 | given-names: "Martin" 6 | orcid: "https://orcid.org/0009-0005-0622-4229" 7 | - family-names: "Lankes" 8 | given-names: "Stefan" 9 | orcid: "https://orcid.org/0000-0003-4718-2238" 10 | 11 | title: "The Hermit Loader" 12 | version: 0.5.4 13 | doi: 10.5281/zenodo.14640797 14 | date-released: 2024-12-10 15 | url: "https://github.com/hermit-os/loader" 16 | -------------------------------------------------------------------------------- /xtask/src/ci/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Subcommand; 3 | 4 | mod firecracker; 5 | mod qemu; 6 | 7 | /// Run CI tasks. 8 | #[derive(Subcommand)] 9 | pub enum Ci { 10 | Firecracker(firecracker::Firecracker), 11 | Qemu(qemu::Qemu), 12 | } 13 | 14 | impl Ci { 15 | pub fn run(self) -> Result<()> { 16 | match self { 17 | Self::Firecracker(firecracker) => firecracker.run(), 18 | Self::Qemu(qemu) => qemu.run(), 19 | } 20 | } 21 | } 22 | 23 | fn in_ci() -> bool { 24 | std::env::var_os("CI") == Some("true".into()) 25 | } 26 | -------------------------------------------------------------------------------- /src/arch/x86_64/link_fc.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf64-x86-64") 2 | ENTRY(_start) 3 | phys = 0x000000100000; 4 | 5 | SECTIONS 6 | { 7 | loader_start = phys; 8 | .mboot phys : AT(ADDR(.mboot)) { 9 | *(.mboot) 10 | } 11 | .text ALIGN(4096) : AT(ADDR(.text)) { 12 | *(.text) 13 | *(.text.*) 14 | } 15 | .rodata ALIGN(4096) : AT(ADDR(.rodata)) { 16 | *(.rodata) 17 | *(.rodata.*) 18 | } 19 | .data ALIGN(4096) : AT(ADDR(.data)) { 20 | *(.data) 21 | *(.data.*) 22 | } 23 | .bss ALIGN(4096) : AT(ADDR(.bss)) { 24 | *(.bss) 25 | *(.bss.*) 26 | } 27 | loader_end = .; 28 | } 29 | -------------------------------------------------------------------------------- /src/arch/x86_64/link.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf64-x86-64") 2 | OUTPUT_ARCH("i386:x86-64") 3 | ENTRY(_start) 4 | phys = 0x000000100000; 5 | 6 | SECTIONS 7 | { 8 | loader_start = phys; 9 | .mboot phys : AT(ADDR(.mboot)) { 10 | *(.mboot) 11 | } 12 | .text ALIGN(4096) : AT(ADDR(.text)) { 13 | *(.text) 14 | *(.text.*) 15 | } 16 | .rodata ALIGN(4096) : AT(ADDR(.rodata)) { 17 | *(.rodata) 18 | *(.rodata.*) 19 | } 20 | .data ALIGN(4096) : AT(ADDR(.data)) { 21 | *(.data) 22 | *(.data.*) 23 | } 24 | .bss ALIGN(4096) : AT(ADDR(.bss)) { 25 | *(.bss) 26 | *(.bss.*) 27 | } 28 | loader_end = .; 29 | } 30 | -------------------------------------------------------------------------------- /xtask/src/clippy.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Args; 3 | use xshell::cmd; 4 | 5 | use crate::target::Target; 6 | 7 | /// Run Clippy for all targets. 8 | #[derive(Args)] 9 | pub struct Clippy; 10 | 11 | impl Clippy { 12 | pub fn run(self) -> Result<()> { 13 | let sh = crate::sh()?; 14 | 15 | for target in [ 16 | Target::X86_64, 17 | Target::X86_64Fc, 18 | Target::X86_64Uefi, 19 | Target::Aarch64, 20 | Target::Riscv64, 21 | ] { 22 | target.install()?; 23 | let triple = target.triple(); 24 | let feature_flags = target.feature_flags(); 25 | cmd!(sh, "cargo clippy --target={triple} {feature_flags...}").run()?; 26 | } 27 | 28 | cmd!(sh, "cargo clippy --package xtask").run()?; 29 | 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /xtask/src/object.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::Result; 4 | use xshell::cmd; 5 | 6 | pub struct Object(PathBuf); 7 | 8 | impl From for Object { 9 | fn from(object: PathBuf) -> Self { 10 | Self(object) 11 | } 12 | } 13 | 14 | impl From for PathBuf { 15 | fn from(value: Object) -> Self { 16 | value.0 17 | } 18 | } 19 | 20 | impl AsRef for Object { 21 | fn as_ref(&self) -> &Path { 22 | &self.0 23 | } 24 | } 25 | 26 | impl Object { 27 | pub fn convert_to_elf32_i386(&self) -> Result<()> { 28 | let sh = crate::sh()?; 29 | let objcopy = crate::binutil("objcopy")?; 30 | let object = self.as_ref(); 31 | cmd!(sh, "{objcopy} --output-target elf32-i386 {object}").run()?; 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/os/none/console.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use one_shot_mutex::sync::OneShotMutex; 4 | 5 | use crate::arch; 6 | 7 | pub struct Console { 8 | console: Option, 9 | } 10 | 11 | impl Console { 12 | const fn new() -> Self { 13 | Self { console: None } 14 | } 15 | 16 | #[cfg(target_arch = "aarch64")] 17 | pub fn get(&mut self) -> &mut arch::Console { 18 | self.console.get_or_insert_with(arch::Console::default) 19 | } 20 | } 21 | 22 | impl fmt::Write for Console { 23 | fn write_str(&mut self, s: &str) -> fmt::Result { 24 | self.console 25 | .get_or_insert_with(arch::Console::default) 26 | .write_bytes(s.as_bytes()); 27 | Ok(()) 28 | } 29 | } 30 | 31 | pub static CONSOLE: OneShotMutex = OneShotMutex::new(Console::new()); 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // "rust-analyzer.cargo.target": "aarch64-unknown-none-softfloat", 3 | // "rust-analyzer.cargo.target": "riscv64imac-unknown-none-elf", 4 | "rust-analyzer.cargo.target": "x86_64-unknown-none", 5 | // "rust-analyzer.cargo.target": "x86_64-unknown-uefi", 6 | "rust-analyzer.check.overrideCommand": [ 7 | "cargo", 8 | "clippy", 9 | "--quiet", 10 | "--message-format=json", 11 | "--target=aarch64-unknown-none-softfloat", 12 | "--target=riscv64imac-unknown-none-elf", 13 | "--target=x86_64-unknown-none", 14 | "--target=x86_64-unknown-uefi", 15 | ], 16 | "rust-analyzer.check.targets": [ 17 | "aarch64-unknown-none-softfloat", 18 | "riscv64imac-unknown-none-elf", 19 | "x86_64-unknown-none", 20 | "x86_64-unknown-uefi", 21 | ] 22 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | env: 9 | GH_TOKEN: ${{ github.token }} 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | - uses: dtolnay/rust-toolchain@stable 17 | - run: cargo xtask build --release --target aarch64 18 | - run: cargo xtask build --release --target riscv64 19 | - run: cargo xtask build --release --target x86_64 20 | - run: cargo xtask build --release --target x86_64-fc --features fc 21 | - run: cargo xtask build --release --target x86_64-uefi 22 | - uses: dtolnay/rust-toolchain@nightly 23 | with: 24 | components: rust-src 25 | - run: cargo xtask build --release --target aarch64_be 26 | - run: gh release create ${GITHUB_REF#refs/tags/} --draft --title ${GITHUB_REF#refs/tags/v} target/release/hermit-loader-* 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/arch/aarch64/drivers/qemu_serial.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroU32; 2 | use core::ptr::NonNull; 3 | 4 | use volatile::{VolatileFieldAccess, VolatileRef}; 5 | 6 | use crate::arch::drivers::SerialSuccess::Success; 7 | use crate::arch::drivers::{SerialDriver, SerialSuccess}; 8 | 9 | #[repr(C)] 10 | #[derive(VolatileFieldAccess)] 11 | struct QemuPort { 12 | out: u8, 13 | } 14 | 15 | pub struct QemuSerial { 16 | regs: VolatileRef<'static, QemuPort>, 17 | } 18 | 19 | impl QemuSerial { 20 | pub fn from_addr(base_addr: NonZeroU32) -> QemuSerial { 21 | Self { 22 | regs: unsafe { 23 | VolatileRef::new(NonNull::new_unchecked(base_addr.get() as *mut QemuPort)) 24 | }, 25 | } 26 | } 27 | } 28 | 29 | impl SerialDriver for QemuSerial { 30 | fn init(&mut self) {} 31 | fn putc(&mut self, c: u8) -> SerialSuccess { 32 | self.regs.as_mut_ptr().out().write(c); 33 | Success(c) 34 | } 35 | 36 | fn putstr(&mut self, s: &[u8]) { 37 | for c in s.iter().copied() { 38 | let _ = self.putc(c); 39 | } 40 | } 41 | fn get_addr(&self) -> u32 { 42 | self.regs.as_ptr().as_raw_ptr().as_ptr() as u32 43 | } 44 | 45 | fn wait_empty(&mut self) {} 46 | } 47 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![warn(rust_2018_idioms)] 4 | #![warn(unsafe_op_in_unsafe_fn)] 5 | #![allow(unstable_name_collisions)] 6 | #![allow(clippy::missing_safety_doc)] 7 | 8 | use ::log::info; 9 | use hermit_entry::boot_info::{BootInfo, RawBootInfo}; 10 | 11 | #[macro_use] 12 | mod macros; 13 | 14 | mod arch; 15 | mod bump_allocator; 16 | #[cfg(any(target_os = "uefi", target_arch = "x86_64"))] 17 | mod fdt; 18 | mod log; 19 | mod os; 20 | 21 | #[cfg(any(target_os = "uefi", all(target_arch = "x86_64", target_os = "none")))] 22 | extern crate alloc; 23 | 24 | trait BootInfoExt { 25 | fn write(self) -> &'static RawBootInfo; 26 | } 27 | 28 | impl BootInfoExt for BootInfo { 29 | fn write(self) -> &'static RawBootInfo { 30 | info!("boot_info = {self:#x?}"); 31 | 32 | take_static::take_static! { 33 | static RAW_BOOT_INFO: Option = None; 34 | } 35 | 36 | let raw_boot_info = RAW_BOOT_INFO.take().unwrap(); 37 | 38 | raw_boot_info.insert(RawBootInfo::from(self)) 39 | } 40 | } 41 | 42 | #[doc(hidden)] 43 | fn _print(args: core::fmt::Arguments<'_>) { 44 | use core::fmt::Write; 45 | 46 | self::os::CONSOLE.lock().write_fmt(args).unwrap(); 47 | } 48 | -------------------------------------------------------------------------------- /src/arch/aarch64/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroU32; 2 | 3 | use enum_dispatch::enum_dispatch; 4 | use fdt::node::FdtNode; 5 | use qemu_serial::QemuSerial; 6 | use xlnx_serial::XlnxSerial; 7 | 8 | pub mod qemu_serial; 9 | pub mod xlnx_serial; 10 | 11 | pub enum SerialSuccess { 12 | Success(T), 13 | ERetry, 14 | } 15 | 16 | #[enum_dispatch] 17 | pub trait SerialDriver { 18 | fn init(&mut self); 19 | fn putc(&mut self, c: u8) -> SerialSuccess; 20 | fn putstr(&mut self, s: &[u8]); 21 | fn get_addr(&self) -> u32; 22 | fn wait_empty(&mut self); 23 | } 24 | 25 | #[enum_dispatch(SerialDriver)] 26 | pub enum SerialPort { 27 | Qemu(QemuSerial), 28 | Xlnx(XlnxSerial), 29 | } 30 | 31 | pub fn get_device<'a>(node: FdtNode<'_, 'a>) -> Option { 32 | let compat = node.compatible()?; 33 | let reg = node.reg()?.next()?; 34 | 35 | for id in compat.all() { 36 | if id == "arm,pl011" { 37 | return Some(SerialPort::Qemu(QemuSerial::from_addr( 38 | NonZeroU32::new(reg.starting_address as u32).unwrap(), 39 | ))); 40 | } else if id == "xlnx,xuartlite" { 41 | return Some(SerialPort::Xlnx(XlnxSerial::from_addr( 42 | NonZeroU32::new(reg.starting_address as u32).unwrap(), 43 | ))); 44 | } 45 | } 46 | None 47 | } 48 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | //! See . 2 | 3 | mod artifact; 4 | mod build; 5 | mod cargo_build; 6 | mod ci; 7 | mod clippy; 8 | mod object; 9 | mod target; 10 | 11 | use std::env; 12 | use std::path::{Path, PathBuf}; 13 | 14 | use anyhow::{Result, anyhow}; 15 | use clap::Parser; 16 | 17 | #[derive(Parser)] 18 | enum Cli { 19 | Build(build::Build), 20 | #[command(subcommand)] 21 | Ci(ci::Ci), 22 | Clippy(clippy::Clippy), 23 | } 24 | 25 | impl Cli { 26 | fn run(self) -> Result<()> { 27 | match self { 28 | Self::Build(build) => build.run(), 29 | Self::Ci(ci) => ci.run(), 30 | Self::Clippy(clippy) => clippy.run(), 31 | } 32 | } 33 | } 34 | 35 | fn main() -> Result<()> { 36 | let cli = Cli::parse(); 37 | cli.run() 38 | } 39 | 40 | pub fn sh() -> Result { 41 | let sh = xshell::Shell::new()?; 42 | let project_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); 43 | sh.change_dir(project_root); 44 | Ok(sh) 45 | } 46 | 47 | pub fn binutil(name: &str) -> Result { 48 | let exe_suffix = env::consts::EXE_SUFFIX; 49 | let exe = format!("llvm-{name}{exe_suffix}"); 50 | 51 | let path = llvm_tools::LlvmTools::new() 52 | .map_err(|err| anyhow!("{err:?}"))? 53 | .tool(&exe) 54 | .ok_or_else(|| anyhow!("could not find {exe}"))?; 55 | 56 | Ok(path) 57 | } 58 | -------------------------------------------------------------------------------- /src/os/none/mod.rs: -------------------------------------------------------------------------------- 1 | mod allocator; 2 | mod console; 3 | 4 | use core::fmt::Write; 5 | use core::mem::MaybeUninit; 6 | use core::slice; 7 | 8 | use hermit_entry::elf::KernelObject; 9 | use log::info; 10 | 11 | pub use self::console::CONSOLE; 12 | use crate::arch; 13 | 14 | unsafe extern "C" { 15 | static loader_end: u8; 16 | static loader_start: u8; 17 | } 18 | 19 | /// Entry Point of the BIOS Loader 20 | /// (called from entry.asm or entry.rs) 21 | pub(crate) unsafe extern "C" fn loader_main() -> ! { 22 | crate::log::init(); 23 | 24 | unsafe { 25 | info!("Loader: [{:p} - {:p}]", &loader_start, &loader_end); 26 | } 27 | 28 | let kernel = arch::find_kernel(); 29 | let kernel = KernelObject::parse(kernel).unwrap(); 30 | 31 | let mem_size = kernel.mem_size(); 32 | let kernel_addr = unsafe { arch::get_memory(mem_size as u64) }; 33 | let kernel_addr = kernel.start_addr().unwrap_or(kernel_addr); 34 | let memory = unsafe { 35 | slice::from_raw_parts_mut( 36 | sptr::from_exposed_addr_mut::>(kernel_addr as usize), 37 | mem_size, 38 | ) 39 | }; 40 | 41 | let kernel_info = kernel.load_kernel(memory, memory.as_ptr() as u64); 42 | 43 | unsafe { arch::boot_kernel(kernel_info) } 44 | } 45 | 46 | #[panic_handler] 47 | fn panic(info: &core::panic::PanicInfo<'_>) -> ! { 48 | // We can't use `println!` or related macros, because `_print` unwraps a result and might panic again 49 | writeln!(crate::os::CONSOLE.lock(), "[LOADER] {info}").ok(); 50 | 51 | loop {} 52 | } 53 | -------------------------------------------------------------------------------- /src/bump_allocator.rs: -------------------------------------------------------------------------------- 1 | //! A bump allocator. 2 | //! 3 | //! This is a simple allocator design which can only allocate and not deallocate. 4 | 5 | use core::cell::Cell; 6 | use core::mem::MaybeUninit; 7 | use core::ptr::NonNull; 8 | 9 | use allocator_api2::alloc::{AllocError, Allocator, Layout}; 10 | 11 | /// A simple, `!Sync` implementation of a bump allocator. 12 | /// 13 | /// This allocator manages the provided memory. 14 | pub struct BumpAllocator { 15 | mem: Cell<&'static mut [MaybeUninit]>, 16 | } 17 | 18 | unsafe impl Allocator for BumpAllocator { 19 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 20 | let ptr: *mut [MaybeUninit] = self.allocate_slice(layout)?; 21 | Ok(NonNull::new(ptr as *mut [u8]).unwrap()) 22 | } 23 | 24 | unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) {} 25 | } 26 | 27 | impl BumpAllocator { 28 | fn allocate_slice(&self, layout: Layout) -> Result<&'static mut [MaybeUninit], AllocError> { 29 | let mem = self.mem.take(); 30 | let align_offset = mem.as_ptr().align_offset(layout.align()); 31 | let mid = layout.size() + align_offset; 32 | if mid > mem.len() { 33 | self.mem.set(mem); 34 | Err(AllocError) 35 | } else { 36 | let (alloc, remaining) = mem.split_at_mut(mid); 37 | self.mem.set(remaining); 38 | Ok(&mut alloc[align_offset..]) 39 | } 40 | } 41 | } 42 | 43 | impl From<&'static mut [MaybeUninit]> for BumpAllocator { 44 | fn from(mem: &'static mut [MaybeUninit]) -> Self { 45 | Self { 46 | mem: Cell::new(mem), 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/os/uefi/console.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | use core::fmt; 3 | use core::ptr::NonNull; 4 | 5 | use one_shot_mutex::sync::OneShotMutex; 6 | use uefi::Event; 7 | use uefi::boot::{EventType, Tpl}; 8 | 9 | use crate::arch; 10 | 11 | pub enum Console { 12 | None, 13 | BootServices, 14 | Native { console: arch::Console }, 15 | } 16 | 17 | impl Console { 18 | const fn new() -> Self { 19 | Self::None 20 | } 21 | 22 | fn exit_boot_services(&mut self) { 23 | assert!(matches!(self, Self::BootServices { .. })); 24 | *self = Self::Native { 25 | console: arch::Console::default(), 26 | }; 27 | } 28 | 29 | fn init(&mut self) { 30 | assert!(matches!(self, Console::None)); 31 | unsafe { 32 | uefi::boot::create_event( 33 | EventType::SIGNAL_EXIT_BOOT_SERVICES, 34 | Tpl::NOTIFY, 35 | Some(exit_boot_services), 36 | None, 37 | ) 38 | .unwrap(); 39 | } 40 | *self = Console::BootServices; 41 | } 42 | } 43 | 44 | impl fmt::Write for Console { 45 | fn write_str(&mut self, s: &str) -> fmt::Result { 46 | match self { 47 | Console::None => { 48 | self.init(); 49 | self.write_str(s)?; 50 | } 51 | Console::BootServices => uefi::system::with_stdout(|stdout| stdout.write_str(s))?, 52 | Console::Native { console } => console.write_bytes(s.as_bytes()), 53 | } 54 | Ok(()) 55 | } 56 | } 57 | 58 | unsafe extern "efiapi" fn exit_boot_services(_e: Event, _ctx: Option>) { 59 | CONSOLE.lock().exit_boot_services(); 60 | } 61 | 62 | pub static CONSOLE: OneShotMutex = OneShotMutex::new(Console::new()); 63 | -------------------------------------------------------------------------------- /src/arch/aarch64/paging.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::marker::PhantomData; 4 | 5 | /// Number of Offset bits of a virtual address for a 4 KiB page, which are shifted away to get its Page Frame Number (PFN). 6 | pub const PAGE_BITS: usize = 12; 7 | 8 | /// Number of bits of the index in each table (L0Table, L1Table, L2Table, L3Table). 9 | pub const PAGE_MAP_BITS: usize = 9; 10 | 11 | /// A mask where PAGE_MAP_BITS are set to calculate a table index. 12 | pub const PAGE_MAP_MASK: usize = 0x1FF; 13 | 14 | /// A generic interface to support all possible page sizes. 15 | /// 16 | /// This is defined as a subtrait of Copy to enable #[derive(Clone, Copy)] for Page. 17 | /// Currently, deriving implementations for these traits only works if all dependent types implement it as well. 18 | pub trait PageSize: Copy { 19 | /// The page size in bytes. 20 | const SIZE: usize; 21 | } 22 | 23 | /// A 4 KiB page mapped in the PGT. 24 | #[derive(Clone, Copy)] 25 | pub enum BasePageSize {} 26 | impl PageSize for BasePageSize { 27 | const SIZE: usize = 4096; 28 | } 29 | 30 | /// A 2 MiB page mapped in the PDT. 31 | #[derive(Clone, Copy)] 32 | pub enum LargePageSize {} 33 | impl PageSize for LargePageSize { 34 | const SIZE: usize = 2 * 1024 * 1024; 35 | } 36 | 37 | /// A memory page of the size given by S. 38 | #[derive(Clone, Copy)] 39 | struct Page { 40 | /// Virtual memory address of this page. 41 | /// This is rounded to a page size boundary on creation. 42 | virtual_address: usize, 43 | 44 | /// Required by Rust to support the S parameter. 45 | size: PhantomData, 46 | } 47 | -------------------------------------------------------------------------------- /src/arch/x86_64/physicalmem.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroUsize; 2 | 3 | use log::debug; 4 | use one_shot_mutex::sync::OneShotMutex; 5 | use x86_64::structures::paging::{FrameAllocator, FrameDeallocator, PageSize, PhysFrame, Size4KiB}; 6 | 7 | static PHYS_ALLOC: OneShotMutex> = OneShotMutex::new(None); 8 | 9 | struct PhysAllocInner { 10 | next: NonZeroUsize, 11 | } 12 | 13 | impl PhysAllocInner { 14 | pub fn new(addr: NonZeroUsize) -> Self { 15 | Self { next: addr } 16 | } 17 | 18 | pub fn allocate(&mut self, size: usize) -> usize { 19 | assert_ne!(size, 0); 20 | assert_eq!(size % Size4KiB::SIZE as usize, 0); 21 | 22 | let addr = self.next.get(); 23 | self.next = self.next.checked_add(size).unwrap(); 24 | addr 25 | } 26 | } 27 | pub struct PhysAlloc; 28 | 29 | impl PhysAlloc { 30 | pub fn init(addr: usize) { 31 | let mut phys_alloc = PHYS_ALLOC.lock(); 32 | assert!(phys_alloc.is_none()); 33 | phys_alloc.replace(PhysAllocInner::new(addr.try_into().unwrap())); 34 | } 35 | 36 | pub fn allocate(size: usize) -> usize { 37 | PHYS_ALLOC.lock().as_mut().unwrap().allocate(size) 38 | } 39 | } 40 | 41 | unsafe impl FrameAllocator for PhysAlloc { 42 | fn allocate_frame(&mut self) -> Option> { 43 | let addr = Self::allocate(S::SIZE as usize) as u64; 44 | Some(PhysFrame::from_start_address(x86_64::PhysAddr::new(addr)).unwrap()) 45 | } 46 | } 47 | 48 | impl FrameDeallocator for PhysAlloc { 49 | unsafe fn deallocate_frame(&mut self, frame: PhysFrame) { 50 | debug!("Tried to free {frame:?}"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/arch/aarch64/link.ld: -------------------------------------------------------------------------------- 1 | /* Parts of this linker script are directly taken from Andre Richters Project: 2 | * https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/blob/master/16_virtual_mem_part4_higher_half_kernel/src/bsp/raspberrypi/link.ld 3 | */ 4 | 5 | ENTRY(_start) 6 | phys = 0x40200000; 7 | 8 | PHDRS 9 | { 10 | segment_ro PT_LOAD FLAGS(4); /* 4 == RO */ 11 | segment_rx PT_LOAD FLAGS(5); /* 5 == RX */ 12 | segment_rw PT_LOAD FLAGS(6); /* 6 == RW */ 13 | } 14 | 15 | SECTIONS 16 | { 17 | . = phys; 18 | loader_start = .; 19 | .text : { 20 | *(.text) 21 | *(.text.*) 22 | } :segment_rx 23 | .rodata : ALIGN(8) { 24 | *(.rodata) 25 | *(.rodata.*) 26 | } :segment_ro 27 | .got : ALIGN(8) { 28 | /* Global offset table Todo */ 29 | *(.got) 30 | } :segment_ro 31 | .data : ALIGN(8) { 32 | *(.data) 33 | *(.data.*) 34 | } :segment_rw 35 | .bss : ALIGN(8) { 36 | *(.bss) 37 | *(.bss.*) 38 | } :segment_rw 39 | . = ALIGN(4K); /* Align to page boundary */ 40 | /*********************************************************************************************** 41 | * Boot Core Stack 42 | ***********************************************************************************************/ 43 | __boot_core_stack_start = .; /* ^ */ 44 | /* | stack */ 45 | . += 16K; /* | growth */ 46 | /* | direction */ 47 | __boot_core_stack_end_exclusive = .; /* | */ 48 | loader_end = .; 49 | prog_size = loader_end - loader_start; 50 | } 51 | -------------------------------------------------------------------------------- /xtask/src/flags.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::target::Target; 4 | 5 | xflags::xflags! { 6 | src "./src/flags.rs" 7 | 8 | /// Run custom build command. 9 | cmd xtask { 10 | /// Build the kernel. 11 | cmd build 12 | { 13 | /// Build for the target. 14 | required --target target: Target 15 | /// Directory for all generated artifacts. 16 | optional --target-dir target_dir: PathBuf 17 | /// Build artifacts in release mode, with optimizations. 18 | optional -r, --release 19 | /// Build artifacts with the specified profile. 20 | optional --profile profile: String 21 | } 22 | 23 | /// Run clippy for all targets. 24 | cmd clippy {} 25 | } 26 | } 27 | 28 | // generated start 29 | // The following code is generated by `xflags` macro. 30 | // Run `env UPDATE_XFLAGS=1 cargo build` to regenerate. 31 | #[derive(Debug)] 32 | pub struct Xtask { 33 | pub subcommand: XtaskCmd, 34 | } 35 | 36 | #[derive(Debug)] 37 | pub enum XtaskCmd { 38 | Build(Build), 39 | Clippy(Clippy), 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct Build { 44 | pub target: Target, 45 | pub target_dir: Option, 46 | pub release: bool, 47 | pub profile: Option, 48 | } 49 | 50 | #[derive(Debug)] 51 | pub struct Clippy; 52 | 53 | impl Xtask { 54 | #[allow(dead_code)] 55 | pub fn from_env_or_exit() -> Self { 56 | Self::from_env_or_exit_() 57 | } 58 | 59 | #[allow(dead_code)] 60 | pub fn from_env() -> xflags::Result { 61 | Self::from_env_() 62 | } 63 | 64 | #[allow(dead_code)] 65 | pub fn from_vec(args: Vec) -> xflags::Result { 66 | Self::from_vec_(args) 67 | } 68 | } 69 | // generated end 70 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use anstyle::AnsiColor; 4 | use log::{Level, LevelFilter, Metadata, Record}; 5 | 6 | struct Logger; 7 | 8 | impl log::Log for Logger { 9 | fn enabled(&self, metadata: &Metadata<'_>) -> bool { 10 | let level = option_env!("LOADER_LOG") 11 | .map(|var| var.parse().unwrap()) 12 | .unwrap_or(Level::Info); 13 | metadata.level() <= level 14 | } 15 | 16 | fn log(&self, record: &Record<'_>) { 17 | if self.enabled(record.metadata()) { 18 | let level = ColorLevel(record.level()); 19 | let args = record.args(); 20 | println!("[LOADER][{level}] {args}"); 21 | } 22 | } 23 | 24 | fn flush(&self) {} 25 | } 26 | 27 | struct ColorLevel(Level); 28 | 29 | impl fmt::Display for ColorLevel { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | let level = self.0; 32 | 33 | if no_color() { 34 | write!(f, "{level}") 35 | } else { 36 | let color = match level { 37 | Level::Trace => AnsiColor::Magenta, 38 | Level::Debug => AnsiColor::Blue, 39 | Level::Info => AnsiColor::Green, 40 | Level::Warn => AnsiColor::Yellow, 41 | Level::Error => AnsiColor::Red, 42 | }; 43 | 44 | let style = anstyle::Style::new().fg_color(Some(color.into())); 45 | write!(f, "{style}{level}{style:#}") 46 | } 47 | } 48 | } 49 | 50 | fn no_color() -> bool { 51 | option_env!("NO_COLOR").is_some_and(|val| !val.is_empty()) 52 | } 53 | 54 | pub fn init() { 55 | static LOGGER: Logger = Logger; 56 | log::set_logger(&LOGGER).unwrap(); 57 | let level_filter = option_env!("LOADER_LOG") 58 | .map(|var| var.parse().unwrap()) 59 | .unwrap_or(LevelFilter::Info); 60 | log::set_max_level(level_filter); 61 | } 62 | -------------------------------------------------------------------------------- /xtask/src/ci/firecracker.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | use anyhow::Result; 5 | use clap::Args; 6 | use xshell::cmd; 7 | 8 | use crate::build::Build; 9 | 10 | /// Run hermit-rs images on Firecracker. 11 | #[derive(Args)] 12 | pub struct Firecracker { 13 | /// Run Firecracker using `sudo`. 14 | #[arg(long)] 15 | sudo: bool, 16 | 17 | #[command(flatten)] 18 | build: Build, 19 | 20 | #[arg(long, default_value_t = String::from("hello_world-microvm"))] 21 | image: String, 22 | } 23 | 24 | impl Firecracker { 25 | pub fn run(mut self) -> Result<()> { 26 | self.build.cargo_build.features.push("fc".to_string()); 27 | 28 | self.build.run()?; 29 | 30 | let sh = crate::sh()?; 31 | 32 | let config = format!( 33 | include_str!("firecracker_vm_config.json"), 34 | kernel_image_path = self.build.dist_object().display(), 35 | initrd_path = self.build.ci_image(&self.image).display(), 36 | ); 37 | eprintln!("firecracker config"); 38 | eprintln!("{config}"); 39 | let config_path = Path::new("firecracker_vm_config.json"); 40 | sh.write_file(config_path, config)?; 41 | 42 | let firecracker = env::var("FIRECRACKER").unwrap_or_else(|_| "firecracker".to_string()); 43 | let program = if self.sudo { 44 | "sudo" 45 | } else { 46 | firecracker.as_str() 47 | }; 48 | let arg = self.sudo.then_some(firecracker.as_str()); 49 | 50 | let log_path = Path::new("firecracker.log"); 51 | sh.write_file(log_path, "")?; 52 | let res = cmd!(sh, "{program} {arg...} --no-api --config-file {config_path} --log-path {log_path} --level Info --show-level --show-log-origin").run(); 53 | let log = sh.read_file(log_path)?; 54 | 55 | eprintln!("firecracker log"); 56 | eprintln!("{log}"); 57 | res?; 58 | 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/os/uefi/allocator.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | use core::alloc::{GlobalAlloc, Layout}; 3 | use core::mem::MaybeUninit; 4 | use core::ptr::{self, NonNull}; 5 | 6 | use allocator_api2::alloc::Allocator; 7 | use one_shot_mutex::sync::OneShotMutex; 8 | 9 | use crate::bump_allocator::BumpAllocator; 10 | 11 | pub enum GlobalAllocator { 12 | Uefi, 13 | Bump(BumpAllocator), 14 | } 15 | 16 | pub struct LockedAllocator(OneShotMutex); 17 | 18 | impl LockedAllocator { 19 | pub const fn uefi() -> Self { 20 | Self(OneShotMutex::new(GlobalAllocator::Uefi)) 21 | } 22 | } 23 | 24 | unsafe impl GlobalAlloc for LockedAllocator { 25 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 26 | match &*self.0.lock() { 27 | GlobalAllocator::Uefi => unsafe { uefi::allocator::Allocator.alloc(layout) }, 28 | GlobalAllocator::Bump(bump) => bump 29 | .allocate(layout) 30 | // FIXME: Use NonNull::as_mut_ptr once `slice_ptr_get` is stabilized 31 | // https://github.com/rust-lang/rust/issues/74265 32 | .map_or(ptr::null_mut(), |ptr| ptr.as_ptr().cast()), 33 | } 34 | } 35 | 36 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 37 | match &*self.0.lock() { 38 | GlobalAllocator::Uefi => unsafe { uefi::allocator::Allocator.dealloc(ptr, layout) }, 39 | GlobalAllocator::Bump(bump) => unsafe { 40 | bump.deallocate(NonNull::new(ptr).unwrap(), layout) 41 | }, 42 | } 43 | } 44 | } 45 | 46 | #[global_allocator] 47 | static ALLOCATOR: LockedAllocator = LockedAllocator::uefi(); 48 | 49 | pub fn exit_boot_services() { 50 | assert!(matches!(*ALLOCATOR.0.lock(), GlobalAllocator::Uefi)); 51 | 52 | let mem = vec![MaybeUninit::uninit(); 4096].leak(); 53 | 54 | let bump = BumpAllocator::from(mem); 55 | 56 | *ALLOCATOR.0.lock() = GlobalAllocator::Bump(bump); 57 | } 58 | -------------------------------------------------------------------------------- /xtask/src/artifact.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use clap::Args; 4 | 5 | use crate::object::Object; 6 | use crate::target::Target; 7 | 8 | #[derive(Args)] 9 | pub struct Artifact { 10 | /// Target. 11 | #[arg(value_enum, long)] 12 | pub target: Target, 13 | 14 | /// Directory for all generated artifacts. 15 | #[arg(long, id = "DIRECTORY")] 16 | pub target_dir: Option, 17 | 18 | /// Build artifacts in release mode, with optimizations. 19 | #[arg(short, long)] 20 | pub release: bool, 21 | 22 | /// Build artifacts with the specified profile. 23 | #[arg(long, id = "PROFILE-NAME")] 24 | pub profile: Option, 25 | } 26 | 27 | impl Artifact { 28 | pub fn profile(&self) -> &str { 29 | self.profile 30 | .as_deref() 31 | .unwrap_or(if self.release { "release" } else { "dev" }) 32 | } 33 | 34 | pub fn profile_path_component(&self) -> &str { 35 | match self.profile() { 36 | "dev" => "debug", 37 | profile => profile, 38 | } 39 | } 40 | 41 | pub fn target_dir(&self) -> &Path { 42 | self.target_dir 43 | .as_deref() 44 | .unwrap_or_else(|| Path::new("target")) 45 | } 46 | 47 | pub fn build_object(&self) -> Object { 48 | [ 49 | self.target_dir(), 50 | self.target.triple().as_ref(), 51 | self.profile_path_component().as_ref(), 52 | self.target.image_name().as_ref(), 53 | ] 54 | .iter() 55 | .collect::() 56 | .into() 57 | } 58 | 59 | pub fn dist_object(&self) -> Object { 60 | [ 61 | self.target_dir(), 62 | self.profile_path_component().as_ref(), 63 | self.target.dist_name().as_ref(), 64 | ] 65 | .iter() 66 | .collect::() 67 | .into() 68 | } 69 | 70 | pub fn ci_image(&self, image: &str) -> PathBuf { 71 | ["data", self.target.arch(), image].iter().collect() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/arch/aarch64/console.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroU32; 2 | 3 | use fdt::Fdt; 4 | 5 | use crate::arch::aarch64::drivers::SerialDriver; 6 | use crate::arch::drivers::qemu_serial::QemuSerial; 7 | use crate::arch::drivers::xlnx_serial::XlnxSerial; 8 | use crate::arch::drivers::{SerialPort, get_device}; 9 | 10 | pub struct Console { 11 | stdout: SerialPort, 12 | } 13 | 14 | fn stdout() -> SerialPort { 15 | /// Physical address of UART0 at Qemu's virt emulation 16 | const SERIAL_PORT_ADDRESS: u32 = 0x09000000; 17 | 18 | let dtb = unsafe { 19 | Fdt::from_ptr(sptr::from_exposed_addr(super::get_dtb_addr() as usize)) 20 | .expect(".dtb file has invalid header") 21 | }; 22 | 23 | let property = dtb.chosen().stdout(); 24 | property 25 | .and_then(|node| get_device(node)) 26 | .unwrap_or(SerialPort::Qemu(QemuSerial::from_addr( 27 | NonZeroU32::new(SERIAL_PORT_ADDRESS).unwrap(), 28 | ))) 29 | } 30 | 31 | impl Console { 32 | pub fn write_bytes(&mut self, bytes: &[u8]) { 33 | self.stdout.putstr(bytes); 34 | } 35 | 36 | pub(super) fn get_stdout(&self) -> u32 { 37 | self.stdout.get_addr() 38 | } 39 | 40 | pub(crate) fn set_stdout(&mut self, stdout: u32) { 41 | match self.stdout { 42 | SerialPort::Qemu(_) => { 43 | self.stdout = 44 | SerialPort::Qemu(QemuSerial::from_addr(NonZeroU32::new(stdout).unwrap())) 45 | } 46 | SerialPort::Xlnx(_) => { 47 | self.stdout = 48 | SerialPort::Xlnx(XlnxSerial::from_addr(NonZeroU32::new(stdout).unwrap())) 49 | } 50 | } 51 | self.stdout.init(); 52 | } 53 | 54 | pub(crate) fn wait_empty(&mut self) { 55 | self.stdout.wait_empty(); 56 | } 57 | } 58 | 59 | impl Default for Console { 60 | fn default() -> Self { 61 | let stdout = stdout(); 62 | Self { stdout } 63 | } 64 | } 65 | 66 | unsafe impl Send for Console {} 67 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Prints to the standard output. 2 | /// 3 | /// Adapted from [`std::print`]. 4 | /// 5 | /// [`std::print`]: https://doc.rust-lang.org/stable/std/macro.print.html 6 | #[macro_export] 7 | macro_rules! print { 8 | ($($arg:tt)*) => {{ 9 | $crate::_print(::core::format_args!($($arg)*)); 10 | }}; 11 | } 12 | 13 | /// Prints to the standard output, with a newline. 14 | /// 15 | /// Adapted from [`std::println`]. 16 | /// 17 | /// [`std::println`]: https://doc.rust-lang.org/stable/std/macro.println.html 18 | #[macro_export] 19 | macro_rules! println { 20 | () => { 21 | $crate::print!("\n") 22 | }; 23 | ($($arg:tt)*) => {{ 24 | $crate::_print(::core::format_args!("{}\n", format_args!($($arg)*))); 25 | }}; 26 | } 27 | 28 | /// Prints and returns the value of a given expression for quick and dirty 29 | /// debugging. 30 | // Copied from std/macros.rs 31 | #[macro_export] 32 | macro_rules! dbg { 33 | // NOTE: We cannot use `concat!` to make a static string as a format argument 34 | // of `eprintln!` because `file!` could contain a `{` or 35 | // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!` 36 | // will be malformed. 37 | () => { 38 | $crate::println!("[{}:{}]", ::core::file!(), ::core::line!()) 39 | }; 40 | ($val:expr $(,)?) => { 41 | // Use of `match` here is intentional because it affects the lifetimes 42 | // of temporaries - https://stackoverflow.com/a/48732525/1063961 43 | match $val { 44 | tmp => { 45 | $crate::println!("[{}:{}] {} = {:#?}", 46 | ::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp); 47 | tmp 48 | } 49 | } 50 | }; 51 | ($($val:expr),+ $(,)?) => { 52 | ($($crate::dbg!($val)),+,) 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /xtask/src/cargo_build.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | 3 | use clap::Args; 4 | use xshell::Cmd; 5 | 6 | use crate::artifact::Artifact; 7 | 8 | #[derive(Args)] 9 | pub struct CargoBuild { 10 | #[command(flatten)] 11 | pub artifact: Artifact, 12 | 13 | /// Do not activate the `default` feature. 14 | #[arg(long)] 15 | no_default_features: bool, 16 | 17 | /// Space or comma separated list of features to activate. 18 | #[arg(long)] 19 | pub features: Vec, 20 | } 21 | 22 | pub trait CmdExt { 23 | fn cargo_build_args(self, cargo_build: &CargoBuild) -> Self; 24 | fn target_dir_args(self, cargo_build: &CargoBuild) -> Self; 25 | } 26 | 27 | impl CmdExt for Cmd<'_> { 28 | fn cargo_build_args(self, cargo_build: &CargoBuild) -> Self { 29 | let cmd = self 30 | .target_dir_args(cargo_build) 31 | .args(cargo_build.no_default_features_args()) 32 | .args(cargo_build.features_args()) 33 | .args(cargo_build.release_args()); 34 | 35 | if let Some(profile) = &cargo_build.artifact.profile { 36 | cmd.args(&["--profile", profile]) 37 | } else { 38 | cmd 39 | } 40 | } 41 | 42 | fn target_dir_args(self, cargo_build: &CargoBuild) -> Self { 43 | if let Some(target_dir) = &cargo_build.artifact.target_dir { 44 | self.args::<&[&OsStr]>(&["--target-dir".as_ref(), target_dir.as_ref()]) 45 | } else { 46 | self 47 | } 48 | } 49 | } 50 | 51 | impl CargoBuild { 52 | fn release_args(&self) -> &'static [&'static str] { 53 | if self.artifact.release { 54 | &["--release"] 55 | } else { 56 | &[] 57 | } 58 | } 59 | 60 | fn no_default_features_args(&self) -> &'static [&'static str] { 61 | if self.no_default_features { 62 | &["--no-default-features"] 63 | } else { 64 | &[] 65 | } 66 | } 67 | 68 | fn features_args(&self) -> impl Iterator { 69 | self.features 70 | .iter() 71 | .flat_map(|feature| ["--features", feature.as_str()]) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/arch/riscv64/start.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; 3 | 4 | use fdt::Fdt; 5 | 6 | static mut STACK: Stack = Stack::new(); 7 | static HART_ID: AtomicUsize = AtomicUsize::new(0); 8 | static FDT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); 9 | 10 | pub fn get_hart_id() -> usize { 11 | HART_ID.load(Ordering::Relaxed) 12 | } 13 | 14 | pub fn get_fdt_ptr() -> *const u8 { 15 | FDT.load(Ordering::Relaxed).cast_const() 16 | } 17 | 18 | pub fn get_fdt() -> Fdt<'static> { 19 | // SAFETY: We trust the FDT pointer provided by the firmware 20 | unsafe { Fdt::from_ptr(get_fdt_ptr()).unwrap() } 21 | } 22 | 23 | pub fn get_stack_ptr() -> *mut u8 { 24 | // SAFETY: We only create a pointer here 25 | let stack_top = ptr::addr_of_mut!(STACK); 26 | // SAFETY: Pointing directly past the object is allowed 27 | let stack_bottom = unsafe { stack_top.add(1) }; 28 | stack_bottom.cast::() 29 | } 30 | 31 | // TODO: Migrate to Constrained Naked Functions once stabilized 32 | // https://github.com/rust-lang/rust/issues/90957 33 | // TODO: Migrate to asm_const for Stack::SIZE once stabilized 34 | // https://github.com/rust-lang/rust/issues/93332 35 | #[no_mangle] 36 | #[naked_function::naked] 37 | pub unsafe extern "C" fn _start(hart_id: usize, fdt: *const u8) -> ! { 38 | asm!( 39 | // Initialize stack 40 | "la sp, {BOOT_STACK}", 41 | "li t0, 0x8000", 42 | "add sp, sp, t0", 43 | 44 | "j {start}", 45 | 46 | BOOT_STACK = sym STACK, 47 | start = sym start, 48 | ) 49 | } 50 | 51 | extern "C" fn start(hart_id: usize, fdt: *const u8) -> ! { 52 | HART_ID.store(hart_id, Ordering::Relaxed); 53 | FDT.store(fdt.cast_mut(), Ordering::Relaxed); 54 | 55 | unsafe { crate::os::loader_main() } 56 | } 57 | 58 | // Align to page size 59 | #[repr(C, align(0x1000))] 60 | pub struct Stack([u8; Self::SIZE]); 61 | 62 | impl Stack { 63 | const SIZE: usize = 0x8000; 64 | 65 | pub const fn new() -> Self { 66 | Self([0; Self::SIZE]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hermit-loader" 3 | version = "0.5.6" 4 | authors = ["Stefan Lankes ", "Colin Finck "] 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | edition = "2024" 8 | 9 | [dependencies] 10 | align-address = "0.3" 11 | allocator-api2 = { version = "0.4", default-features = false } 12 | anstyle = { version = "1", default-features = false } 13 | cfg-if = "1" 14 | hermit-entry = { version = "0.10", features = ["loader"] } 15 | log = "0.4" 16 | one-shot-mutex = "0.2" 17 | sptr = "0.3" 18 | take-static = "0.1" 19 | vm-fdt = { version = "0.3", default-features = false, features = ["alloc"] } 20 | 21 | [features] 22 | default = [] 23 | fc = [] 24 | 25 | [target.'cfg(all(target_os = "none", target_arch = "x86_64"))'.dependencies] 26 | multiboot = "0.8" 27 | 28 | [target.'cfg(target_arch = "x86_64")'.dependencies] 29 | uart_16550 = "0.4" 30 | x86_64 = { version = "0.15", default-features = false, features = ["instructions"] } 31 | 32 | [target.'cfg(target_arch = "aarch64")'.dependencies] 33 | aarch64-cpu = "11" 34 | enum_dispatch = "0.3" 35 | fdt = { version = "0.1" } 36 | goblin = { version = "0.10", default-features = false, features = ["elf64"] } 37 | volatile = { version = "0.6", features = ["derive"] } 38 | 39 | [target.'cfg(target_os = "uefi")'.dependencies] 40 | uefi = { version = "0.36", features = ["alloc", "panic_handler", "qemu"] } 41 | 42 | [target.'cfg(target_arch = "riscv64")'.dependencies] 43 | fdt = "0.1" 44 | naked-function = "0.1" 45 | sbi-rt = "0.0.3" 46 | sptr = "0.3" 47 | 48 | [profile.dev] 49 | # This is a workaround for the loader growing too large to boot with QEMU's multiboot. 50 | # This broke last with nightly-2024-06-29 (https://github.com/rust-lang/rust/compare/9c3bc805dd9cb84019c124b9a50fdff1e62a7ec9...e9e6e2e444c30c23a9c878a88fbc3978c2acad95) 51 | # We should fix this properly, allowing larger loaders to still function correctly. 52 | opt-level = 1 53 | 54 | [profile.release] 55 | strip = "debuginfo" 56 | lto = true 57 | codegen-units = 1 58 | 59 | [workspace] 60 | members = [ 61 | "xtask", 62 | ] 63 | -------------------------------------------------------------------------------- /src/arch/riscv64/address_range.rs: -------------------------------------------------------------------------------- 1 | // TODO: Move this into its own crate. 2 | #![allow(dead_code)] 3 | 4 | use core::cmp::Ordering; 5 | use core::fmt; 6 | use core::ops::Range; 7 | 8 | use align_address::Align; 9 | 10 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 11 | pub struct AddressRange { 12 | start: usize, 13 | end: usize, 14 | } 15 | 16 | impl AddressRange { 17 | pub fn new(start: usize, end: usize) -> Option { 18 | (start <= end).then_some(Self { start, end }) 19 | } 20 | 21 | pub fn from_start_len(start: usize, len: usize) -> Self { 22 | Self { 23 | start, 24 | end: start + len, 25 | } 26 | } 27 | 28 | pub fn overlaps(self, other: Self) -> bool { 29 | self.partial_cmp(&other).is_none() 30 | } 31 | 32 | pub fn next(self, len: usize) -> Self { 33 | Self::from_start_len(self.end, len) 34 | } 35 | 36 | pub fn align_to(self, align: usize) -> Self { 37 | Self { 38 | start: self.start.align_down(align), 39 | end: self.end.align_up(align), 40 | } 41 | } 42 | 43 | pub fn start(self) -> usize { 44 | self.start 45 | } 46 | 47 | pub fn end(self) -> usize { 48 | self.end 49 | } 50 | 51 | pub fn len(self) -> usize { 52 | self.end - self.start 53 | } 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct TryFromRangeError(()); 58 | 59 | impl TryFrom> for AddressRange { 60 | type Error = TryFromRangeError; 61 | 62 | fn try_from(value: Range<*const T>) -> Result { 63 | Self::new(value.start as usize, value.end as usize).ok_or(TryFromRangeError(())) 64 | } 65 | } 66 | 67 | impl fmt::Display for AddressRange { 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 69 | let Self { start, end } = self; 70 | let len = self.len(); 71 | write!(f, "{start:#x}..{end:#x} (len = {len:#10x})") 72 | } 73 | } 74 | 75 | impl PartialOrd for AddressRange { 76 | fn partial_cmp(&self, other: &Self) -> Option { 77 | if self.end <= other.start { 78 | Some(Ordering::Less) 79 | } else if self.start >= other.end { 80 | Some(Ordering::Greater) 81 | } else if self == other { 82 | Some(Ordering::Equal) 83 | } else { 84 | None 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /xtask/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::env::{self, VarError}; 2 | use std::path::PathBuf; 3 | 4 | use anyhow::Result; 5 | use clap::Args; 6 | use xshell::cmd; 7 | 8 | use crate::cargo_build::{CargoBuild, CmdExt}; 9 | use crate::target::Target; 10 | 11 | /// Build the kernel. 12 | #[derive(Args)] 13 | pub struct Build { 14 | #[command(flatten)] 15 | pub cargo_build: CargoBuild, 16 | } 17 | 18 | impl Build { 19 | pub fn run(&self) -> Result<()> { 20 | self.cargo_build.artifact.target.install()?; 21 | 22 | let sh = crate::sh()?; 23 | 24 | eprintln!("Building loader"); 25 | cmd!(sh, "cargo build") 26 | .env("CARGO_ENCODED_RUSTFLAGS", self.cargo_encoded_rustflags()?) 27 | .args(self.cargo_build.artifact.target.cargo_args()) 28 | .cargo_build_args(&self.cargo_build) 29 | .run()?; 30 | 31 | let build_object = self.cargo_build.artifact.build_object(); 32 | let dist_object = self.cargo_build.artifact.dist_object(); 33 | eprintln!( 34 | "Copying {} to {}", 35 | build_object.as_ref().display(), 36 | dist_object.as_ref().display() 37 | ); 38 | sh.create_dir(dist_object.as_ref().parent().unwrap())?; 39 | sh.copy_file(&build_object, &dist_object)?; 40 | 41 | if self.cargo_build.artifact.target == Target::X86_64 { 42 | eprintln!("Converting object to elf32-i386"); 43 | dist_object.convert_to_elf32_i386()?; 44 | } 45 | 46 | eprintln!("Loader available at {}", dist_object.as_ref().display()); 47 | Ok(()) 48 | } 49 | 50 | fn cargo_encoded_rustflags(&self) -> Result { 51 | let outer_rustflags = match env::var("CARGO_ENCODED_RUSTFLAGS") { 52 | Ok(s) => Some(s), 53 | Err(VarError::NotPresent) => None, 54 | Err(err) => return Err(err.into()), 55 | }; 56 | 57 | let mut rustflags = outer_rustflags 58 | .as_ref() 59 | .map(|s| vec![s.as_str()]) 60 | .unwrap_or_default(); 61 | 62 | rustflags.extend(self.cargo_build.artifact.target.rustflags()); 63 | 64 | Ok(rustflags.join("\x1f")) 65 | } 66 | 67 | pub fn dist_object(&self) -> PathBuf { 68 | self.cargo_build.artifact.dist_object().into() 69 | } 70 | 71 | pub fn target(&self) -> Target { 72 | self.cargo_build.artifact.target 73 | } 74 | 75 | pub fn ci_image(&self, image: &str) -> PathBuf { 76 | self.cargo_build.artifact.ci_image(image) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/arch/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(feature = "fc")] { 3 | mod firecracker; 4 | pub use self::firecracker::*; 5 | } else if #[cfg(target_os = "none")] { 6 | mod multiboot; 7 | pub use self::multiboot::*; 8 | } 9 | } 10 | 11 | mod console; 12 | #[cfg(target_os = "none")] 13 | mod paging; 14 | #[cfg(target_os = "none")] 15 | mod physicalmem; 16 | 17 | pub use console::Console; 18 | 19 | #[cfg(target_os = "none")] 20 | const KERNEL_STACK_SIZE: u64 = 32_768; 21 | pub const SERIAL_IO_PORT: u16 = 0x3F8; 22 | 23 | #[cfg(target_os = "none")] 24 | unsafe fn map_memory(address: usize, memory_size: usize) -> usize { 25 | use align_address::Align; 26 | use x86_64::structures::paging::{PageSize, PageTableFlags, Size2MiB}; 27 | 28 | let address = address.align_up(Size2MiB::SIZE as usize); 29 | let page_count = memory_size.align_up(Size2MiB::SIZE as usize) / Size2MiB::SIZE as usize; 30 | 31 | paging::map::(address, address, page_count, PageTableFlags::WRITABLE); 32 | 33 | address 34 | } 35 | 36 | #[cfg(target_os = "none")] 37 | pub unsafe fn get_memory(memory_size: u64) -> u64 { 38 | use align_address::Align; 39 | use x86_64::structures::paging::{PageSize, Size2MiB}; 40 | 41 | use self::physicalmem::PhysAlloc; 42 | 43 | let address = PhysAlloc::allocate((memory_size as usize).align_up(Size2MiB::SIZE as usize)); 44 | unsafe { map_memory(address, memory_size as usize) as u64 } 45 | } 46 | 47 | pub unsafe fn enter_kernel( 48 | stack: *mut u8, 49 | entry: *const (), 50 | raw_boot_info: &'static hermit_entry::boot_info::RawBootInfo, 51 | ) -> ! { 52 | use core::arch::asm; 53 | 54 | use hermit_entry::Entry; 55 | use hermit_entry::boot_info::RawBootInfo; 56 | use log::info; 57 | 58 | // Check expected signature of entry function 59 | let entry: Entry = { 60 | let entry: unsafe extern "C" fn(raw_boot_info: &'static RawBootInfo, cpu_id: u32) -> ! = 61 | unsafe { core::mem::transmute(entry) }; 62 | entry 63 | }; 64 | 65 | info!("Entering kernel at {entry:p}, stack at {stack:p}, raw_boot_info at {raw_boot_info:p}"); 66 | 67 | unsafe { 68 | asm!( 69 | "mov rsp, {stack_address}", 70 | "jmp {entry}", 71 | stack_address = in(reg) stack, 72 | entry = in(reg) entry, 73 | in("rdi") raw_boot_info, 74 | in("rsi") 0, 75 | options(noreturn) 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/arch/aarch64/drivers/xlnx_serial.rs: -------------------------------------------------------------------------------- 1 | use core::hint; 2 | use core::num::NonZeroU32; 3 | use core::ptr::NonNull; 4 | 5 | use aarch64_cpu::asm::barrier; 6 | use aarch64_cpu::asm::barrier::SY; 7 | use volatile::{VolatileFieldAccess, VolatileRef}; 8 | 9 | use crate::arch::drivers::SerialSuccess::{ERetry, Success}; 10 | use crate::arch::drivers::{SerialDriver, SerialSuccess}; 11 | 12 | const ZYNQ_UART_SR_TXACTIVE: u32 = 1 << 11; 13 | const ZYNQ_UART_SR_TXFULL: u32 = 1 << 4; 14 | const ZYNQ_UART_SR_TXEMPTY: u32 = 1 << 3; 15 | 16 | const ZYNQ_UART_CR_TX_EN: u32 = 1 << 4; 17 | const ZYNQ_UART_CR_RX_EN: u32 = 1 << 2; 18 | const ZYNQ_UART_CR_TXRST: u32 = 1 << 1; 19 | const ZYNQ_UART_CR_RXRST: u32 = 1 << 0; 20 | 21 | const ZYNQ_UART_MR_PARITY_NONE: u32 = 0x00000020; 22 | 23 | #[repr(C)] 24 | #[derive(VolatileFieldAccess)] 25 | pub struct XlnxRegisters { 26 | control: u32, 27 | mode: u32, 28 | reserved1: [u32; 4], 29 | baud_rate_gen: u32, 30 | reserved2: [u32; 4], 31 | channel_sts: u32, 32 | tx_rx_fifo: u32, 33 | baud_rate_divider: u32, 34 | } 35 | 36 | pub struct XlnxSerial { 37 | regs: VolatileRef<'static, XlnxRegisters>, 38 | } 39 | 40 | impl XlnxSerial { 41 | pub fn from_addr(base_addr: NonZeroU32) -> XlnxSerial { 42 | Self { 43 | regs: unsafe { 44 | VolatileRef::new(NonNull::new_unchecked(base_addr.get() as *mut XlnxRegisters)) 45 | }, 46 | } 47 | } 48 | } 49 | 50 | impl SerialDriver for XlnxSerial { 51 | fn init(&mut self) { 52 | self.regs.as_mut_ptr().control().write( 53 | ZYNQ_UART_CR_TX_EN | ZYNQ_UART_CR_RX_EN | ZYNQ_UART_CR_TXRST | ZYNQ_UART_CR_RXRST, 54 | ); 55 | self.regs 56 | .as_mut_ptr() 57 | .mode() 58 | .write(ZYNQ_UART_MR_PARITY_NONE); 59 | } 60 | 61 | fn putc(&mut self, c: u8) -> SerialSuccess { 62 | barrier::dmb(SY); 63 | barrier::dsb(SY); 64 | if self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXFULL != 0 { 65 | return ERetry; 66 | } 67 | 68 | self.regs.as_mut_ptr().tx_rx_fifo().write(c as u32); 69 | Success(c) 70 | } 71 | 72 | fn putstr(&mut self, s: &[u8]) { 73 | 'foo: for c in s.iter().copied() { 74 | if c == b'\n' { 75 | for _ in 0..1000 { 76 | match self.putc(b'\r') { 77 | ERetry => continue, 78 | Success(_) => break, 79 | } 80 | } 81 | } 82 | for _ in 0..1000 { 83 | hint::spin_loop(); 84 | match self.putc(c) { 85 | ERetry => continue, 86 | Success(_) => continue 'foo, 87 | } 88 | } 89 | self.init(); 90 | while self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXEMPTY != 0 { 91 | hint::spin_loop(); 92 | } 93 | } 94 | } 95 | 96 | fn get_addr(&self) -> u32 { 97 | self.regs.as_ptr().as_raw_ptr().as_ptr() as u32 98 | } 99 | 100 | fn wait_empty(&mut self) { 101 | while self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXACTIVE != 0 { 102 | hint::spin_loop(); 103 | } 104 | while self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXEMPTY != 0 { 105 | hint::spin_loop(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/os/none/allocator/bootstrap.rs: -------------------------------------------------------------------------------- 1 | //! A bootstrap allocator based on a statically allocated buffer. 2 | 3 | /// A pointer range that can only be compared against. 4 | mod ptr_range { 5 | use core::ops::Range; 6 | use core::ptr::NonNull; 7 | 8 | /// A pointer range that can only be compared against. 9 | pub struct PtrRange { 10 | inner: Range>, 11 | } 12 | 13 | // SAFETY: We never dereference, but only compare, pointers. 14 | unsafe impl Send for PtrRange {} 15 | unsafe impl Sync for PtrRange {} 16 | 17 | impl PtrRange { 18 | /// Returns `true` if the pointer range contains `ptr`. 19 | pub fn contains(&self, ptr: NonNull) -> bool { 20 | self.inner.contains(&ptr) 21 | } 22 | } 23 | 24 | impl From>> for PtrRange { 25 | fn from(value: Range>) -> Self { 26 | Self { inner: value } 27 | } 28 | } 29 | } 30 | 31 | use core::mem::MaybeUninit; 32 | use core::ops::Range; 33 | use core::ptr::NonNull; 34 | 35 | use allocator_api2::alloc::{AllocError, Allocator, Layout}; 36 | use take_static::take_static; 37 | 38 | use self::ptr_range::PtrRange; 39 | 40 | /// A bootstrap allocator. 41 | /// 42 | /// This allocator is generic over the internal allocator and can only be created once. 43 | /// The bootstrap allocator provides the internal allocator with static memory. 44 | /// 45 | /// This allocator tracks, which static memory it was using initially. 46 | /// It can be queried whether a pointer belongs to it. 47 | pub struct BootstrapAllocator { 48 | ptr_range: PtrRange, 49 | allocator: A, 50 | } 51 | 52 | impl Default for BootstrapAllocator 53 | where 54 | A: From<&'static mut [MaybeUninit]>, 55 | { 56 | fn default() -> Self { 57 | let mem = { 58 | const SIZE: usize = 4 * 1024; 59 | const BYTE: MaybeUninit = MaybeUninit::uninit(); 60 | take_static! { 61 | /// The actual memory of the boostrap allocator. 62 | static MEM: [MaybeUninit; SIZE] = [BYTE; SIZE]; 63 | } 64 | MEM.take().unwrap() 65 | }; 66 | 67 | let ptr_range = { 68 | let Range { start, end } = mem.as_mut_ptr_range(); 69 | let start = NonNull::new(start).unwrap().cast::(); 70 | let end = NonNull::new(end).unwrap().cast::(); 71 | PtrRange::from(start..end) 72 | }; 73 | let allocator = A::from(mem); 74 | 75 | Self { 76 | ptr_range, 77 | allocator, 78 | } 79 | } 80 | } 81 | 82 | impl BootstrapAllocator { 83 | /// Returns `true` if the pointer belonged to the static memory of this allocator. 84 | pub fn manages(&self, ptr: NonNull) -> bool { 85 | self.ptr_range.contains(ptr) 86 | } 87 | } 88 | 89 | unsafe impl Allocator for BootstrapAllocator 90 | where 91 | A: Allocator, 92 | { 93 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 94 | self.allocator.allocate(layout) 95 | } 96 | 97 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 98 | debug_assert!(self.manages(ptr)); 99 | unsafe { self.allocator.deallocate(ptr, layout) } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/os/none/allocator/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the Hermit Allocator in the loader 2 | 3 | mod bootstrap; 4 | 5 | use core::ptr; 6 | use core::ptr::NonNull; 7 | 8 | use allocator_api2::alloc::{AllocError, Allocator, GlobalAlloc, Layout}; 9 | use one_shot_mutex::sync::OneShotMutex; 10 | 11 | use self::bootstrap::BootstrapAllocator; 12 | use crate::bump_allocator::BumpAllocator; 13 | 14 | /// The global system allocator for Hermit. 15 | struct GlobalAllocator { 16 | /// The bootstrap allocator, which is available immediately. 17 | /// 18 | /// It allows allocations before the heap has been initalized. 19 | bootstrap_allocator: Option>, 20 | } 21 | 22 | impl GlobalAllocator { 23 | const fn empty() -> Self { 24 | Self { 25 | bootstrap_allocator: None, 26 | } 27 | } 28 | 29 | fn align_layout(layout: Layout) -> Layout { 30 | let size = layout.size(); 31 | let align = layout.align(); 32 | Layout::from_size_align(size, align).unwrap() 33 | } 34 | 35 | fn allocate(&mut self, layout: Layout) -> Result, AllocError> { 36 | let layout = Self::align_layout(layout); 37 | self.bootstrap_allocator 38 | .get_or_insert_with(Default::default) 39 | .allocate(layout) 40 | // FIXME: Use NonNull::as_mut_ptr once `slice_ptr_get` is stabilized 41 | // https://github.com/rust-lang/rust/issues/74265 42 | .map(|ptr| NonNull::new(ptr.as_ptr() as *mut u8).unwrap()) 43 | } 44 | 45 | unsafe fn deallocate(&mut self, ptr: NonNull, layout: Layout) { 46 | let layout = Self::align_layout(layout); 47 | let bootstrap_allocator = self.bootstrap_allocator.as_ref().unwrap(); 48 | assert!(bootstrap_allocator.manages(ptr)); 49 | unsafe { 50 | bootstrap_allocator.deallocate(ptr, layout); 51 | } 52 | } 53 | } 54 | 55 | pub struct LockedAllocator(OneShotMutex); 56 | 57 | impl LockedAllocator { 58 | /// Creates an empty allocator. All allocate calls will return `None`. 59 | pub const fn empty() -> LockedAllocator { 60 | LockedAllocator(OneShotMutex::new(GlobalAllocator::empty())) 61 | } 62 | } 63 | 64 | /// To avoid false sharing, the global memory allocator align 65 | /// all requests to a cache line. 66 | unsafe impl GlobalAlloc for LockedAllocator { 67 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 68 | self.0 69 | .lock() 70 | .allocate(layout) 71 | .ok() 72 | .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) 73 | } 74 | 75 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 76 | unsafe { 77 | self.0 78 | .lock() 79 | .deallocate(NonNull::new_unchecked(ptr), layout) 80 | } 81 | } 82 | } 83 | 84 | #[global_allocator] 85 | static ALLOCATOR: LockedAllocator = LockedAllocator::empty(); 86 | 87 | #[cfg(all(test, not(target_os = "none")))] 88 | mod tests { 89 | use core::mem; 90 | 91 | use super::*; 92 | 93 | #[test] 94 | fn empty() { 95 | let mut allocator = GlobalAllocator::empty(); 96 | let layout = Layout::from_size_align(1, 1).unwrap(); 97 | // we have 4 kbyte static memory 98 | assert!(allocator.allocate(layout.clone()).is_ok()); 99 | 100 | let layout = Layout::from_size_align(0x1000, mem::align_of::()); 101 | let addr = allocator.allocate(layout.unwrap()); 102 | assert!(addr.is_err()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/arch/x86_64/paging.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use log::warn; 4 | use x86_64::structures::paging::mapper::CleanUp; 5 | use x86_64::structures::paging::{ 6 | Mapper, Page, PageSize, PageTableFlags, PhysFrame, RecursivePageTable, 7 | }; 8 | 9 | use super::physicalmem::PhysAlloc; 10 | 11 | pub fn map(virtual_address: usize, physical_address: usize, count: usize, flags: PageTableFlags) 12 | where 13 | S: PageSize + Debug, 14 | RecursivePageTable<'static>: Mapper, 15 | { 16 | let pages = { 17 | let start = Page::::containing_address(x86_64::VirtAddr::new(virtual_address as u64)); 18 | let end = start + count as u64; 19 | Page::range(start, end) 20 | }; 21 | 22 | let frames = { 23 | let start = 24 | PhysFrame::::containing_address(x86_64::PhysAddr::new(physical_address as u64)); 25 | let end = start + count as u64; 26 | PhysFrame::range(start, end) 27 | }; 28 | 29 | warn!( 30 | "Mapping {count} {size} pages from {from_start:p}..{from_end:p} to {to_start:p}..{to_end:p}", 31 | count = (pages.end.start_address() - pages.start.start_address()) / S::SIZE, 32 | size = S::DEBUG_STR, 33 | from_start = pages.start.start_address(), 34 | from_end = pages.end.start_address(), 35 | to_start = frames.start.start_address(), 36 | to_end = frames.end.start_address(), 37 | ); 38 | 39 | let flags = flags | PageTableFlags::PRESENT; 40 | let mut table = unsafe { recursive_page_table() }; 41 | 42 | for (page, frame) in pages.zip(frames) { 43 | unsafe { 44 | table 45 | .map_to(page, frame, flags, &mut PhysAlloc) 46 | .unwrap() 47 | .flush(); 48 | } 49 | } 50 | } 51 | 52 | #[cfg(all(target_arch = "x86_64", not(feature = "fc")))] 53 | pub fn map_range( 54 | virtual_start: usize, 55 | phys_start: usize, 56 | phys_end: usize, 57 | mut flags: PageTableFlags, 58 | ) where 59 | S: PageSize + Debug, 60 | RecursivePageTable<'static>: Mapper, 61 | { 62 | let first_page = Page::::containing_address(x86_64::VirtAddr::new(virtual_start as u64)); 63 | let first_frame = PhysFrame::containing_address(x86_64::PhysAddr::new(phys_start as u64)); 64 | let last_frame = PhysFrame::containing_address(x86_64::PhysAddr::new(phys_end as u64)); 65 | warn!( 66 | "Mapping {size} pages starting from {from_start:p} to frames {to_start:p}..{to_end:p}", 67 | size = S::DEBUG_STR, 68 | from_start = first_page.start_address(), 69 | to_start = first_frame.start_address(), 70 | to_end = last_frame.start_address() 71 | ); 72 | flags |= PageTableFlags::PRESENT; 73 | let mut table = unsafe { recursive_page_table() }; 74 | let page_range = core::iter::successors(Some(first_page), |page| Some(*page + 1u64)); 75 | let frame_range = PhysFrame::::range(first_frame, last_frame); 76 | for (page, frame) in core::iter::zip(page_range, frame_range) { 77 | unsafe { 78 | table 79 | .map_to(page, frame, flags, &mut PhysAlloc) 80 | .unwrap() 81 | .flush(); 82 | } 83 | } 84 | } 85 | 86 | pub fn clean_up() { 87 | let mut table = unsafe { recursive_page_table() }; 88 | 89 | unsafe { table.clean_up(&mut PhysAlloc) } 90 | } 91 | 92 | unsafe fn recursive_page_table() -> RecursivePageTable<'static> { 93 | let level_4_table_addr = 0xFFFF_FFFF_FFFF_F000_usize; 94 | let level_4_table_ptr = sptr::from_exposed_addr_mut(level_4_table_addr); 95 | unsafe { 96 | let level_4_table = &mut *(level_4_table_ptr); 97 | RecursivePageTable::new(level_4_table).unwrap() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/arch/aarch64/entry.s: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/blob/master/02_runtime_init/src/_arch/aarch64/cpu/boot.s 2 | 3 | .equ _core_id_mask, 0xff 4 | 5 | .section .text 6 | 7 | _linux: // Let's fake being linux! https://www.kernel.org/doc/Documentation/arm64/booting.txt 8 | nop // code0: Do nothing here (no uefi) 9 | b _linux_start // code1: Branch to real stuff.... 10 | .quad 0 // text_offset: linux needs none, neither do we 11 | .quad prog_size 12 | .quad 2 // flags: 4k pagesize. might need adjustment. Page size currently undefined. 13 | .quad 0 // res2: reserved 14 | .quad 0 // res3: reserved 15 | .quad 0 // res4: reserved 16 | .long 0x644d5241 // magic: "ARMx64" 17 | .long 0 // res5: header size for efi boot. Not needed. 18 | .align 8 19 | _linux_start: 20 | adr x8, dtb_addr 21 | str x0, [x8, 0] 22 | _start: 23 | // Only proceed on the boot core. Park it otherwise. 24 | mrs x1, mpidr_el1 25 | and x1, x1, _core_id_mask 26 | mov x2, xzr // Assume CPU 0 is responsible for booting 27 | cmp x1, x2 28 | b.ne 1f 29 | 30 | // If execution reaches here, it is the boot core. Now, prepare the jump to Rust code. 31 | 32 | // This loads the physical address of the stack end. For details see 33 | // https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/blob/master/16_virtual_mem_part4_higher_half_kernel/src/bsp/raspberrypi/link.ld 34 | adrp x4, __boot_core_stack_end_exclusive 35 | add x4, x4, #:lo12:__boot_core_stack_end_exclusive 36 | mov sp, x4 37 | 38 | // Set correct Exception level! 39 | 40 | mrs x0, CurrentEL 41 | cmp x0, #4 // EL = 1 42 | b.eq el_1_entry 43 | 44 | // Test if EL2 45 | cmp x0, #8 46 | b.eq el_2_entry 47 | 48 | //EL3 49 | msr SP_EL2, x4 50 | msr SP_EL1, x4 51 | msr SCTLR_EL2, xzr 52 | msr HCR_EL2, xzr 53 | 54 | mrs x0, SCR_EL3 55 | and x0, x0, #(~(1 << 3)) 56 | and x0, x0, #(~(1 << 2)) 57 | and x0, x0, #(~(1 << 1)) 58 | orr x0, x0, #(1<<10) 59 | orr x0, x0, #(1<<0) 60 | msr SCR_EL3, x0 61 | 62 | mov x0, #0b1111001001 // D-Flag, I-FLAG, A-FLAG, F-FLAG, EL2h 63 | msr SPSR_EL3, x0 64 | 65 | adr x0, el_2_entry 66 | msr ELR_EL3, x0 67 | 68 | eret 69 | 70 | // EL2 71 | el_2_entry: 72 | msr SP_EL1, x4 73 | msr SCTLR_EL1, xzr 74 | mrs x0, HCR_EL2 75 | orr x0, x0, #(1<<31) 76 | msr HCR_EL2, x0 77 | 78 | mov x0, #0b1111000101 // D-Flag, I-FLAG, A-FLAG, F-FLAG, EL1h 79 | msr SPSR_EL2, x0 80 | 81 | adr x0, el_1_entry 82 | msr ELR_EL2, x0 83 | 84 | eret 85 | 86 | // Jump to Rust code. 87 | el_1_entry: 88 | b {start_rust} 89 | 90 | // Infinitely wait for events (aka "park the core"). 91 | 1: wfe 92 | b 1b 93 | 94 | .size _start, . - _start 95 | .type _start, function 96 | .global _start 97 | 98 | .section .bss 99 | 100 | .global l0_pgtable 101 | .global l1_pgtable 102 | .global l2_pgtable 103 | .global l2k_pgtable 104 | .global l3_pgtable 105 | .global L0mib_pgtable 106 | .global dtb_addr 107 | 108 | .align 12 109 | l0_pgtable: 110 | .space 512*8, 0 111 | l1_pgtable: 112 | .space 512*8, 0 113 | l2_pgtable: 114 | .space 512*8, 0 115 | l2k_pgtable: 116 | .space 512*8, 0 117 | l3_pgtable: 118 | .space 512*8, 0 119 | L0mib_pgtable: 120 | .space 512*8, 0 121 | L2mib_pgtable: 122 | .space 512*8, 0 123 | L4mib_pgtable: 124 | .space 512*8, 0 125 | L6mib_pgtable: 126 | .space 512*8, 0 127 | L8mib_pgtable: 128 | .space 512*8, 0 129 | L10mib_pgtable: 130 | .space 512*8, 0 131 | L12mib_pgtable: 132 | .space 512*8, 0 133 | L14mib_pgtable: 134 | .space 512*8, 0 135 | L16mib_pgtable: 136 | .space 512*8, 0 137 | L18mib_pgtable: 138 | .space 512*8, 0 139 | dtb_addr: 140 | .space 8, 0 -------------------------------------------------------------------------------- /xtask/src/target.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::anyhow; 4 | use xshell::{Shell, cmd}; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 7 | pub enum Target { 8 | X86_64, 9 | X86_64Fc, 10 | X86_64Uefi, 11 | Aarch64, 12 | Aarch64Be, 13 | Riscv64, 14 | } 15 | 16 | impl Target { 17 | pub fn install(&self) -> xshell::Result<()> { 18 | let sh = Shell::new()?; 19 | 20 | if self.tier() <= 2 { 21 | let triple = self.triple(); 22 | cmd!(sh, "rustup target add {triple}").run()?; 23 | } 24 | 25 | if self == &Self::X86_64 { 26 | cmd!(sh, "rustup component add llvm-tools-preview").run()?; 27 | } 28 | 29 | Ok(()) 30 | } 31 | 32 | pub fn arch(&self) -> &'static str { 33 | match self { 34 | Self::X86_64 => "x86_64", 35 | Self::X86_64Fc => "x86_64", 36 | Self::X86_64Uefi => "x86_64", 37 | Self::Aarch64 => "aarch64", 38 | Self::Aarch64Be => "aarch64_be", 39 | Self::Riscv64 => "riscv64", 40 | } 41 | } 42 | 43 | pub fn triple(&self) -> &'static str { 44 | match self { 45 | Self::X86_64 => "x86_64-unknown-none", 46 | Self::X86_64Fc => "x86_64-unknown-none", 47 | Self::X86_64Uefi => "x86_64-unknown-uefi", 48 | Self::Aarch64 => "aarch64-unknown-none-softfloat", 49 | Self::Aarch64Be => "aarch64_be-unknown-none-softfloat", 50 | Self::Riscv64 => "riscv64imac-unknown-none-elf", 51 | } 52 | } 53 | 54 | pub fn tier(&self) -> u8 { 55 | match self { 56 | Self::Aarch64Be => 3, 57 | _ => 2, 58 | } 59 | } 60 | 61 | pub fn cargo_args(&self) -> &'static [&'static str] { 62 | match self { 63 | Self::X86_64 => &["--target=x86_64-unknown-none"], 64 | Self::X86_64Fc => &["--target=x86_64-unknown-none"], 65 | Self::X86_64Uefi => &["--target=x86_64-unknown-uefi"], 66 | Self::Aarch64 => &["--target=aarch64-unknown-none-softfloat"], 67 | Self::Aarch64Be => &[ 68 | "--target=aarch64_be-unknown-none-softfloat", 69 | "-Zbuild-std=core,alloc,panic_abort", 70 | ], 71 | Self::Riscv64 => &["--target=riscv64imac-unknown-none-elf"], 72 | } 73 | } 74 | 75 | pub fn rustflags(&self) -> &'static [&'static str] { 76 | match self { 77 | Self::X86_64 => &[ 78 | "-Clink-arg=-Tsrc/arch/x86_64/link.ld", 79 | "-Crelocation-model=static", 80 | ], 81 | Self::X86_64Fc => &[ 82 | "-Clink-arg=-Tsrc/arch/x86_64/link_fc.ld", 83 | "-Crelocation-model=static", 84 | ], 85 | Self::X86_64Uefi => &[], 86 | Self::Aarch64 | Self::Aarch64Be => &["-Clink-arg=-Tsrc/arch/aarch64/link.ld"], 87 | Self::Riscv64 => &["-Clink-arg=-Tsrc/arch/riscv64/link.ld"], 88 | } 89 | } 90 | 91 | pub fn feature_flags(&self) -> &'static [&'static str] { 92 | match self { 93 | Self::X86_64Fc => &["--features=fc"], 94 | _ => &[], 95 | } 96 | } 97 | 98 | pub fn image_name(&self) -> &'static str { 99 | match self { 100 | Self::X86_64Uefi => "hermit-loader.efi", 101 | _ => "hermit-loader", 102 | } 103 | } 104 | 105 | pub fn dist_name(&self) -> &'static str { 106 | match self { 107 | Self::X86_64 => "hermit-loader-x86_64", 108 | Self::X86_64Fc => "hermit-loader-x86_64-fc", 109 | Self::X86_64Uefi => "hermit-loader-x86_64.efi", 110 | Self::Aarch64 => "hermit-loader-aarch64", 111 | Self::Aarch64Be => "hermit-loader-aarch64_be", 112 | Self::Riscv64 => "hermit-loader-riscv64", 113 | } 114 | } 115 | 116 | pub fn qemu(&self) -> &'static str { 117 | match self { 118 | Self::X86_64 | Self::X86_64Fc | Self::X86_64Uefi => "x86_64", 119 | Self::Aarch64 | Self::Aarch64Be => "aarch64", 120 | Self::Riscv64 => "riscv64", 121 | } 122 | } 123 | } 124 | 125 | impl FromStr for Target { 126 | type Err = anyhow::Error; 127 | 128 | fn from_str(s: &str) -> Result { 129 | match s { 130 | "x86_64" => Ok(Self::X86_64), 131 | "x86_64-fc" => Ok(Self::X86_64Fc), 132 | "x86_64-uefi" => Ok(Self::X86_64Uefi), 133 | "aarch64" => Ok(Self::Aarch64), 134 | "aarch64_be" => Ok(Self::Aarch64Be), 135 | "riscv64" => Ok(Self::Riscv64), 136 | s => Err(anyhow!("Unsupported target: {s}")), 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/arch/x86_64/entry_fc.s: -------------------------------------------------------------------------------- 1 | # This is the kernel's entry point, if Hermit is running with 2 | # FireCracker. FireCracker assumes a 64 bit Linux kernel. 3 | 4 | .code64 5 | 6 | .set BOOT_STACK_SIZE, 4096 7 | 8 | .extern loader_start # defined in linker script 9 | .extern loader_end 10 | 11 | # Move entry point at the beginning of the elf file 12 | .section .mboot, "a" 13 | .align 8 14 | .global _start 15 | _start: 16 | cli # avoid any interrupt 17 | 18 | # Initialize stack pointer 19 | movabs rsp, OFFSET boot_stack 20 | add rsp, BOOT_STACK_SIZE - 16 21 | 22 | mov [boot_params], rsi 23 | 24 | # initialize page tables 25 | # map kernel 1:1 26 | push rdi 27 | push rbx 28 | push rcx 29 | movabs rcx, OFFSET loader_start 30 | movabs rbx, OFFSET loader_end 31 | add rbx, 0x1000 32 | L0: cmp rcx, rbx 33 | jae L1 34 | mov rax, rcx 35 | and eax, 0xFFFFF000 # page align lower half 36 | mov rdi, rax 37 | shr rdi, 9 # (edi >> 12) * 8 (index for boot_pgt) 38 | add rdi, OFFSET boot_pgt1 39 | or rax, 0x3 # set present and writable bits 40 | mov [rdi], rax 41 | add rcx, 0x1000 42 | jmp L0 43 | L1: 44 | pop rcx 45 | pop rbx 46 | pop rdi 47 | 48 | # Set CR3 49 | mov rax, OFFSET boot_pml4 50 | mov cr3, rax 51 | 52 | lgdt [GDT64.Pointer] # Load the 64-bit global descriptor table. 53 | jmp start64 # Set the code segment and enter 64-bit long mode. 54 | 55 | .section .text 56 | .align 8 57 | start64: 58 | # initialize segment registers 59 | mov ax, OFFSET GDT64.Data 60 | mov ds, ax 61 | mov es, ax 62 | mov ss, ax 63 | xor ax, ax 64 | mov fs, ax 65 | mov gs, ax 66 | cld 67 | # set default stack pointer 68 | movabs rsp, OFFSET boot_stack 69 | add rsp, BOOT_STACK_SIZE-16 70 | 71 | # jump to the boot processors's C code 72 | jmp {loader_main} 73 | invalid: 74 | jmp invalid 75 | 76 | .section .data 77 | .align 4 78 | # we need already a valid GDT to switch in the 64bit modus 79 | GDT64: # Global Descriptor Table (64-bit). 80 | .set GDT64.Null, . - GDT64 # The null descriptor. 81 | .2byte 0 # Limit (low). 82 | .2byte 0 # Base (low). 83 | .byte 0 # Base (middle) 84 | .byte 0 # Access. 85 | .byte 0 # Granularity. 86 | .byte 0 # Base (high). 87 | .set GDT64.Code, . - GDT64 # The code descriptor. 88 | .2byte 0 # Limit (low). 89 | .2byte 0 # Base (low). 90 | .byte 0 # Base (middle) 91 | .byte 0b10011010 # Access. 92 | .byte 0b00100000 # Granularity. 93 | .byte 0 # Base (high). 94 | .set GDT64.Data, . - GDT64 # The data descriptor. 95 | .2byte 0 # Limit (low). 96 | .2byte 0 # Base (low). 97 | .byte 0 # Base (middle) 98 | .byte 0b10010010 # Access. 99 | .byte 0b00000000 # Granularity. 100 | .byte 0 # Base (high). 101 | GDT64.Pointer: # The GDT-pointer. 102 | .2byte . - GDT64 - 1 # Limit. 103 | .8byte GDT64 # Base. 104 | 105 | .global boot_params 106 | .align 8 107 | boot_params: 108 | .8byte 0 109 | 110 | .align 4096 111 | .global boot_stack 112 | boot_stack: 113 | .fill BOOT_STACK_SIZE, 1, 0xcd 114 | 115 | # Bootstrap page tables are used during the initialization. 116 | .align 4096 117 | boot_pml4: 118 | .8byte boot_pdpt + 0x3 # PG_PRESENT | PG_RW 119 | .fill 510, 8, 0 # PAGE_MAP_ENTRIES - 2 120 | .8byte boot_pml4 + 0x3 # PG_PRESENT | PG_RW 121 | boot_pdpt: 122 | .8byte boot_pgd + 0x3 # PG_PRESENT | PG_RW 123 | .fill 511, 8, 0 # PAGE_MAP_ENTRIES - 1 124 | boot_pgd: 125 | .8byte boot_pgt1 + 0x3 # PG_PRESENT | PG_RW 126 | .8byte boot_pgt2 + 0x3 # PG_PRESENT | PG_RW 127 | .fill 510, 8, 0 # PAGE_MAP_ENTRIES - 1 128 | boot_pgt1: 129 | .fill 512, 8, 0 130 | boot_pgt2: 131 | .fill 512, 8, 0 132 | -------------------------------------------------------------------------------- /src/os/uefi/mod.rs: -------------------------------------------------------------------------------- 1 | mod allocator; 2 | mod console; 3 | 4 | use alloc::string::String; 5 | use alloc::vec::Vec; 6 | use core::ffi::c_void; 7 | use core::mem::MaybeUninit; 8 | use core::slice; 9 | 10 | use align_address::Align; 11 | use hermit_entry::boot_info::{ 12 | BootInfo, DeviceTreeAddress, HardwareInfo, PlatformInfo, SerialPortBase, 13 | }; 14 | use hermit_entry::elf::{KernelObject, LoadedKernel}; 15 | use log::info; 16 | use sptr::Strict; 17 | use uefi::boot::{AllocateType, MemoryType, PAGE_SIZE}; 18 | use uefi::fs::{self, FileSystem, Path}; 19 | use uefi::prelude::*; 20 | use uefi::table::cfg::ConfigTableEntry; 21 | 22 | pub use self::console::CONSOLE; 23 | use crate::fdt::Fdt; 24 | use crate::{BootInfoExt, arch}; 25 | 26 | // Entry Point of the Uefi Loader 27 | #[entry] 28 | fn main() -> Status { 29 | uefi::helpers::init().unwrap(); 30 | crate::log::init(); 31 | 32 | let kernel_image = read_app(); 33 | let kernel = KernelObject::parse(&kernel_image).unwrap(); 34 | 35 | let kernel_memory = alloc_page_slice(kernel.mem_size()).unwrap(); 36 | let kernel_memory = &mut kernel_memory[..kernel.mem_size()]; 37 | 38 | let kernel_info = kernel.load_kernel(kernel_memory, kernel_memory.as_ptr() as u64); 39 | 40 | let rsdp = rsdp(); 41 | 42 | drop(kernel_image); 43 | 44 | let mut fdt = Fdt::new("uefi") 45 | .unwrap() 46 | .rsdp(u64::try_from(rsdp.expose_addr()).unwrap()) 47 | .unwrap(); 48 | 49 | if let Some(bootargs) = read_bootargs() { 50 | fdt = fdt.bootargs(bootargs).unwrap(); 51 | } 52 | 53 | allocator::exit_boot_services(); 54 | let mut memory_map = unsafe { boot::exit_boot_services(None) }; 55 | 56 | let fdt = fdt.memory_map(&mut memory_map).unwrap().finish().unwrap(); 57 | 58 | unsafe { boot_kernel(kernel_info, fdt) } 59 | } 60 | 61 | fn read_app() -> Vec { 62 | let image_handle = boot::image_handle(); 63 | let fs = boot::get_image_file_system(image_handle).expect("should open file system"); 64 | 65 | let path = Path::new(cstr16!(r"\efi\boot\hermit-app")); 66 | 67 | let data = FileSystem::new(fs) 68 | .read(path) 69 | .expect("should read file content"); 70 | 71 | let len = data.len(); 72 | info!("Read Hermit application from \"{path}\" (size = {len} B)"); 73 | 74 | data 75 | } 76 | 77 | fn read_bootargs() -> Option { 78 | let image_handle = boot::image_handle(); 79 | let fs = boot::get_image_file_system(image_handle).expect("should open file system"); 80 | 81 | let path = Path::new(cstr16!(r"\efi\boot\hermit-bootargs")); 82 | 83 | match FileSystem::new(fs).read_to_string(path) { 84 | Ok(bootargs) => { 85 | info!("Read Hermit bootargs from from \"{path}\": {bootargs}"); 86 | Some(bootargs) 87 | } 88 | Err(fs::Error::Io(err)) if err.uefi_error.status() == Status::NOT_FOUND => { 89 | info!("Hermit bootargs file does not exist: \"{path}\""); 90 | None 91 | } 92 | Err(err) => panic!("{err:?}"), 93 | } 94 | } 95 | 96 | pub unsafe fn boot_kernel(kernel_info: LoadedKernel, fdt: Vec) -> ! { 97 | let LoadedKernel { 98 | load_info, 99 | entry_point, 100 | } = kernel_info; 101 | 102 | let device_tree = 103 | DeviceTreeAddress::new(u64::try_from(fdt.leak().as_ptr().expose_addr()).unwrap()); 104 | 105 | let boot_info = BootInfo { 106 | hardware_info: HardwareInfo { 107 | phys_addr_range: 0..0, 108 | serial_port_base: SerialPortBase::new(arch::SERIAL_IO_PORT), 109 | device_tree, 110 | }, 111 | load_info, 112 | platform_info: PlatformInfo::Fdt, 113 | }; 114 | 115 | let stack = usize::try_from(boot_info.load_info.kernel_image_addr_range.end) 116 | .unwrap() 117 | .align_down(PAGE_SIZE); 118 | let entry = sptr::from_exposed_addr(entry_point.try_into().unwrap()); 119 | let stack = sptr::from_exposed_addr_mut(stack); 120 | let raw_boot_info = boot_info.write(); 121 | 122 | unsafe { arch::enter_kernel(stack, entry, raw_boot_info) } 123 | } 124 | 125 | fn alloc_page_slice(size: usize) -> uefi::Result<&'static mut [MaybeUninit]> { 126 | let size = size.align_up(PAGE_SIZE); 127 | let ptr = boot::allocate_pages( 128 | AllocateType::AnyPages, 129 | MemoryType::LOADER_DATA, 130 | size / PAGE_SIZE, 131 | )?; 132 | Ok(unsafe { slice::from_raw_parts_mut(ptr.cast().as_ptr(), size) }) 133 | } 134 | 135 | /// Returns the RSDP. 136 | /// 137 | /// This must be called before exiting boot services. 138 | /// See [5.2.5.2. Finding the RSDP on UEFI Enabled Systems — ACPI Specification 6.5 documentation](https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#finding-the-rsdp-on-uefi-enabled-systems) for details. 139 | fn rsdp() -> *const c_void { 140 | system::with_config_table(|config_table| { 141 | let (rsdp, version) = if let Some(entry) = config_table 142 | .iter() 143 | .find(|entry| entry.guid == ConfigTableEntry::ACPI2_GUID) 144 | { 145 | (entry.address, 2) 146 | } else { 147 | let entry = config_table 148 | .iter() 149 | .find(|entry| entry.guid == ConfigTableEntry::ACPI_GUID) 150 | .unwrap(); 151 | (entry.address, 1) 152 | }; 153 | info!("Found ACPI {version} RSDP at {rsdp:p}"); 154 | rsdp 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /src/fdt.rs: -------------------------------------------------------------------------------- 1 | use alloc::format; 2 | use alloc::string::String; 3 | use alloc::vec::Vec; 4 | use core::ops::Range; 5 | 6 | use vm_fdt::{FdtWriter, FdtWriterNode, FdtWriterResult}; 7 | 8 | pub struct Fdt { 9 | writer: FdtWriter, 10 | root_node: FdtWriterNode, 11 | bootargs: Option, 12 | } 13 | 14 | impl Fdt { 15 | pub fn new(platform: &str) -> FdtWriterResult { 16 | let mut writer = FdtWriter::new()?; 17 | 18 | let root_node = writer.begin_node("")?; 19 | writer.property_string("compatible", &format!("hermit,{platform}"))?; 20 | writer.property_u32("#address-cells", 0x2)?; 21 | writer.property_u32("#size-cells", 0x2)?; 22 | 23 | let bootargs = None; 24 | 25 | Ok(Self { 26 | writer, 27 | root_node, 28 | bootargs, 29 | }) 30 | } 31 | 32 | pub fn finish(mut self) -> FdtWriterResult> { 33 | let chosen_node = self.writer.begin_node("chosen")?; 34 | if let Some(bootargs) = &self.bootargs { 35 | self.writer.property_string("bootargs", bootargs)?; 36 | } 37 | self.writer.end_node(chosen_node)?; 38 | 39 | self.writer.end_node(self.root_node)?; 40 | 41 | self.writer.finish() 42 | } 43 | 44 | pub fn bootargs(mut self, bootargs: String) -> FdtWriterResult { 45 | assert!(self.bootargs.is_none()); 46 | self.bootargs = Some(bootargs); 47 | 48 | Ok(self) 49 | } 50 | 51 | #[cfg_attr(all(target_arch = "x86_64", not(target_os = "uefi")), expect(unused))] 52 | pub fn rsdp(mut self, rsdp: u64) -> FdtWriterResult { 53 | let rsdp_node = self.writer.begin_node(&format!("hermit,rsdp@{rsdp:x}"))?; 54 | self.writer.property_array_u64("reg", &[rsdp, 1])?; 55 | self.writer.end_node(rsdp_node)?; 56 | 57 | Ok(self) 58 | } 59 | 60 | pub fn memory(mut self, memory: Range) -> FdtWriterResult { 61 | let memory_node = self 62 | .writer 63 | .begin_node(format!("memory@{:x}", memory.start).as_str())?; 64 | self.writer.property_string("device_type", "memory")?; 65 | self.writer 66 | .property_array_u64("reg", &[memory.start, memory.end - memory.start])?; 67 | self.writer.end_node(memory_node)?; 68 | 69 | Ok(self) 70 | } 71 | } 72 | 73 | #[cfg(all(target_arch = "x86_64", not(target_os = "uefi"), not(feature = "fc")))] 74 | mod x86_64 { 75 | use multiboot::information::{MemoryMapIter, MemoryType}; 76 | use vm_fdt::FdtWriterResult; 77 | 78 | impl super::Fdt { 79 | pub fn memory_regions( 80 | mut self, 81 | memory_regions: MemoryMapIter<'_, '_>, 82 | ) -> FdtWriterResult { 83 | let memory_regions = 84 | memory_regions.filter(|m| m.memory_type() == MemoryType::Available); 85 | 86 | for memory_region in memory_regions { 87 | self = self.memory( 88 | memory_region.base_address() 89 | ..memory_region.base_address() + memory_region.length(), 90 | )?; 91 | } 92 | 93 | Ok(self) 94 | } 95 | } 96 | } 97 | 98 | #[cfg(target_os = "uefi")] 99 | mod uefi { 100 | use core::fmt; 101 | use core::fmt::Write; 102 | 103 | use log::info; 104 | use uefi::boot::{MemoryDescriptor, MemoryType, PAGE_SIZE}; 105 | use uefi::mem::memory_map::{MemoryMap, MemoryMapMut}; 106 | use vm_fdt::FdtWriterResult; 107 | 108 | impl super::Fdt { 109 | pub fn memory_map(mut self, memory_map: &mut impl MemoryMapMut) -> FdtWriterResult { 110 | memory_map.sort(); 111 | info!("Memory map:\n{}", memory_map.display()); 112 | 113 | let entries = memory_map 114 | .entries() 115 | .filter(|entry| entry.ty == MemoryType::CONVENTIONAL); 116 | 117 | for entry in entries { 118 | self = self.memory( 119 | entry.phys_start..entry.phys_start + entry.page_count * PAGE_SIZE as u64, 120 | )?; 121 | } 122 | 123 | Ok(self) 124 | } 125 | } 126 | 127 | trait MemoryMapExt: MemoryMap { 128 | fn display(&self) -> MemoryMapDisplay<'_, Self> { 129 | MemoryMapDisplay { inner: self } 130 | } 131 | } 132 | 133 | impl MemoryMapExt for T where T: MemoryMap {} 134 | 135 | struct MemoryMapDisplay<'a, T: ?Sized> { 136 | inner: &'a T, 137 | } 138 | 139 | impl fmt::Display for MemoryMapDisplay<'_, T> 140 | where 141 | T: MemoryMap, 142 | { 143 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 144 | let mut has_fields = false; 145 | 146 | for desc in self.inner.entries() { 147 | if has_fields { 148 | f.write_char('\n')?; 149 | } 150 | write!(f, "{}", desc.display())?; 151 | 152 | has_fields = true; 153 | } 154 | Ok(()) 155 | } 156 | } 157 | 158 | trait MemoryDescriptorExt { 159 | fn display(&self) -> MemoryDescriptorDisplay<'_>; 160 | } 161 | 162 | impl MemoryDescriptorExt for MemoryDescriptor { 163 | fn display(&self) -> MemoryDescriptorDisplay<'_> { 164 | MemoryDescriptorDisplay { inner: self } 165 | } 166 | } 167 | 168 | struct MemoryDescriptorDisplay<'a> { 169 | inner: &'a MemoryDescriptor, 170 | } 171 | 172 | impl fmt::Display for MemoryDescriptorDisplay<'_> { 173 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 174 | write!( 175 | f, 176 | "start: {:#12x}, pages: {:#8x}, type: {:?}", 177 | self.inner.phys_start, self.inner.page_count, self.inner.ty 178 | ) 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/arch/riscv64/mod.rs: -------------------------------------------------------------------------------- 1 | mod console; 2 | pub use self::console::Console; 3 | mod address_range; 4 | mod start; 5 | 6 | use core::arch::asm; 7 | use core::{mem, slice}; 8 | 9 | use address_range::AddressRange; 10 | use fdt::node::FdtNode; 11 | use hermit_entry::Entry; 12 | use hermit_entry::boot_info::{ 13 | BootInfo, DeviceTreeAddress, HardwareInfo, PlatformInfo, RawBootInfo, 14 | }; 15 | use hermit_entry::elf::LoadedKernel; 16 | use log::info; 17 | use sptr::Strict; 18 | 19 | use crate::BootInfoExt; 20 | 21 | fn find_kernel_linux(chosen: &FdtNode<'_, '_>) -> Option<&'static [u8]> { 22 | let initrd_start = chosen.property("linux,initrd-start")?.as_usize()?; 23 | let initrd_start = sptr::from_exposed_addr_mut::(initrd_start); 24 | let initrd_end = chosen.property("linux,initrd-end")?.as_usize()?; 25 | let initrd_end = sptr::from_exposed_addr_mut::(initrd_end); 26 | // SAFETY: We trust the raw pointer from the firmware 27 | let initrd_len = unsafe { initrd_end.offset_from(initrd_start).try_into().unwrap() }; 28 | 29 | // SAFETY: We trust the raw pointer from the firmware 30 | Some(unsafe { slice::from_raw_parts(initrd_start, initrd_len) }) 31 | } 32 | 33 | fn find_kernel_multiboot(chosen: &FdtNode<'_, '_>) -> Option<&'static [u8]> { 34 | let module = chosen 35 | .children() 36 | .filter(|child| child.name.starts_with("module@")) 37 | .find(|child| { 38 | child.compatible().is_some_and(|compatible| { 39 | compatible 40 | .all() 41 | .any(|compatible| compatible == "multiboot,ramdisk") 42 | }) 43 | })?; 44 | let reg = module.property("reg").unwrap(); 45 | let addr = usize::from_be_bytes(reg.value[..mem::size_of::()].try_into().unwrap()); 46 | let len = usize::from_be_bytes(reg.value[mem::size_of::()..].try_into().unwrap()); 47 | 48 | let initrd_start = sptr::from_exposed_addr_mut::(addr); 49 | // SAFETY: We trust the raw pointer from the firmware 50 | Some(unsafe { slice::from_raw_parts(initrd_start, len) }) 51 | } 52 | 53 | pub fn find_kernel() -> &'static [u8] { 54 | let fdt = start::get_fdt(); 55 | let chosen = fdt.find_node("/chosen").unwrap(); 56 | find_kernel_linux(&chosen) 57 | .or_else(|| find_kernel_multiboot(&chosen)) 58 | .expect("could not find kernel") 59 | } 60 | 61 | pub unsafe fn get_memory(memory_size: u64) -> u64 { 62 | let memory_size = usize::try_from(memory_size).unwrap(); 63 | 64 | let initrd = AddressRange::try_from(find_kernel().as_ptr_range()).unwrap(); 65 | let fdt = { 66 | let start = start::get_fdt_ptr(); 67 | let end = unsafe { start.add(start::get_fdt().total_size()) }; 68 | AddressRange::try_from(start..end).unwrap() 69 | }; 70 | 71 | info!("initrd = {initrd}"); 72 | info!("fdt = {fdt}"); 73 | 74 | const SUPERPAGE_SIZE: usize = 2 * 1024 * 1024; 75 | let initrd = initrd.align_to(SUPERPAGE_SIZE); 76 | let fdt = fdt.align_to(SUPERPAGE_SIZE); 77 | 78 | let [first, second] = if initrd < fdt { 79 | [initrd, fdt] 80 | } else { 81 | [fdt, initrd] 82 | }; 83 | 84 | let start_address = if first.next(memory_size).overlaps(second) { 85 | second.end() 86 | } else { 87 | first.end() 88 | }; 89 | 90 | u64::try_from(start_address).unwrap() 91 | } 92 | 93 | pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { 94 | let LoadedKernel { 95 | load_info, 96 | entry_point, 97 | } = kernel_info; 98 | 99 | let fdt = start::get_fdt(); 100 | 101 | let phys_addr_range = { 102 | let memory = fdt.memory(); 103 | let mut regions = memory.regions(); 104 | 105 | let mem_region = regions.next().unwrap(); 106 | assert!( 107 | regions.next().is_none(), 108 | "hermit-loader can only handle one memory region yet" 109 | ); 110 | 111 | let mem_base = u64::try_from(mem_region.starting_address.addr()).unwrap(); 112 | let mem_size = u64::try_from(mem_region.size.unwrap()).unwrap(); 113 | mem_base..mem_base + mem_size 114 | }; 115 | 116 | let device_tree = { 117 | let fdt_addr = start::get_fdt_ptr().expose_addr(); 118 | DeviceTreeAddress::new(fdt_addr.try_into().unwrap()) 119 | }; 120 | 121 | let boot_info = BootInfo { 122 | hardware_info: HardwareInfo { 123 | phys_addr_range, 124 | serial_port_base: None, 125 | device_tree, 126 | }, 127 | load_info, 128 | platform_info: PlatformInfo::LinuxBoot, 129 | }; 130 | 131 | let stack = start::get_stack_ptr(); 132 | let entry = sptr::from_exposed_addr(entry_point.try_into().unwrap()); 133 | let hart_id = start::get_hart_id(); 134 | let raw_boot_info = boot_info.write(); 135 | 136 | unsafe { enter_kernel(stack, entry, hart_id, raw_boot_info) } 137 | } 138 | 139 | unsafe fn enter_kernel( 140 | stack: *mut u8, 141 | entry: *const (), 142 | hart_id: usize, 143 | raw_boot_info: &'static RawBootInfo, 144 | ) -> ! { 145 | // Check expected signature of entry function 146 | let entry: Entry = { 147 | let entry: unsafe extern "C" fn(hart_id: usize, boot_info: &'static RawBootInfo) -> ! = 148 | unsafe { core::mem::transmute(entry) }; 149 | entry 150 | }; 151 | 152 | info!("Entering kernel at {entry:p}, stack at {stack:p}, raw_boot_info at {raw_boot_info:p}"); 153 | 154 | unsafe { 155 | asm!( 156 | "mv sp, {stack}", 157 | "jr {entry}", 158 | entry = in(reg) entry, 159 | stack = in(reg) stack, 160 | in("a0") hart_id, 161 | in("a1") raw_boot_info, 162 | options(noreturn) 163 | ) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | merge_group: 6 | 7 | env: 8 | GH_TOKEN: ${{ github.token }} 9 | RUSTFLAGS: -Dwarnings 10 | RUSTDOCFLAGS: -Dwarnings 11 | 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | jobs: 17 | clippy: 18 | name: Clippy 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v6 22 | - name: Install Rust toolchain 23 | uses: dtolnay/rust-toolchain@stable 24 | with: 25 | components: clippy 26 | - name: Clippy 27 | run: cargo xtask clippy 28 | 29 | fmt: 30 | name: Format 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v6 34 | - name: Install Rust toolchain 35 | uses: dtolnay/rust-toolchain@nightly 36 | with: 37 | components: rustfmt 38 | - name: Check Formatting 39 | run: cargo fmt -- --check 40 | 41 | run: 42 | name: Run 43 | strategy: 44 | matrix: 45 | target: [x86_64, aarch64, aarch64_be, riscv64] 46 | os: [ubuntu-latest, macos-latest, windows-latest] 47 | include: 48 | - target: x86_64 49 | os: ubuntu-latest 50 | flags: --accel --sudo 51 | runs-on: ${{ matrix.os }} 52 | steps: 53 | - name: Install QEMU (ubuntu) 54 | if: matrix.os == 'ubuntu-latest' 55 | run: | 56 | sudo apt-get update 57 | sudo apt-get install qemu-system-x86 qemu-system-arm qemu-system-misc 58 | - name: Install QEMU (macos) 59 | if: matrix.os == 'macos-latest' 60 | run: | 61 | brew update 62 | brew install qemu 63 | - name: Install QEMU (windows) 64 | if: matrix.os == 'windows-latest' 65 | run: | 66 | choco install qemu 67 | echo "C:\Program Files\qemu" >> $GITHUB_PATH 68 | - name: Install firecracker 69 | run: | 70 | # https://github.com/firecracker-microvm/firecracker/blob/v1.5.1/docs/getting-started.md#getting-a-firecracker-binary 71 | ARCH="$(uname -m)" 72 | release_url="https://github.com/firecracker-microvm/firecracker/releases" 73 | latest=$(basename $(curl -fsSLI -o /dev/null -w %{url_effective} ${release_url}/latest)) 74 | curl -L ${release_url}/download/${latest}/firecracker-${latest}-${ARCH}.tgz \ 75 | | tar -xz 76 | 77 | mkdir -p $HOME/.local/bin 78 | mv release-${latest}-$(uname -m)/firecracker-${latest}-${ARCH} $HOME/.local/bin/firecracker 79 | echo $HOME/.local/bin >> $GITHUB_PATH 80 | 81 | $HOME/.local/bin/firecracker --version 82 | if: matrix.os == 'ubuntu-latest' && matrix.target == 'x86_64' 83 | - uses: actions/checkout@v6 84 | with: 85 | lfs: true 86 | - name: Install Rust toolchain 87 | uses: dtolnay/rust-toolchain@master 88 | with: 89 | toolchain: ${{ matrix.target == 'aarch64_be' && 'nightly' || 'stable' }} 90 | components: ${{ matrix.target == 'aarch64_be' && 'rust-src' || '' }} 91 | - name: Dowload OpenSBI 92 | if: matrix.target == 'riscv64' 93 | run: | 94 | gh release download v1.7 --repo riscv-software-src/opensbi --pattern 'opensbi-*-rv-bin.tar.xz' 95 | tar -xvf opensbi-*-rv-bin.tar.xz opensbi-1.7-rv-bin/share/opensbi/lp64/generic/firmware/fw_jump.bin 96 | - name: Run VM (hello_world, dev) 97 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }} 98 | - name: Run VM (hello_world, release) 99 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }} --release 100 | - name: Run VM (hello_world, uefi, dev) 101 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }}-uefi 102 | if: matrix.target == 'x86_64' 103 | - name: Run VM (hello_world, uefi, release) 104 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }}-uefi --release 105 | if: matrix.target == 'x86_64' 106 | - name: Run VM (hello_world-microvm, dev) 107 | if: matrix.target == 'x86_64' && matrix.os == 'ubuntu-latest' 108 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }} --microvm 109 | - name: Run VM (hello_world-microvm, release) 110 | if: matrix.target == 'x86_64' && matrix.os == 'ubuntu-latest' 111 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }} --microvm --release 112 | - name: Run VM (hello_c, dev) 113 | if: matrix.target == 'x86_64' 114 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }} --image hello_c 115 | - name: Run VM (hello_c, release) 116 | if: matrix.target == 'x86_64' 117 | run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.target }} --image hello_c --release 118 | - name: Run VM (hello_world-microvm, fc, release) 119 | run: FIRECRACKER=$HOME/.local/bin/firecracker cargo xtask ci firecracker --target ${{ matrix.target }}-fc --sudo 120 | if: matrix.os == 'ubuntu-latest' && matrix.target == 'x86_64' 121 | - name: Run VM (hello_world-microvm, fc, release) 122 | run: FIRECRACKER=$HOME/.local/bin/firecracker cargo xtask ci firecracker --target ${{ matrix.target }}-fc --sudo --release 123 | if: matrix.os == 'ubuntu-latest' && matrix.target == 'x86_64' 124 | -------------------------------------------------------------------------------- /src/arch/aarch64/entry.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::arch::{asm, global_asm}; 4 | 5 | use aarch64_cpu::registers::{SCTLR_EL1, Writeable}; 6 | use log::info; 7 | 8 | const BOOT_CORE_ID: u64 = 0; // ID of CPU for booting on SMP systems - this might be board specific in the future 9 | 10 | /* 11 | * Memory types available. 12 | */ 13 | #[allow(non_upper_case_globals)] 14 | const MT_DEVICE_nGnRnE: u64 = 0; 15 | #[allow(non_upper_case_globals)] 16 | const MT_DEVICE_nGnRE: u64 = 1; 17 | const MT_DEVICE_GRE: u64 = 2; 18 | const MT_NORMAL_NC: u64 = 3; 19 | const MT_NORMAL: u64 = 4; 20 | 21 | #[inline(always)] 22 | const fn mair(attr: u64, mt: u64) -> u64 { 23 | attr << (mt * 8) 24 | } 25 | 26 | /* 27 | * TCR flags 28 | */ 29 | const TCR_IRGN_WBWA: u64 = ((1) << 8) | ((1) << 24); 30 | const TCR_ORGN_WBWA: u64 = ((1) << 10) | ((1) << 26); 31 | const TCR_SHARED: u64 = ((3) << 12) | ((3) << 28); 32 | const TCR_TBI0: u64 = 1 << 37; 33 | const TCR_TBI1: u64 = 1 << 38; 34 | const TCR_ASID16: u64 = 1 << 36; 35 | const TCR_TG1_16K: u64 = 1 << 30; 36 | const TCR_TG1_4K: u64 = 0 << 30; 37 | const TCR_FLAGS: u64 = TCR_IRGN_WBWA | TCR_ORGN_WBWA | TCR_SHARED; 38 | 39 | /// Number of virtual address bits for 4KB page 40 | const VA_BITS: u64 = 48; 41 | 42 | #[inline(always)] 43 | const fn tcr_size(x: u64) -> u64 { 44 | ((64 - x) << 16) | (64 - x) 45 | } 46 | 47 | global_asm!( 48 | include_str!("entry.s"), 49 | start_rust = sym start_rust, 50 | ); 51 | 52 | #[inline(never)] 53 | pub unsafe fn start_rust() -> ! { 54 | unsafe { pre_init() } 55 | } 56 | 57 | unsafe fn pre_init() -> ! { 58 | info!("Enter startup code"); 59 | 60 | /* disable interrupts */ 61 | unsafe { 62 | asm!("msr daifset, 0b111", options(nostack)); 63 | } 64 | 65 | /* reset thread id registers */ 66 | unsafe { 67 | asm!("msr tpidr_el0, xzr", "msr tpidr_el1, xzr", options(nostack)); 68 | } 69 | 70 | /* 71 | * Disable the MMU. We may have entered the kernel with it on and 72 | * will need to update the tables later. If this has been set up 73 | * with anything other than a VA == PA map then this will fail, 74 | * but in this case the code to find where we are running from 75 | * would have also failed. 76 | */ 77 | unsafe { 78 | asm!("dsb sy", 79 | "mrs x2, sctlr_el1", 80 | "bic x2, x2, 0x1", 81 | "msr sctlr_el1, x2", 82 | "isb", 83 | out("x2") _, 84 | options(nostack), 85 | ); 86 | } 87 | 88 | unsafe { 89 | asm!("ic iallu", "tlbi vmalle1is", "dsb ish", options(nostack)); 90 | } 91 | 92 | /* 93 | * Setup memory attribute type tables 94 | * 95 | * Memory regioin attributes for LPAE: 96 | * 97 | * n = AttrIndx[2:0] 98 | * n MAIR 99 | * DEVICE_nGnRnE 000 00000000 (0x00) 100 | * DEVICE_nGnRE 001 00000100 (0x04) 101 | * DEVICE_GRE 010 00001100 (0x0c) 102 | * NORMAL_NC 011 01000100 (0x44) 103 | * NORMAL 100 11111111 (0xff) 104 | */ 105 | let mair_el1 = mair(0x00, MT_DEVICE_nGnRnE) 106 | | mair(0x04, MT_DEVICE_nGnRE) 107 | | mair(0x0c, MT_DEVICE_GRE) 108 | | mair(0x44, MT_NORMAL_NC) 109 | | mair(0xff, MT_NORMAL); 110 | unsafe { 111 | asm!("msr mair_el1, {}", 112 | in(reg) mair_el1, 113 | options(nostack), 114 | ); 115 | } 116 | 117 | /* 118 | * Setup translation control register (TCR) 119 | */ 120 | 121 | // determine physical address size 122 | unsafe { 123 | asm!("mrs x0, id_aa64mmfr0_el1", 124 | "and x0, x0, 0xF", 125 | "lsl x0, x0, 32", 126 | "orr x0, x0, {tcr_bits}", 127 | "mrs x1, id_aa64mmfr0_el1", 128 | "bfi x0, x1, #32, #3", 129 | "msr tcr_el1, x0", 130 | tcr_bits = in(reg) tcr_size(VA_BITS) | TCR_TG1_4K | TCR_FLAGS, 131 | out("x0") _, 132 | out("x1") _, 133 | ); 134 | } 135 | 136 | /* 137 | * Enable FP/ASIMD in Architectural Feature Access Control Register, 138 | */ 139 | let bit_mask: u64 = 3 << 20; 140 | unsafe { 141 | asm!("msr cpacr_el1, {mask}", 142 | mask = in(reg) bit_mask, 143 | options(nostack), 144 | ); 145 | } 146 | 147 | /* 148 | * Reset debug control register 149 | */ 150 | unsafe { 151 | asm!("msr mdscr_el1, xzr", options(nostack)); 152 | } 153 | 154 | /* Memory barrier */ 155 | unsafe { 156 | asm!("dsb sy", options(nostack)); 157 | } 158 | 159 | /* 160 | * Prepare system control register (SCTRL) 161 | * Todo: - Verify if all of these bits actually should be explicitly set 162 | - Link origin of this documentation and check to which instruction set versions 163 | it applies (if applicable) 164 | - Fill in the missing Documentation for some of the bits and verify if we care about them 165 | or if loading and not setting them would be the appropriate action. 166 | */ 167 | 168 | #[cfg(target_endian = "big")] 169 | let endian = SCTLR_EL1::EE::BigEndian + SCTLR_EL1::E0E::BigEndian; 170 | #[cfg(target_endian = "little")] 171 | let endian = SCTLR_EL1::EE::LittleEndian + SCTLR_EL1::E0E::LittleEndian; 172 | 173 | SCTLR_EL1.write( 174 | SCTLR_EL1::UCI::DontTrap 175 | + SCTLR_EL1::WXN::Disable 176 | + SCTLR_EL1::NTWE::DontTrap 177 | + SCTLR_EL1::NTWI::DontTrap 178 | + SCTLR_EL1::UCT::DontTrap 179 | + SCTLR_EL1::DZE::DontTrap 180 | + SCTLR_EL1::I::Cacheable 181 | + SCTLR_EL1::UMA::Trap 182 | + SCTLR_EL1::NAA::Disable 183 | + SCTLR_EL1::SA0::Enable 184 | + SCTLR_EL1::SA::Enable 185 | + SCTLR_EL1::C::Cacheable 186 | + SCTLR_EL1::A::Disable 187 | + SCTLR_EL1::M::Disable 188 | + endian, 189 | ); 190 | 191 | // Enter loader 192 | unsafe { 193 | crate::os::loader_main(); 194 | } 195 | } 196 | 197 | pub unsafe fn wait_forever() -> ! { 198 | loop { 199 | unsafe { 200 | asm!("wfe"); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/arch/x86_64/multiboot.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::ToOwned; 2 | use core::ptr::write_bytes; 3 | use core::{mem, ptr, slice}; 4 | 5 | use align_address::Align; 6 | use hermit_entry::boot_info::{ 7 | BootInfo, DeviceTreeAddress, HardwareInfo, PlatformInfo, SerialPortBase, 8 | }; 9 | use hermit_entry::elf::LoadedKernel; 10 | use log::info; 11 | use multiboot::information::{MemoryManagement, Multiboot, PAddr}; 12 | use sptr::Strict; 13 | use vm_fdt::FdtWriterResult; 14 | use x86_64::structures::paging::{PageSize, PageTableFlags, Size2MiB, Size4KiB}; 15 | 16 | use super::paging; 17 | use super::physicalmem::PhysAlloc; 18 | use crate::BootInfoExt; 19 | use crate::arch::x86_64::{KERNEL_STACK_SIZE, SERIAL_IO_PORT}; 20 | use crate::fdt::Fdt; 21 | 22 | unsafe extern "C" { 23 | static mut loader_end: u8; 24 | static mb_info: usize; 25 | } 26 | 27 | #[allow(bad_asm_style)] 28 | mod entry { 29 | core::arch::global_asm!( 30 | include_str!("entry.s"), 31 | loader_main = sym crate::os::loader_main, 32 | ); 33 | } 34 | 35 | struct Mem; 36 | 37 | impl MemoryManagement for Mem { 38 | unsafe fn paddr_to_slice<'a>(&self, p: PAddr, sz: usize) -> Option<&'static [u8]> { 39 | let ptr = sptr::from_exposed_addr(p as usize); 40 | unsafe { Some(slice::from_raw_parts(ptr, sz)) } 41 | } 42 | 43 | // If you only want to read fields, you can simply return `None`. 44 | unsafe fn allocate(&mut self, _length: usize) -> Option<(PAddr, &mut [u8])> { 45 | None 46 | } 47 | 48 | unsafe fn deallocate(&mut self, addr: PAddr) { 49 | if addr != 0 { 50 | unimplemented!() 51 | } 52 | } 53 | } 54 | 55 | pub struct DeviceTree; 56 | 57 | impl DeviceTree { 58 | pub fn create() -> FdtWriterResult<&'static [u8]> { 59 | let mut mem = Mem; 60 | let multiboot = unsafe { Multiboot::from_ptr(mb_info as u64, &mut mem).unwrap() }; 61 | 62 | let memory_regions = multiboot 63 | .memory_regions() 64 | .expect("Could not find a memory map in the Multiboot information"); 65 | 66 | let mut fdt = Fdt::new("multiboot")?.memory_regions(memory_regions)?; 67 | 68 | if let Some(cmdline) = multiboot.command_line() { 69 | fdt = fdt.bootargs(cmdline.to_owned())?; 70 | } 71 | 72 | let fdt = fdt.finish()?; 73 | 74 | Ok(fdt.leak()) 75 | } 76 | } 77 | 78 | pub fn find_kernel() -> &'static [u8] { 79 | use core::cmp; 80 | 81 | paging::clean_up(); 82 | // Identity-map the Multiboot information. 83 | unsafe { 84 | assert!(mb_info > 0, "Could not find Multiboot information"); 85 | info!("Found Multiboot information at {mb_info:#x}"); 86 | paging::map::(mb_info, mb_info, 1, PageTableFlags::empty()) 87 | } 88 | 89 | let mut mem = Mem; 90 | // Load the Multiboot information and identity-map the modules information. 91 | let multiboot = unsafe { Multiboot::from_ptr(mb_info as u64, &mut mem).unwrap() }; 92 | 93 | // Iterate through all modules. 94 | // Collect the start address of the first module and the highest end address of all modules. 95 | let mut module_iter = multiboot 96 | .modules() 97 | .expect("Could not find a memory map in the Multiboot information"); 98 | 99 | let first_module = module_iter 100 | .next() 101 | .expect("Could not find a single module in the Multiboot information"); 102 | info!( 103 | "Found an ELF module at [{:#x} - {:#x}]", 104 | first_module.start, first_module.end 105 | ); 106 | let elf_start = first_module.start as usize; 107 | let elf_len = (first_module.end - first_module.start) as usize; 108 | info!("Module length: {elf_len:#x}"); 109 | 110 | // Find the maximum end address from the remaining modules 111 | let mut end_address = first_module.end; 112 | for m in module_iter { 113 | end_address = cmp::max(end_address, m.end); 114 | } 115 | 116 | let modules_mapping_end = end_address.align_up(Size2MiB::SIZE) as usize; 117 | // TODO: Workaround for https://github.com/hermitcore/loader/issues/96 118 | let free_memory_address = cmp::max(modules_mapping_end, 0x800000); 119 | // Memory after the highest end address is unused and available for the physical memory manager. 120 | PhysAlloc::init(free_memory_address); 121 | 122 | // Identity-map the ELF header of the first module and until the 2 MiB 123 | // mapping starts. We cannot start the 2 MiB mapping right from 124 | // `first_module.end` because when it is aligned down, the 125 | // resulting mapping range may overlap with the 4 KiB mapping. 126 | let first_module_mapping_end = first_module.start.align_up(Size2MiB::SIZE) as usize; 127 | paging::map_range::( 128 | first_module.start as usize, 129 | first_module.start as usize, 130 | first_module_mapping_end, 131 | PageTableFlags::empty(), 132 | ); 133 | 134 | // map also the rest of the modules 135 | paging::map_range::( 136 | first_module_mapping_end, 137 | first_module_mapping_end, 138 | modules_mapping_end, 139 | PageTableFlags::empty(), 140 | ); 141 | 142 | unsafe { slice::from_raw_parts(sptr::from_exposed_addr(elf_start), elf_len) } 143 | } 144 | 145 | pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { 146 | let LoadedKernel { 147 | load_info, 148 | entry_point, 149 | } = kernel_info; 150 | 151 | let mut mem = Mem; 152 | let multiboot = unsafe { Multiboot::from_ptr(mb_info as u64, &mut mem).unwrap() }; 153 | 154 | // determine boot stack address 155 | let mut new_stack = ptr::addr_of!(loader_end) 156 | .addr() 157 | .align_up(Size4KiB::SIZE as usize); 158 | 159 | if new_stack + KERNEL_STACK_SIZE as usize > unsafe { mb_info } { 160 | new_stack = (unsafe { mb_info } + mem::size_of::>()) 161 | .align_up(Size4KiB::SIZE as usize); 162 | } 163 | 164 | let command_line = multiboot.command_line(); 165 | if let Some(command_line) = command_line { 166 | let cmdline = command_line.as_ptr() as usize; 167 | let cmdsize = command_line.len(); 168 | if new_stack + KERNEL_STACK_SIZE as usize > cmdline { 169 | new_stack = (cmdline + cmdsize).align_up(Size4KiB::SIZE as usize); 170 | } 171 | } 172 | 173 | // map stack in the address space 174 | paging::map::( 175 | new_stack, 176 | new_stack, 177 | KERNEL_STACK_SIZE as usize / Size4KiB::SIZE as usize, 178 | PageTableFlags::WRITABLE, 179 | ); 180 | 181 | let stack = ptr::addr_of_mut!(loader_end).with_addr(new_stack); 182 | 183 | // clear stack 184 | unsafe { 185 | write_bytes(stack, 0, KERNEL_STACK_SIZE.try_into().unwrap()); 186 | } 187 | 188 | let device_tree = DeviceTree::create().expect("Unable to create devicetree!"); 189 | let device_tree = 190 | DeviceTreeAddress::new(u64::try_from(device_tree.as_ptr().expose_addr()).unwrap()); 191 | 192 | let boot_info = BootInfo { 193 | hardware_info: HardwareInfo { 194 | phys_addr_range: 0..0, 195 | serial_port_base: SerialPortBase::new(SERIAL_IO_PORT), 196 | device_tree, 197 | }, 198 | load_info, 199 | platform_info: PlatformInfo::Multiboot { 200 | command_line, 201 | multiboot_info_addr: (unsafe { mb_info } as u64).try_into().unwrap(), 202 | }, 203 | }; 204 | 205 | let entry = sptr::from_exposed_addr(entry_point.try_into().unwrap()); 206 | let raw_boot_info = boot_info.write(); 207 | 208 | unsafe { super::enter_kernel(stack, entry, raw_boot_info) } 209 | } 210 | -------------------------------------------------------------------------------- /src/arch/x86_64/entry.s: -------------------------------------------------------------------------------- 1 | # This is the kernel's entry point. We could either call main here, 2 | # or we can use this to setup the stack or other nice stuff, like 3 | # perhaps setting up the GDT and segments. Please note that interrupts 4 | # are disabled at this point: More on interrupts later! 5 | 6 | .code32 7 | 8 | .set BOOT_STACK_SIZE, 4096 9 | 10 | .extern loader_start # defined in linker script 11 | .extern loader_end 12 | 13 | # We use a special name to map this section at the begin of our kernel 14 | # => Multiboot expects its magic number at the beginning of the kernel. 15 | .section .mboot, "a" 16 | 17 | # This part MUST be 4 byte aligned, so we solve that issue using '.align 4'. 18 | .align 4 19 | mboot: 20 | # Multiboot macros to make a few lines more readable later 21 | .set MULTIBOOT_PAGE_ALIGN, (1 << 0) 22 | .set MULTIBOOT_MEMORY_INFO, (1 << 1) 23 | .set MULTIBOOT_HEADER_MAGIC, 0x1BADB002 24 | .set MULTIBOOT_HEADER_FLAGS, MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO 25 | .set MULTIBOOT_CHECKSUM, -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) 26 | 27 | # This is the GRUB Multiboot header. A boot signature 28 | .4byte MULTIBOOT_HEADER_MAGIC 29 | .4byte MULTIBOOT_HEADER_FLAGS 30 | .4byte MULTIBOOT_CHECKSUM 31 | .4byte 0, 0, 0, 0, 0 # address fields 32 | 33 | .align 4 34 | # we need already a valid GDT to switch in the 64bit modus 35 | GDT64: # Global Descriptor Table (64-bit). 36 | .set GDT64.Null, . - GDT64 # The null descriptor. 37 | .2byte 0 # Limit (low). 38 | .2byte 0 # Base (low). 39 | .byte 0 # Base (middle) 40 | .byte 0 # Access. 41 | .byte 0 # Granularity. 42 | .byte 0 # Base (high). 43 | .set GDT64.Code, . - GDT64 # The code descriptor. 44 | .2byte 0 # Limit (low). 45 | .2byte 0 # Base (low). 46 | .byte 0 # Base (middle) 47 | .byte 0b10011010 # Access. 48 | .byte 0b00100000 # Granularity. 49 | .byte 0 # Base (high). 50 | .set GDT64.Data, . - GDT64 # The data descriptor. 51 | .2byte 0 # Limit (low). 52 | .2byte 0 # Base (low). 53 | .byte 0 # Base (middle) 54 | .byte 0b10010010 # Access. 55 | .byte 0b00000000 # Granularity. 56 | .byte 0 # Base (high). 57 | GDT64.Pointer: # The GDT-pointer. 58 | .2byte . - GDT64 - 1 # Limit. 59 | .8byte GDT64 # Base. 60 | 61 | .section .text 62 | .align 4 63 | .global _start 64 | _start: 65 | cli # avoid any interrupt 66 | 67 | # Initialize stack pointer 68 | mov esp, OFFSET boot_stack 69 | add esp, BOOT_STACK_SIZE - 16 70 | 71 | # Interpret multiboot information 72 | mov [mb_info], ebx 73 | 74 | # This will set up the x86 control registers: 75 | # Caching and the floating point unit are enabled 76 | # Bootstrap page tables are loaded and page size 77 | # extensions (huge pages) enabled. 78 | cpu_init: 79 | 80 | # initialize page tables 81 | # map kernel 1:1 82 | push edi 83 | push ebx 84 | push ecx 85 | mov ecx, OFFSET loader_start 86 | mov ebx, OFFSET loader_end 87 | add ebx, 0x1000 88 | L0: cmp ecx, ebx 89 | jae L1 90 | mov eax, ecx 91 | and eax, 0xFFFFF000 # page align lower half 92 | mov edi, eax 93 | shr edi, 9 # (edi >> 12) * 8 (index for boot_pgt) 94 | add edi, OFFSET boot_pgt1 95 | or eax, 0x3 # set present and writable bits 96 | mov [edi], eax 97 | add ecx, 0x1000 98 | jmp L0 99 | L1: 100 | pop ecx 101 | pop ebx 102 | pop edi 103 | 104 | # check for long mode 105 | 106 | # do we have the instruction cpuid? 107 | pushfd 108 | pop eax 109 | mov ecx, eax 110 | xor eax, 1 << 21 111 | push eax 112 | popfd 113 | pushfd 114 | pop eax 115 | push ecx 116 | popfd 117 | xor eax, ecx 118 | jz Linvalid 119 | 120 | # cpuid > 0x80000000? 121 | mov eax, 0x80000000 122 | cpuid 123 | cmp eax, 0x80000001 124 | jb Linvalid # It is less, there is no long mode. 125 | 126 | # do we have a long mode? 127 | mov eax, 0x80000001 128 | cpuid 129 | test edx, 1 << 29 # Test if the LM-bit, which is bit 29, is set in the D-register. 130 | jz Linvalid # They aren't, there is no long mode. 131 | 132 | # Set CR3 133 | mov eax, OFFSET boot_pml4 134 | mov cr3, eax 135 | 136 | # we need to enable PAE modus 137 | mov eax, cr4 138 | or eax, 1 << 5 139 | mov cr4, eax 140 | 141 | # switch to the compatibility mode (which is part of long mode) 142 | mov ecx, 0xC0000080 143 | rdmsr 144 | or eax, 1 << 8 145 | wrmsr 146 | 147 | # Set CR4 148 | mov eax, cr4 149 | and eax, 0xfffbf9ff # disable SSE 150 | # or eax, (1 << 7) # enable PGE 151 | mov cr4, eax 152 | 153 | # Set CR0 (PM-bit is already set) 154 | mov eax, cr0 155 | and eax, ~(1 << 2) # disable FPU emulation 156 | or eax, (1 << 1) # enable FPU montitoring 157 | and eax, ~(1 << 30) # enable caching 158 | and eax, ~(1 << 29) # disable write through caching 159 | and eax, ~(1 << 16) # allow kernel write access to read-only pages 160 | or eax, (1 << 31) # enable paging 161 | mov cr0, eax 162 | 163 | lgdt [GDT64.Pointer] # Load the 64-bit global descriptor table. 164 | # https://github.com/llvm/llvm-project/issues/46048 165 | .att_syntax prefix 166 | # Set the code segment and enter 64-bit long mode. 167 | ljmp $GDT64.Code, $start64 168 | .intel_syntax noprefix 169 | 170 | # there is no long mode 171 | Linvalid: 172 | jmp Linvalid 173 | 174 | .code64 175 | start64: 176 | # initialize segment registers 177 | mov ax, OFFSET GDT64.Data 178 | mov ds, eax 179 | mov es, eax 180 | mov ss, eax 181 | xor ax, ax 182 | mov fs, eax 183 | mov gs, eax 184 | cld 185 | # set default stack pointer 186 | movabs rsp, OFFSET boot_stack 187 | add rsp, BOOT_STACK_SIZE-16 188 | 189 | # jump to the boot processors's C code 190 | jmp {loader_main} 191 | jmp start64+0x28 192 | 193 | .section .data 194 | 195 | .global mb_info 196 | .align 8 197 | mb_info: 198 | .8byte 0 199 | 200 | .align 4096 201 | .global boot_stack 202 | boot_stack: 203 | .fill BOOT_STACK_SIZE, 1, 0xcd 204 | 205 | # Bootstrap page tables are used during the initialization. 206 | .align 4096 207 | boot_pml4: 208 | .8byte boot_pdpt + 0x3 # PG_PRESENT | PG_RW 209 | .fill 510, 8, 0 # PAGE_MAP_ENTRIES - 2 210 | .8byte boot_pml4 + 0x3 # PG_PRESENT | PG_RW 211 | boot_pdpt: 212 | .8byte boot_pgd + 0x3 # PG_PRESENT | PG_RW 213 | .fill 511, 8, 0 # PAGE_MAP_ENTRIES - 1 214 | boot_pgd: 215 | .8byte boot_pgt1 + 0x3 # PG_PRESENT | PG_RW 216 | .8byte boot_pgt2 + 0x3 # PG_PRESENT | PG_RW 217 | .fill 510, 8, 0 # PAGE_MAP_ENTRIES - 1 218 | boot_pgt1: 219 | .fill 512, 8, 0 220 | boot_pgt2: 221 | .fill 512, 8, 0 222 | -------------------------------------------------------------------------------- /xtask/src/ci/qemu.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::{Command, ExitStatus}; 3 | 4 | use anyhow::{Result, ensure}; 5 | use clap::Args; 6 | use sysinfo::{CpuRefreshKind, System}; 7 | use xshell::cmd; 8 | 9 | use crate::build::Build; 10 | use crate::target::Target; 11 | 12 | /// Run hermit-rs images on QEMU. 13 | #[derive(Args)] 14 | pub struct Qemu { 15 | /// Enable hardware acceleration. 16 | #[arg(long)] 17 | accel: bool, 18 | 19 | /// Run QEMU using `sudo`. 20 | #[arg(long)] 21 | sudo: bool, 22 | 23 | /// Enable the `microvm` machine type. 24 | #[arg(long)] 25 | microvm: bool, 26 | 27 | #[command(flatten)] 28 | build: Build, 29 | 30 | #[arg(long)] 31 | image: Option, 32 | } 33 | 34 | impl Qemu { 35 | pub fn run(mut self) -> Result<()> { 36 | let default_image = if self.microvm { 37 | "hello_world-microvm" 38 | } else { 39 | "hello_world" 40 | }; 41 | 42 | self.image.get_or_insert_with(|| default_image.to_string()); 43 | 44 | if super::in_ci() { 45 | eprintln!("::group::cargo build") 46 | } 47 | 48 | self.build.run()?; 49 | 50 | if super::in_ci() { 51 | eprintln!("::endgroup::") 52 | } 53 | 54 | let sh = crate::sh()?; 55 | 56 | if self.build.target() == Target::X86_64Uefi { 57 | sh.create_dir("target/esp/efi/boot")?; 58 | sh.copy_file(self.build.dist_object(), "target/esp/efi/boot/bootx64.efi")?; 59 | sh.copy_file( 60 | self.build.ci_image(self.image.as_deref().unwrap()), 61 | "target/esp/efi/boot/hermit-app", 62 | )?; 63 | } 64 | 65 | let target = self.build.target(); 66 | let qemu = target.qemu(); 67 | let qemu = env::var("QEMU").unwrap_or_else(|_| format!("qemu-system-{qemu}")); 68 | let program = if self.sudo { "sudo" } else { qemu.as_str() }; 69 | let arg = self.sudo.then_some(qemu.as_str()); 70 | 71 | let qemu = cmd!(sh, "{program} {arg...}") 72 | .args(&["-display", "none"]) 73 | .args(&["-serial", "stdio"]) 74 | .args(self.machine_args()) 75 | .args(self.cpu_args()) 76 | .args(self.memory_args()); 77 | 78 | eprintln!("$ {qemu}"); 79 | let status = Command::from(qemu).status()?; 80 | ensure!(status.qemu_success(), "QEMU exit code: {:?}", status.code()); 81 | 82 | Ok(()) 83 | } 84 | 85 | fn machine_args(&self) -> Vec { 86 | if self.microvm { 87 | let frequency = get_frequency(); 88 | vec![ 89 | "-M".to_string(), 90 | "microvm,x-option-roms=off,pit=off,pic=off,rtc=on,auto-kernel-cmdline=off,acpi=off" 91 | .to_string(), 92 | "-global".to_string(), 93 | "virtio-mmio.force-legacy=on".to_string(), 94 | "-nodefaults".to_string(), 95 | "-no-user-config".to_string(), 96 | "-append".to_string(), 97 | format!("-freq {frequency}"), 98 | ] 99 | } else if matches!(self.build.target(), Target::Aarch64 | Target::Aarch64Be) { 100 | vec!["-machine".to_string(), "virt,gic-version=3".to_string()] 101 | } else if self.build.target() == Target::Riscv64 { 102 | vec![ 103 | "-machine".to_string(), 104 | "virt".to_string(), 105 | "-bios".to_string(), 106 | "opensbi-1.7-rv-bin/share/opensbi/lp64/generic/firmware/fw_jump.bin".to_string(), 107 | ] 108 | } else { 109 | vec![] 110 | } 111 | } 112 | 113 | fn cpu_args(&self) -> Vec { 114 | match self.build.target() { 115 | Target::X86_64 | Target::X86_64Uefi => { 116 | let mut cpu_args = if self.accel { 117 | if cfg!(target_os = "linux") { 118 | vec![ 119 | "-enable-kvm".to_string(), 120 | "-cpu".to_string(), 121 | "host".to_string(), 122 | ] 123 | } else { 124 | todo!() 125 | } 126 | } else { 127 | vec!["-cpu".to_string(), "Skylake-Client".to_string()] 128 | }; 129 | cpu_args.push("-device".to_string()); 130 | cpu_args.push("isa-debug-exit,iobase=0xf4,iosize=0x04".to_string()); 131 | 132 | match self.build.target() { 133 | Target::X86_64 => { 134 | cpu_args.push("-kernel".to_string()); 135 | cpu_args.push( 136 | self.build 137 | .dist_object() 138 | .into_os_string() 139 | .into_string() 140 | .unwrap(), 141 | ); 142 | cpu_args.push("-initrd".to_string()); 143 | cpu_args.push( 144 | self.build 145 | .ci_image(self.image.as_deref().unwrap()) 146 | .into_os_string() 147 | .into_string() 148 | .unwrap(), 149 | ); 150 | } 151 | Target::X86_64Uefi => { 152 | use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source}; 153 | 154 | let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf") 155 | .expect("failed to update prebuilt"); 156 | let code = prebuilt.get_file(Arch::X64, FileType::Code); 157 | let vars = prebuilt.get_file(Arch::X64, FileType::Vars); 158 | 159 | cpu_args.push("-drive".to_string()); 160 | cpu_args.push(format!( 161 | "if=pflash,format=raw,readonly=on,file={}", 162 | code.display() 163 | )); 164 | cpu_args.push("-drive".to_string()); 165 | cpu_args.push(format!( 166 | "if=pflash,format=raw,readonly=on,file={}", 167 | vars.display() 168 | )); 169 | cpu_args.push("-drive".to_string()); 170 | cpu_args.push("format=raw,file=fat:rw:target/esp".to_string()); 171 | } 172 | _ => unreachable!(), 173 | } 174 | cpu_args 175 | } 176 | Target::X86_64Fc => panic!("unsupported"), 177 | Target::Aarch64 | Target::Aarch64Be => { 178 | let mut cpu_args = if self.accel { 179 | todo!() 180 | } else { 181 | vec![ 182 | "-cpu".to_string(), 183 | "cortex-a72".to_string(), 184 | "-kernel".to_string(), 185 | self.build 186 | .dist_object() 187 | .into_os_string() 188 | .into_string() 189 | .unwrap(), 190 | ] 191 | }; 192 | cpu_args.push("-semihosting".to_string()); 193 | cpu_args.push("-device".to_string()); 194 | cpu_args.push(format!( 195 | "guest-loader,addr=0x48000000,initrd={}", 196 | self.build 197 | .ci_image(self.image.as_deref().unwrap()) 198 | .display() 199 | )); 200 | cpu_args 201 | } 202 | Target::Riscv64 => { 203 | let mut cpu_args = if self.accel { 204 | todo!() 205 | } else { 206 | vec![ 207 | "-cpu".to_string(), 208 | "rv64".to_string(), 209 | "-kernel".to_string(), 210 | self.build 211 | .dist_object() 212 | .into_os_string() 213 | .into_string() 214 | .unwrap(), 215 | ] 216 | }; 217 | cpu_args.push("-initrd".to_string()); 218 | cpu_args.push( 219 | self.build 220 | .ci_image(self.image.as_deref().unwrap()) 221 | .into_os_string() 222 | .into_string() 223 | .unwrap(), 224 | ); 225 | cpu_args 226 | } 227 | } 228 | } 229 | 230 | fn memory(&self) -> usize { 231 | let mut memory = 64usize; 232 | match self.build.target() { 233 | Target::X86_64Uefi => { 234 | memory = memory.max(512); 235 | } 236 | Target::Aarch64 | Target::Aarch64Be => { 237 | memory = memory.max(256); 238 | } 239 | Target::Riscv64 => { 240 | memory = memory.max(128); 241 | } 242 | _ => {} 243 | } 244 | memory 245 | } 246 | 247 | fn memory_args(&self) -> [String; 2] { 248 | ["-m".to_string(), format!("{}M", self.memory())] 249 | } 250 | } 251 | 252 | fn get_frequency() -> u64 { 253 | let mut sys = System::new(); 254 | sys.refresh_cpu_specifics(CpuRefreshKind::nothing().with_frequency()); 255 | let frequency = sys.cpus().first().unwrap().frequency(); 256 | if !sys.cpus().iter().all(|cpu| cpu.frequency() == frequency) { 257 | eprintln!("CPU frequencies are not all equal"); 258 | } 259 | frequency 260 | } 261 | 262 | trait ExitStatusExt { 263 | fn qemu_success(&self) -> bool; 264 | } 265 | 266 | impl ExitStatusExt for ExitStatus { 267 | fn qemu_success(&self) -> bool { 268 | self.success() || self.code() == Some(3) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![DOI](https://zenodo.org/badge/228644911.svg)](https://doi.org/10.5281/zenodo.14640796) 2 | 3 | # The Hermit Loader 4 | 5 | This project is a bootloader to run the [Hermit kernel](https://github.com/hermitcore/kernel) in different environments. 6 | 7 | ## Requirements 8 | 9 | * [`rustup`](https://www.rust-lang.org/tools/install) 10 | 11 | ### UEFI Boot in x86-64 QEMU 12 | 13 | As QEMU does not include a UEFI implementation, you have to download the Open Virtual Machine Firmware (OVMF) UEFI implementation separately. 14 | You can download prebuilt OVMF images from [rust-osdev/ovmf-prebuilt](https://github.com/rust-osdev/ovmf-prebuilt). 15 | 16 | ## Building 17 | 18 | ```bash 19 | cargo xtask build --target --release 20 | ``` 21 | 22 | With `` being either `x86_64`, `x86_64-uefi`, `aarch64`, or `riscv64`. 23 | 24 | Afterward, the loader is located in `target/release`. 25 | 26 | ## Running 27 | 28 | ### x86-64 29 | 30 | On x86-64 Linux with KVM, you can boot Hermit like this: 31 | 32 | ```bash 33 | qemu-system-x86_64 \ 34 | -enable-kvm \ 35 | -cpu host \ 36 | -smp 1 \ 37 | -m 128M \ 38 | -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ 39 | -display none -serial stdio \ 40 | -kernel \ 41 | -initrd 42 | ``` 43 | 44 | #### UEFI Boot 45 | 46 | For booting from UEFI, we have to set up an EFI system partition (ESP). 47 | OVMF will automatically load and execute the loader if placed at `\efi\boot\bootx64.efi` in the ESP. 48 | The Hermit application has to be next to the loader with the filename `hermit-app`. 49 | You can set the ESP up with the following commands: 50 | 51 | ```bash 52 | $ mkdir -p esp/efi/boot 53 | $ cp esp/efi/boot/bootx64.efi 54 | $ cp esp/efi/boot/hermit-app 55 | ``` 56 | 57 | Then, you can boot Hermit like this: 58 | 59 | ```bash 60 | qemu-system-x86_64 \ 61 | -enable-kvm \ 62 | -cpu host \ 63 | -smp 1 \ 64 | -m 512M \ 65 | -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ 66 | -display none -serial stdio \ 67 | -drive if=pflash,format=raw,readonly=on,file= \ 68 | -drive if=pflash,format=raw,readonly=on,file= \ 69 | -drive format=raw,file=fat:rw:esp 70 | ``` 71 | 72 | #### No KVM 73 | 74 | If you want to emulate x86-64 instead of using KVM, omit `-enable-kvm` and set the CPU explicitly to a model of your choice, for example `-cpu Skylake-Client`. 75 | 76 | #### Benchmarking 77 | 78 | If you want to benchmark Hermit, make sure to enable the _invariant TSC_ (`invtsc`) feature by setting `-cpu host,migratable=no,+invtsc,enforce`. 79 | 80 | #### Providing Arguments 81 | 82 | Unikernel arguments can be provided like this: 83 | 84 | ```bash 85 | qemu-system-x86_64 ... \ 86 | -append "[KERNEL_ARGS] [--] [APP_ARGS]" 87 | ``` 88 | 89 | ### AArch64 90 | 91 | On AArch64, the base command is as follows: 92 | 93 | ```bash 94 | qemu-system-aarch64 \ 95 | -machine virt,gic-version=3 \ 96 | -cpu cortex-a76 \ 97 | -smp 1 \ 98 | -m 512M \ 99 | -semihosting \ 100 | -display none -serial stdio \ 101 | -kernel \ 102 | -device guest-loader,addr=0x48000000,initrd= 103 | ``` 104 | 105 | ### 64-bit RISC-V 106 | 107 | For 64-bit RISC-V, we need a recent version of [OpenSBI]. 108 | To download the release asset with [GitHub CLI] and extract the correct binary, run: 109 | 110 | ```bash 111 | gh release download v1.7 --repo riscv-software-src/opensbi --pattern 'opensbi-*-rv-bin.tar.xz' 112 | tar -xvf opensbi-*-rv-bin.tar.xz opensbi-1.7-rv-bin/share/opensbi/lp64/generic/firmware/fw_jump.bin 113 | ``` 114 | 115 | [OpenSBI]: https://github.com/riscv-software-src/opensbi 116 | [GitHub CLI]: https://cli.github.com/ 117 | 118 | The QEMU base command is as follows: 119 | 120 | ```bash 121 | qemu-system-riscv64 \ 122 | -machine virt \ 123 | -cpu rv64 \ 124 | -smp 1 \ 125 | -m 128M \ 126 | -display none -serial stdio \ 127 | -bios opensbi-1.7-rv-bin/share/opensbi/lp64/generic/firmware/fw_jump.bin 128 | -kernel 129 | -initrd 130 | ``` 131 | 132 | ### Debugging 133 | 134 | You can use QEMU to debug the loaded Hermit images: 135 | 136 | 1. Start your Hermit image normally. 137 | 138 | Look for the following line: 139 | 140 | ```log 141 | [LOADER][INFO] Loading kernel to .. (len = B) 142 | ``` 143 | 144 | We need to know `` to tell GDB later where the program is loaded. 145 | 146 | 2. Add `-S -s` to your QEMU command. 147 | 148 | `-S` makes QEMU start with a stopped CPU, which can be started explicitly. 149 | `-s` is a shorthand for `-gdb tcp::1234` for accepting GDB connections. 150 | 151 | 3. Start GDB without arguments. 152 | 153 | You should use the `rust-gdb` or `rust-gdbgui` wrappers for Rust's pretty printing. 154 | Both respect the `RUST_GDB` environment variable for cross-debugging (e.g., `aarch64-elf-gdb`). 155 | 156 | 4. Connect to QEMU. 157 | 158 | ```gdb 159 | target remote :1234 160 | ``` 161 | 162 | 5. Load the Hermit image to the correct address. 163 | 164 | We can now tell GDB where the Hermit image will be located: 165 | 166 | ```gdb 167 | symbol-file -o 168 | ``` 169 | 170 | 6. Debug away! 171 | 172 | You can now add breakpoints and start execution: 173 | 174 | ```gdb 175 | b hermit::boot_processor_main 176 | c 177 | ``` 178 | 179 | For fast iteration times, consider creating a [`.gdbinit`](https://sourceware.org/gdb/onlinedocs/gdb/gdbinit-man.html). 180 | 181 | 182 | ### Using QEMU as microvm 183 | 184 | QEMU provides the [microvm virtual platform], which is a minimalist machine type without PCI nor ACPI support. 185 | Microvms have a smaller memory footprint and a faster boot time. 186 | 187 | [microvm virtual platform]: https://qemu.readthedocs.io/en/latest/system/i386/microvm.html 188 | 189 | To use this VM type, PCI and ACPI support have to be disabled for your app (using `no-default-features`). 190 | 191 | ```bash 192 | qemu-system-x86_64 ... \ 193 | -M microvm,x-option-roms=off,pit=off,pic=off,rtc=on,auto-kernel-cmdline=off,acpi=off \ 194 | -nodefaults -no-user-config \ 195 | -append "-freq 2800" 196 | ``` 197 | 198 | Depending on the virtualized processor, the processor frequency has to be passed as kernel argument (`-freq`, in MHz). 199 | 200 | ### Network support 201 | 202 | To enable an Ethernet device, we have to set up a tap device on the host system. 203 | The following commands establish the tap device `tap10` on Linux: 204 | 205 | ```bash 206 | ip tuntap add tap10 mode tap 207 | ip addr add 10.0.5.1/24 broadcast 10.0.5.255 dev tap10 208 | ip link set dev tap10 up 209 | echo 1 > /proc/sys/net/ipv4/conf/tap10/proxy_arp 210 | ``` 211 | 212 | If you want Hermit to be accessible from outside the host, you have to enable IP forwarding: 213 | ```bash 214 | sysctl -w net.ipv4.ip_forward=1 215 | ``` 216 | 217 | You need to enable the `tcp` feature of the kernel. 218 | 219 | The network configuration can be set via environment variables during compile time. 220 | By default, it is: 221 | 222 | ```bash 223 | HERMIT_IP="10.0.5.3" 224 | HERMIT_GATEWAY="10.0.5.1" 225 | HERMIT_MASK="255.255.255.0" 226 | ``` 227 | 228 | Currently, Hermit only supports [Virtio]: 229 | 230 | [Virtio]: https://www.redhat.com/en/blog/introduction-virtio-networking-and-vhost-net 231 | 232 | ```bash 233 | qemu-system-x86_64 ... \ 234 | -netdev tap,id=net0,ifname=tap10,script=no,downscript=no,vhost=on \ 235 | -device virtio-net-pci,netdev=net0,disable-legacy=on 236 | ``` 237 | 238 | You can now access the files in `SHARED_DIRECTORY` under the virtiofs tag like `/myfs/testfile`. 239 | 240 | ## License 241 | 242 | Licensed under either of 243 | 244 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 245 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 246 | 247 | at your option. 248 | -------------------------------------------------------------------------------- /src/arch/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | mod console; 2 | 3 | pub use self::console::Console; 4 | pub mod drivers; 5 | pub mod entry; 6 | pub mod paging; 7 | 8 | use core::arch::asm; 9 | use core::ptr::{self}; 10 | 11 | use aarch64_cpu::asm::barrier::{NSH, SY, dmb, dsb, isb}; 12 | use align_address::Align; 13 | use fdt::Fdt; 14 | use goblin::elf::header::header64::{EI_DATA, ELFDATA2LSB, ELFMAG, Header, SELFMAG}; 15 | use hermit_entry::Entry; 16 | use hermit_entry::boot_info::{BootInfo, HardwareInfo, PlatformInfo, RawBootInfo, SerialPortBase}; 17 | use hermit_entry::elf::LoadedKernel; 18 | use log::info; 19 | use sptr::Strict; 20 | 21 | use crate::BootInfoExt; 22 | use crate::arch::paging::*; 23 | use crate::os::CONSOLE; 24 | 25 | unsafe extern "C" { 26 | static mut loader_end: u8; 27 | static mut l0_pgtable: u64; 28 | static mut l1_pgtable: u64; 29 | static mut l2_pgtable: u64; 30 | static mut l2k_pgtable: u64; 31 | static mut l3_pgtable: u64; 32 | static mut L0mib_pgtable: u64; 33 | static mut dtb_addr: u64; 34 | } 35 | 36 | /// start address of the RAM at Qemu's virt emulation 37 | const RAM_START: u64 = 0x40000000; 38 | /// Default stack size of the kernel 39 | const KERNEL_STACK_SIZE: usize = 32_768; 40 | /// Qemu assumes for ELF kernel that the DTB is located at 41 | /// start of RAM (0x4000_0000) 42 | /// see 43 | const DEVICE_TREE: u64 = RAM_START; 44 | 45 | #[allow(dead_code)] 46 | const PT_DEVICE: u64 = 0x707; 47 | const PT_PT: u64 = 0x713; 48 | const PT_MEM: u64 = 0x713; 49 | const PT_MEM_CD: u64 = 0x70F; 50 | const PT_SELF: u64 = 1 << 55; 51 | 52 | pub unsafe fn get_memory(_memory_size: u64) -> u64 { 53 | (ptr::addr_of_mut!(loader_end).expose_addr() as u64).align_up(LargePageSize::SIZE as u64) 54 | } 55 | 56 | pub unsafe fn get_dtb_addr() -> u64 { 57 | unsafe { if dtb_addr != 0 { dtb_addr } else { DEVICE_TREE } } 58 | } 59 | 60 | pub fn find_kernel() -> &'static [u8] { 61 | let dtb = unsafe { 62 | Fdt::from_ptr(sptr::from_exposed_addr(get_dtb_addr() as usize)) 63 | .expect(".dtb file has invalid header") 64 | }; 65 | let module_start = dtb 66 | .find_node("/chosen") 67 | .unwrap() 68 | .children() 69 | .find(|node| node.name.starts_with("module@")) 70 | .map(|node| { 71 | let value = node.name.strip_prefix("module@").unwrap(); 72 | if let Some(value) = value.strip_prefix("0x") { 73 | usize::from_str_radix(value, 16).unwrap() 74 | } else if let Some(value) = value.strip_prefix("0X") { 75 | usize::from_str_radix(value, 16).unwrap() 76 | } else { 77 | value.parse().unwrap() 78 | } 79 | }) 80 | .unwrap(); 81 | 82 | let header = unsafe { 83 | &*core::mem::transmute::<*const u8, *const Header>(sptr::from_exposed_addr(module_start)) 84 | }; 85 | 86 | if header.e_ident[0..SELFMAG] != ELFMAG[..] { 87 | panic!("Didn't find valid ELF file!"); 88 | } 89 | 90 | #[cfg(target_endian = "little")] 91 | let file_size = if header.e_ident[EI_DATA] == ELFDATA2LSB { 92 | header.e_shoff + (header.e_shentsize as u64 * header.e_shnum as u64) 93 | } else { 94 | header.e_shoff.to_le() + (header.e_shentsize.to_le() as u64 * header.e_shnum.to_le() as u64) 95 | }; 96 | #[cfg(target_endian = "big")] 97 | let file_size = if header.e_ident[EI_DATA] == ELFDATA2LSB { 98 | header.e_shoff.to_be() + (header.e_shentsize.to_be() as u64 * header.e_shnum.to_be() as u64) 99 | } else { 100 | header.e_shoff + (header.e_shentsize as u64 * header.e_shnum as u64) 101 | }; 102 | 103 | info!("Found ELF file with size {file_size}"); 104 | 105 | unsafe { 106 | core::slice::from_raw_parts( 107 | sptr::from_exposed_addr(module_start), 108 | file_size.try_into().unwrap(), 109 | ) 110 | } 111 | } 112 | 113 | pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { 114 | let LoadedKernel { 115 | load_info, 116 | entry_point, 117 | } = kernel_info; 118 | 119 | let dtb = unsafe { 120 | Fdt::from_ptr(sptr::from_exposed_addr(get_dtb_addr() as usize)) 121 | .expect(".dtb file has invalid header") 122 | }; 123 | let cpus = dtb.cpus().count(); 124 | info!("Detect {cpus} CPU(s)"); 125 | 126 | let uart_address: u32 = CONSOLE.lock().get().get_stdout(); 127 | info!("Detect UART at {uart_address:#x}"); 128 | 129 | let pgt_slice = unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(l0_pgtable), 512) }; 130 | for i in pgt_slice.iter_mut() { 131 | *i = 0; 132 | } 133 | pgt_slice[0] = ptr::addr_of_mut!(l1_pgtable).expose_addr() as u64 + PT_PT; 134 | pgt_slice[511] = ptr::addr_of_mut!(l0_pgtable).expose_addr() as u64 + PT_PT + PT_SELF; 135 | 136 | let pgt_slice = unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(l1_pgtable), 512) }; 137 | for i in pgt_slice.iter_mut() { 138 | *i = 0; 139 | } 140 | pgt_slice[0] = ptr::addr_of_mut!(l2_pgtable).expose_addr() as u64 + PT_PT; 141 | pgt_slice[1] = ptr::addr_of_mut!(l2k_pgtable).expose_addr() as u64 + PT_PT; 142 | 143 | let pgt_slice = unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(l2_pgtable), 512) }; 144 | for i in pgt_slice.iter_mut() { 145 | *i = 0; 146 | } 147 | pgt_slice[0] = ptr::addr_of_mut!(l3_pgtable).expose_addr() as u64 + PT_PT; 148 | 149 | let pgt_slice = unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(l3_pgtable), 512) }; 150 | for i in pgt_slice.iter_mut() { 151 | *i = 0; 152 | } 153 | pgt_slice[1] = uart_address as u64 + PT_MEM_CD; 154 | 155 | // map kernel to loader_start and stack below the kernel 156 | let pgt_slice = unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(l2k_pgtable), 512) }; 157 | for i in pgt_slice.iter_mut() { 158 | *i = 0; 159 | } 160 | for (i, pgt_slice) in pgt_slice.iter_mut().enumerate().take(10) { 161 | *pgt_slice = ptr::addr_of_mut!(L0mib_pgtable).expose_addr() as u64 162 | + (i * BasePageSize::SIZE) as u64 163 | + PT_PT; 164 | } 165 | 166 | let pgt_slice = 167 | unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(L0mib_pgtable), 10 * 512) }; 168 | for (i, entry) in pgt_slice.iter_mut().enumerate() { 169 | *entry = RAM_START + (i * BasePageSize::SIZE) as u64 + PT_MEM; 170 | } 171 | 172 | CONSOLE.lock().get().set_stdout(0x1000); 173 | 174 | // Load TTBRx 175 | unsafe { 176 | asm!( 177 | "msr ttbr1_el1, xzr", 178 | "msr ttbr0_el1, {}", 179 | "dsb sy", 180 | "isb", 181 | in(reg) ptr::addr_of_mut!(l0_pgtable), 182 | options(nostack), 183 | ) 184 | }; 185 | 186 | // Enable paging 187 | unsafe { 188 | asm!( 189 | "mrs x0, sctlr_el1", 190 | "orr x0, x0, #1", 191 | "msr sctlr_el1, x0", 192 | "bl 0f", 193 | "0:", 194 | out("x0") _, 195 | options(nostack), 196 | ); 197 | } 198 | 199 | info!("Successfully set up paging."); 200 | 201 | let dtb = unsafe { 202 | Fdt::from_ptr(sptr::from_exposed_addr(DEVICE_TREE as usize)) 203 | .expect(".dtb file has invalid header") 204 | }; 205 | 206 | if let Some(device_type) = dtb 207 | .find_node("/memory") 208 | .and_then(|node| node.property("device_type")) 209 | { 210 | let device_type = core::str::from_utf8(device_type.value) 211 | .unwrap() 212 | .trim_matches(char::from(0)); 213 | assert!(device_type == "memory"); 214 | } 215 | info!("Memory found!"); 216 | let regions = dtb.memory().regions().next().unwrap(); 217 | let ram_start = regions.starting_address as u64; 218 | let ram_size = regions.size.unwrap() as u64; 219 | 220 | info!("ram_start: {ram_start:#x}, ram_size: {ram_size:#x}. Trying to jump into kernel soon."); 221 | let boot_info = BootInfo { 222 | hardware_info: HardwareInfo { 223 | phys_addr_range: ram_start..ram_start + ram_size, 224 | serial_port_base: SerialPortBase::new(0x1000), 225 | device_tree: unsafe { core::num::NonZeroU64::new(get_dtb_addr()) }, 226 | }, 227 | load_info, 228 | platform_info: PlatformInfo::LinuxBoot, 229 | }; 230 | 231 | let stack = boot_info.load_info.kernel_image_addr_range.start as usize - KERNEL_STACK_SIZE; 232 | let stack = sptr::from_exposed_addr_mut(stack); 233 | let entry = sptr::from_exposed_addr(entry_point.try_into().unwrap()); 234 | let raw_boot_info = boot_info.write(); 235 | 236 | unsafe { enter_kernel(stack, entry, raw_boot_info) } 237 | } 238 | 239 | unsafe fn enter_kernel(stack: *mut u8, entry: *const (), raw_boot_info: &'static RawBootInfo) -> ! { 240 | // Check expected signature of entry function 241 | let entry: Entry = { 242 | let entry: unsafe extern "C" fn(raw_boot_info: &'static RawBootInfo, cpu_id: u32) -> ! = 243 | unsafe { core::mem::transmute(entry) }; 244 | entry 245 | }; 246 | 247 | info!("Entering kernel at {entry:p}, stack at {stack:p}, raw_boot_info at {raw_boot_info:p}"); 248 | 249 | // Memory barrier 250 | CONSOLE.lock().get().wait_empty(); 251 | dsb(SY); 252 | isb(SY); 253 | dmb(SY); 254 | dsb(NSH); 255 | 256 | unsafe { 257 | asm!( 258 | "mov sp, {stack}", 259 | "br {entry}", 260 | stack = in(reg) stack, 261 | entry = in(reg) entry, 262 | in("x0") raw_boot_info, 263 | in("x1") 0, 264 | options(noreturn) 265 | ) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/arch/x86_64/firecracker.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::ToOwned; 2 | use core::ptr::write_bytes; 3 | use core::{ptr, slice}; 4 | 5 | use align_address::Align; 6 | use hermit_entry::boot_info::{ 7 | BootInfo, DeviceTreeAddress, HardwareInfo, PlatformInfo, SerialPortBase, 8 | }; 9 | use hermit_entry::elf::LoadedKernel; 10 | use hermit_entry::fc::{ 11 | BOOT_FLAG_OFFSET, CMD_LINE_PTR_OFFSET, CMD_LINE_SIZE_OFFSET, E820_ENTRIES_OFFSET, 12 | E820_TABLE_OFFSET, HDR_MAGIC_OFFSET, LINUX_KERNEL_BOOT_FLAG_MAGIC, LINUX_KERNEL_HRD_MAGIC, 13 | LINUX_SETUP_HEADER_OFFSET, RAMDISK_IMAGE_OFFSET, RAMDISK_SIZE_OFFSET, 14 | }; 15 | use log::info; 16 | use sptr::Strict; 17 | use x86_64::structures::paging::{PageSize, PageTableFlags, Size2MiB, Size4KiB}; 18 | 19 | use super::physicalmem::PhysAlloc; 20 | use super::{KERNEL_STACK_SIZE, SERIAL_IO_PORT, paging}; 21 | use crate::BootInfoExt; 22 | use crate::fdt::Fdt; 23 | 24 | unsafe extern "C" { 25 | static mut loader_end: u8; 26 | static boot_params: usize; 27 | } 28 | 29 | mod entry { 30 | core::arch::global_asm!( 31 | include_str!("entry_fc.s"), 32 | loader_main = sym crate::os::loader_main, 33 | ); 34 | } 35 | 36 | pub fn find_kernel() -> &'static [u8] { 37 | use core::cmp; 38 | 39 | paging::clean_up(); 40 | 41 | // Identity-map the Multiboot information. 42 | unsafe { 43 | assert!(boot_params > 0, "Could not find boot_params"); 44 | info!("Found boot_params at {boot_params:#x}"); 45 | } 46 | let page_address = unsafe { boot_params }.align_down(Size4KiB::SIZE as usize); 47 | paging::map::(page_address, page_address, 1, PageTableFlags::empty()); 48 | 49 | let linux_kernel_boot_flag_magic: u16 = unsafe { 50 | *(sptr::from_exposed_addr(boot_params + LINUX_SETUP_HEADER_OFFSET + BOOT_FLAG_OFFSET)) 51 | }; 52 | let linux_kernel_header_magic = unsafe { 53 | sptr::from_exposed_addr::(boot_params + LINUX_SETUP_HEADER_OFFSET + HDR_MAGIC_OFFSET) 54 | .read_unaligned() 55 | }; 56 | if linux_kernel_boot_flag_magic == LINUX_KERNEL_BOOT_FLAG_MAGIC 57 | && linux_kernel_header_magic == LINUX_KERNEL_HRD_MAGIC 58 | { 59 | info!("Found Linux kernel boot flag and header magic! Probably booting in firecracker."); 60 | } else { 61 | info!( 62 | "Kernel boot flag and hdr magic have values {linux_kernel_boot_flag_magic:#x} and {linux_kernel_header_magic:#x} which does not align with the normal linux kernel values" 63 | ); 64 | } 65 | 66 | // Load the boot_param memory-map information 67 | let linux_e820_entries: u8 = 68 | unsafe { *(sptr::from_exposed_addr(boot_params + E820_ENTRIES_OFFSET)) }; 69 | info!("Number of e820-entries: {linux_e820_entries}"); 70 | 71 | let e820_entries_address = unsafe { boot_params } + E820_TABLE_OFFSET; 72 | info!("e820-entry-table at {e820_entries_address:#x}"); 73 | let page_address = e820_entries_address.align_down(Size4KiB::SIZE as usize); 74 | 75 | if !(unsafe { boot_params } >= page_address 76 | && unsafe { boot_params } < page_address + Size4KiB::SIZE as usize) 77 | { 78 | paging::map::(page_address, page_address, 1, PageTableFlags::empty()); 79 | } 80 | 81 | // Load the Hermit-ELF from the initrd supplied by Firecracker 82 | let ramdisk_address: u32 = unsafe { 83 | *(sptr::from_exposed_addr(boot_params + LINUX_SETUP_HEADER_OFFSET + RAMDISK_IMAGE_OFFSET)) 84 | }; 85 | let ramdisk_size: u32 = unsafe { 86 | *(sptr::from_exposed_addr(boot_params + LINUX_SETUP_HEADER_OFFSET + RAMDISK_SIZE_OFFSET)) 87 | }; 88 | 89 | info!("Initrd: Address {ramdisk_address:#x}, Size {ramdisk_size:#x}"); 90 | 91 | let elf_start = ramdisk_address as usize; 92 | let elf_len = ramdisk_size as usize; 93 | 94 | let free_memory_address = ptr::addr_of!(loader_end) 95 | .addr() 96 | .align_up(Size2MiB::SIZE as usize); 97 | // TODO: Workaround for https://github.com/hermitcore/loader/issues/96 98 | let free_memory_address = cmp::max(free_memory_address, 0x800000); 99 | info!("Intialize PhysAlloc with {free_memory_address:#x}"); 100 | // Memory after the highest end address is unused and available for the physical memory manager. 101 | PhysAlloc::init(free_memory_address); 102 | 103 | assert!(ramdisk_address > 0); 104 | info!("Found an ELF module at {elf_start:#x}"); 105 | let page_address = elf_start.align_down(Size4KiB::SIZE as usize); 106 | let counter = 107 | (elf_start.align_up(Size2MiB::SIZE as usize) - page_address) / Size4KiB::SIZE as usize; 108 | paging::map::(page_address, page_address, counter, PageTableFlags::empty()); 109 | 110 | // map also the rest of the module 111 | let address = elf_start.align_up(Size2MiB::SIZE as usize); 112 | let counter = ((elf_start + elf_len).align_up(Size2MiB::SIZE as usize) - address) 113 | / Size2MiB::SIZE as usize; 114 | if counter > 0 { 115 | paging::map::(address, address, counter, PageTableFlags::empty()); 116 | } 117 | 118 | unsafe { slice::from_raw_parts(sptr::from_exposed_addr(elf_start), elf_len) } 119 | } 120 | 121 | pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { 122 | let LoadedKernel { 123 | load_info, 124 | entry_point, 125 | } = kernel_info; 126 | 127 | // determine boot stack address 128 | let new_stack = (ptr::addr_of!(loader_end).addr() + 0x1000).align_up(Size4KiB::SIZE as usize); 129 | 130 | let cmdline_ptr = unsafe { 131 | *(sptr::from_exposed_addr(boot_params + LINUX_SETUP_HEADER_OFFSET + CMD_LINE_PTR_OFFSET)) 132 | }; 133 | let cmdline_size: u32 = unsafe { 134 | *(sptr::from_exposed_addr(boot_params + LINUX_SETUP_HEADER_OFFSET + CMD_LINE_SIZE_OFFSET)) 135 | }; 136 | 137 | let command_line = if cmdline_size > 0 { 138 | // Identity-map the command line. 139 | let page_address = (cmdline_ptr as usize).align_down(Size4KiB::SIZE as usize); 140 | paging::map::(page_address, page_address, 1, PageTableFlags::empty()); 141 | 142 | info!("Found command line at {cmdline_ptr:#x}"); 143 | let slice = unsafe { 144 | core::slice::from_raw_parts( 145 | sptr::from_exposed_addr(cmdline_ptr), 146 | cmdline_size.try_into().unwrap(), 147 | ) 148 | }; 149 | 150 | let s = core::str::from_utf8(slice) 151 | .unwrap() 152 | .strip_suffix('\0') 153 | .unwrap(); 154 | 155 | if s.is_empty() { None } else { Some(s) } 156 | } else { 157 | None 158 | }; 159 | 160 | // map stack in the address space 161 | paging::map::( 162 | new_stack, 163 | new_stack, 164 | KERNEL_STACK_SIZE as usize / Size4KiB::SIZE as usize, 165 | PageTableFlags::WRITABLE, 166 | ); 167 | 168 | let stack = ptr::addr_of_mut!(loader_end).with_addr(new_stack); 169 | 170 | // clear stack 171 | unsafe { 172 | write_bytes(stack, 0, KERNEL_STACK_SIZE.try_into().unwrap()); 173 | } 174 | 175 | let mut fdt = Fdt::new("firecracker").unwrap(); 176 | 177 | // Load the boot_param memory-map information 178 | let linux_e820_entries = 179 | unsafe { *(sptr::from_exposed_addr::(boot_params + E820_ENTRIES_OFFSET)) }; 180 | info!("Number of e820-entries: {linux_e820_entries}"); 181 | 182 | let mut found_entry = false; 183 | let mut start_address: usize = 0; 184 | let mut end_address: usize = 0; 185 | 186 | let e820_entries_address = unsafe { boot_params } + E820_TABLE_OFFSET; 187 | 188 | for index in 0..linux_e820_entries { 189 | found_entry = true; 190 | 191 | //20: Size of one e820-Entry 192 | let entry_address = e820_entries_address + (index as usize) * 20; 193 | let entry_start = unsafe { sptr::from_exposed_addr::(entry_address).read_unaligned() }; 194 | let entry_size = 195 | unsafe { sptr::from_exposed_addr::(entry_address + 8).read_unaligned() }; 196 | let entry_type: u32 = unsafe { sptr::from_exposed_addr::(entry_address + 16).read() }; 197 | 198 | info!( 199 | "e820-Entry with index {index}: Address {entry_start:#x}, Size {entry_size:#x}, Type {entry_type:#x}" 200 | ); 201 | 202 | let entry_end = entry_start + entry_size; 203 | 204 | fdt = fdt.memory(entry_start..entry_end).unwrap(); 205 | 206 | if start_address == 0 { 207 | start_address = entry_start as usize; 208 | } 209 | 210 | if entry_end as usize > end_address { 211 | end_address = entry_end as usize; 212 | } 213 | } 214 | 215 | // Identity-map the start of RAM 216 | assert!(found_entry, "Could not find any free RAM areas!"); 217 | 218 | info!("Found available RAM: [{start_address:#x} - {end_address:#x}]"); 219 | 220 | if let Some(command_line) = command_line { 221 | fdt = fdt.bootargs(command_line.to_owned()).unwrap(); 222 | } 223 | 224 | let fdt = fdt.finish().unwrap(); 225 | 226 | let device_tree = 227 | DeviceTreeAddress::new(u64::try_from(fdt.leak().as_ptr().expose_addr()).unwrap()); 228 | 229 | let boot_info = BootInfo { 230 | hardware_info: HardwareInfo { 231 | phys_addr_range: start_address as u64..end_address as u64, 232 | serial_port_base: SerialPortBase::new(SERIAL_IO_PORT), 233 | device_tree, 234 | }, 235 | load_info, 236 | platform_info: PlatformInfo::LinuxBootParams { 237 | command_line, 238 | boot_params_addr: (unsafe { boot_params } as u64).try_into().unwrap(), 239 | }, 240 | }; 241 | 242 | let entry = sptr::from_exposed_addr(entry_point.try_into().unwrap()); 243 | let raw_boot_info = boot_info.write(); 244 | 245 | unsafe { super::enter_kernel(stack, entry, raw_boot_info) } 246 | } 247 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aarch64-cpu" 7 | version = "11.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "44171e22925ec72b63d86747bc3655c7849a5b8d865c980222128839f45ac034" 10 | dependencies = [ 11 | "tock-registers", 12 | ] 13 | 14 | [[package]] 15 | name = "ahash" 16 | version = "0.8.12" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 19 | dependencies = [ 20 | "cfg-if", 21 | "once_cell", 22 | "version_check", 23 | "zerocopy", 24 | ] 25 | 26 | [[package]] 27 | name = "align-address" 28 | version = "0.3.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4ac6c08a67736554282858203cd9b7ff53cf55f54c34e85689962748a350cbf0" 31 | 32 | [[package]] 33 | name = "allocator-api2" 34 | version = "0.2.21" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 37 | 38 | [[package]] 39 | name = "allocator-api2" 40 | version = "0.4.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.20" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "is_terminal_polyfill", 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle" 61 | version = "1.0.13" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 64 | 65 | [[package]] 66 | name = "anstyle-parse" 67 | version = "0.2.7" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 70 | dependencies = [ 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-query" 76 | version = "1.1.4" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 79 | dependencies = [ 80 | "windows-sys 0.60.2", 81 | ] 82 | 83 | [[package]] 84 | name = "anstyle-wincon" 85 | version = "3.0.10" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 88 | dependencies = [ 89 | "anstyle", 90 | "once_cell_polyfill", 91 | "windows-sys 0.60.2", 92 | ] 93 | 94 | [[package]] 95 | name = "anyhow" 96 | version = "1.0.100" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 99 | 100 | [[package]] 101 | name = "autocfg" 102 | version = "1.5.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 105 | 106 | [[package]] 107 | name = "base64" 108 | version = "0.22.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 111 | 112 | [[package]] 113 | name = "bit_field" 114 | version = "0.10.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" 117 | 118 | [[package]] 119 | name = "bitflags" 120 | version = "1.3.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 123 | 124 | [[package]] 125 | name = "bitflags" 126 | version = "2.9.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 129 | 130 | [[package]] 131 | name = "block-buffer" 132 | version = "0.10.4" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 135 | dependencies = [ 136 | "generic-array", 137 | ] 138 | 139 | [[package]] 140 | name = "byteorder" 141 | version = "1.5.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 144 | 145 | [[package]] 146 | name = "bytes" 147 | version = "1.10.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 150 | 151 | [[package]] 152 | name = "call-once" 153 | version = "0.1.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "3a57a50948117a233b27f9bf73ab74709ab90d245216c4707cc16eea067a50bb" 156 | 157 | [[package]] 158 | name = "cc" 159 | version = "1.2.37" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" 162 | dependencies = [ 163 | "find-msvc-tools", 164 | "shlex", 165 | ] 166 | 167 | [[package]] 168 | name = "cfg-if" 169 | version = "1.0.4" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 172 | 173 | [[package]] 174 | name = "clap" 175 | version = "4.5.53" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 178 | dependencies = [ 179 | "clap_builder", 180 | "clap_derive", 181 | ] 182 | 183 | [[package]] 184 | name = "clap_builder" 185 | version = "4.5.53" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 188 | dependencies = [ 189 | "anstream", 190 | "anstyle", 191 | "clap_lex", 192 | "strsim", 193 | ] 194 | 195 | [[package]] 196 | name = "clap_derive" 197 | version = "4.5.49" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 200 | dependencies = [ 201 | "heck", 202 | "proc-macro2", 203 | "quote", 204 | "syn", 205 | ] 206 | 207 | [[package]] 208 | name = "clap_lex" 209 | version = "0.7.5" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 212 | 213 | [[package]] 214 | name = "colorchoice" 215 | version = "1.0.4" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 218 | 219 | [[package]] 220 | name = "const_fn" 221 | version = "0.4.11" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "2f8a2ca5ac02d09563609681103aada9e1777d54fc57a5acd7a41404f9c93b6e" 224 | 225 | [[package]] 226 | name = "const_parse" 227 | version = "1.0.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "f33a5ebcb58fc85ce4a836200bf7a2b6a86edf36000e4698bb97ac0b53955bb6" 230 | 231 | [[package]] 232 | name = "cpufeatures" 233 | version = "0.2.17" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 236 | dependencies = [ 237 | "libc", 238 | ] 239 | 240 | [[package]] 241 | name = "crc" 242 | version = "3.3.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" 245 | dependencies = [ 246 | "crc-catalog", 247 | ] 248 | 249 | [[package]] 250 | name = "crc-catalog" 251 | version = "2.4.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 254 | 255 | [[package]] 256 | name = "crypto-common" 257 | version = "0.1.6" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 260 | dependencies = [ 261 | "generic-array", 262 | "typenum", 263 | ] 264 | 265 | [[package]] 266 | name = "deranged" 267 | version = "0.5.3" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" 270 | dependencies = [ 271 | "powerfmt", 272 | ] 273 | 274 | [[package]] 275 | name = "digest" 276 | version = "0.10.7" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 279 | dependencies = [ 280 | "block-buffer", 281 | "crypto-common", 282 | ] 283 | 284 | [[package]] 285 | name = "enum_dispatch" 286 | version = "0.3.13" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" 289 | dependencies = [ 290 | "once_cell", 291 | "proc-macro2", 292 | "quote", 293 | "syn", 294 | ] 295 | 296 | [[package]] 297 | name = "errno" 298 | version = "0.3.14" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 301 | dependencies = [ 302 | "libc", 303 | "windows-sys 0.61.0", 304 | ] 305 | 306 | [[package]] 307 | name = "fdt" 308 | version = "0.1.5" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" 311 | 312 | [[package]] 313 | name = "filetime" 314 | version = "0.2.26" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" 317 | dependencies = [ 318 | "cfg-if", 319 | "libc", 320 | "libredox", 321 | "windows-sys 0.60.2", 322 | ] 323 | 324 | [[package]] 325 | name = "find-msvc-tools" 326 | version = "0.1.1" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" 329 | 330 | [[package]] 331 | name = "fnv" 332 | version = "1.0.7" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 335 | 336 | [[package]] 337 | name = "generic-array" 338 | version = "0.14.7" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 341 | dependencies = [ 342 | "typenum", 343 | "version_check", 344 | ] 345 | 346 | [[package]] 347 | name = "getrandom" 348 | version = "0.2.16" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 351 | dependencies = [ 352 | "cfg-if", 353 | "libc", 354 | "wasi", 355 | ] 356 | 357 | [[package]] 358 | name = "goblin" 359 | version = "0.10.4" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "4db6758c546e6f81f265638c980e5e84dfbda80cfd8e89e02f83454c8e8124bd" 362 | dependencies = [ 363 | "plain", 364 | "scroll", 365 | ] 366 | 367 | [[package]] 368 | name = "hashbrown" 369 | version = "0.14.5" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 372 | dependencies = [ 373 | "ahash", 374 | "allocator-api2 0.2.21", 375 | ] 376 | 377 | [[package]] 378 | name = "heck" 379 | version = "0.5.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 382 | 383 | [[package]] 384 | name = "hermit-entry" 385 | version = "0.10.7" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "920f9bfe21a23408aade84f1cd2f550c0490be1ec90ae5151b919614808e2e85" 388 | dependencies = [ 389 | "align-address", 390 | "const_parse", 391 | "goblin", 392 | "log", 393 | "plain", 394 | "time", 395 | ] 396 | 397 | [[package]] 398 | name = "hermit-loader" 399 | version = "0.5.6" 400 | dependencies = [ 401 | "aarch64-cpu", 402 | "align-address", 403 | "allocator-api2 0.4.0", 404 | "anstyle", 405 | "cfg-if", 406 | "enum_dispatch", 407 | "fdt", 408 | "goblin", 409 | "hermit-entry", 410 | "log", 411 | "multiboot", 412 | "naked-function", 413 | "one-shot-mutex", 414 | "sbi-rt", 415 | "sptr", 416 | "take-static", 417 | "uart_16550", 418 | "uefi", 419 | "vm-fdt", 420 | "volatile 0.6.1", 421 | "x86_64", 422 | ] 423 | 424 | [[package]] 425 | name = "http" 426 | version = "1.3.1" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 429 | dependencies = [ 430 | "bytes", 431 | "fnv", 432 | "itoa", 433 | ] 434 | 435 | [[package]] 436 | name = "httparse" 437 | version = "1.10.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 440 | 441 | [[package]] 442 | name = "is_terminal_polyfill" 443 | version = "1.70.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 446 | 447 | [[package]] 448 | name = "itoa" 449 | version = "1.0.15" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 452 | 453 | [[package]] 454 | name = "libc" 455 | version = "0.2.175" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 458 | 459 | [[package]] 460 | name = "libredox" 461 | version = "0.1.10" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 464 | dependencies = [ 465 | "bitflags 2.9.4", 466 | "libc", 467 | "redox_syscall", 468 | ] 469 | 470 | [[package]] 471 | name = "linux-raw-sys" 472 | version = "0.11.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 475 | 476 | [[package]] 477 | name = "llvm-tools" 478 | version = "0.1.1" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" 481 | 482 | [[package]] 483 | name = "lock_api" 484 | version = "0.4.13" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 487 | dependencies = [ 488 | "autocfg", 489 | "scopeguard", 490 | ] 491 | 492 | [[package]] 493 | name = "log" 494 | version = "0.4.29" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 497 | 498 | [[package]] 499 | name = "lzma-rs" 500 | version = "0.3.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" 503 | dependencies = [ 504 | "byteorder", 505 | "crc", 506 | ] 507 | 508 | [[package]] 509 | name = "memchr" 510 | version = "2.7.5" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 513 | 514 | [[package]] 515 | name = "multiboot" 516 | version = "0.8.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "f87ad3b7b7bcf5da525c22221e3eb3a020cd68b2d55ae62f629c15e8bc3bd56e" 519 | dependencies = [ 520 | "paste", 521 | ] 522 | 523 | [[package]] 524 | name = "naked-function" 525 | version = "0.1.5" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "3b8d5fca6ab1e6215b010aefd3b9ac5aae369dae0faea3a7f34f296cc9f719ac" 528 | dependencies = [ 529 | "cfg-if", 530 | "naked-function-macro", 531 | ] 532 | 533 | [[package]] 534 | name = "naked-function-macro" 535 | version = "0.1.5" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "5b4123e70df5fe0bb370cff166ae453b9c5324a2cfc932c0f7e55498147a0475" 538 | dependencies = [ 539 | "proc-macro2", 540 | "quote", 541 | "syn", 542 | ] 543 | 544 | [[package]] 545 | name = "ntapi" 546 | version = "0.4.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 549 | dependencies = [ 550 | "winapi", 551 | ] 552 | 553 | [[package]] 554 | name = "num-conv" 555 | version = "0.1.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 558 | 559 | [[package]] 560 | name = "objc2-core-foundation" 561 | version = "0.3.1" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" 564 | dependencies = [ 565 | "bitflags 2.9.4", 566 | ] 567 | 568 | [[package]] 569 | name = "objc2-io-kit" 570 | version = "0.3.1" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" 573 | dependencies = [ 574 | "libc", 575 | "objc2-core-foundation", 576 | ] 577 | 578 | [[package]] 579 | name = "once_cell" 580 | version = "1.21.3" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 583 | 584 | [[package]] 585 | name = "once_cell_polyfill" 586 | version = "1.70.1" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 589 | 590 | [[package]] 591 | name = "one-shot-mutex" 592 | version = "0.2.1" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "cbb10614a03e671fcbb7f1421656788f9a761aab44563b83b07140c354fa9334" 595 | dependencies = [ 596 | "lock_api", 597 | ] 598 | 599 | [[package]] 600 | name = "ovmf-prebuilt" 601 | version = "0.2.5" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "b0546cdad449ad5969182f02bfa1cac24fcd63b825ba2060dfde13698d7ef1e0" 604 | dependencies = [ 605 | "log", 606 | "lzma-rs", 607 | "sha2", 608 | "tar", 609 | "ureq", 610 | ] 611 | 612 | [[package]] 613 | name = "paste" 614 | version = "1.0.15" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 617 | 618 | [[package]] 619 | name = "percent-encoding" 620 | version = "2.3.2" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 623 | 624 | [[package]] 625 | name = "plain" 626 | version = "0.2.3" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 629 | 630 | [[package]] 631 | name = "powerfmt" 632 | version = "0.2.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 635 | 636 | [[package]] 637 | name = "proc-macro2" 638 | version = "1.0.101" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 641 | dependencies = [ 642 | "unicode-ident", 643 | ] 644 | 645 | [[package]] 646 | name = "ptr_meta" 647 | version = "0.3.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" 650 | dependencies = [ 651 | "ptr_meta_derive", 652 | ] 653 | 654 | [[package]] 655 | name = "ptr_meta_derive" 656 | version = "0.3.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" 659 | dependencies = [ 660 | "proc-macro2", 661 | "quote", 662 | "syn", 663 | ] 664 | 665 | [[package]] 666 | name = "qemu-exit" 667 | version = "3.0.2" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "8bb0fd6580eeed0103c054e3fba2c2618ff476943762f28a645b63b8692b21c9" 670 | 671 | [[package]] 672 | name = "quote" 673 | version = "1.0.40" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 676 | dependencies = [ 677 | "proc-macro2", 678 | ] 679 | 680 | [[package]] 681 | name = "raw-cpuid" 682 | version = "10.7.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" 685 | dependencies = [ 686 | "bitflags 1.3.2", 687 | ] 688 | 689 | [[package]] 690 | name = "redox_syscall" 691 | version = "0.5.17" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 694 | dependencies = [ 695 | "bitflags 2.9.4", 696 | ] 697 | 698 | [[package]] 699 | name = "ring" 700 | version = "0.17.14" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 703 | dependencies = [ 704 | "cc", 705 | "cfg-if", 706 | "getrandom", 707 | "libc", 708 | "untrusted", 709 | "windows-sys 0.52.0", 710 | ] 711 | 712 | [[package]] 713 | name = "rustix" 714 | version = "1.1.2" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 717 | dependencies = [ 718 | "bitflags 2.9.4", 719 | "errno", 720 | "libc", 721 | "linux-raw-sys", 722 | "windows-sys 0.61.0", 723 | ] 724 | 725 | [[package]] 726 | name = "rustls" 727 | version = "0.23.31" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" 730 | dependencies = [ 731 | "log", 732 | "once_cell", 733 | "ring", 734 | "rustls-pki-types", 735 | "rustls-webpki", 736 | "subtle", 737 | "zeroize", 738 | ] 739 | 740 | [[package]] 741 | name = "rustls-pemfile" 742 | version = "2.2.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 745 | dependencies = [ 746 | "rustls-pki-types", 747 | ] 748 | 749 | [[package]] 750 | name = "rustls-pki-types" 751 | version = "1.12.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 754 | dependencies = [ 755 | "zeroize", 756 | ] 757 | 758 | [[package]] 759 | name = "rustls-webpki" 760 | version = "0.103.6" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" 763 | dependencies = [ 764 | "ring", 765 | "rustls-pki-types", 766 | "untrusted", 767 | ] 768 | 769 | [[package]] 770 | name = "rustversion" 771 | version = "1.0.22" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 774 | 775 | [[package]] 776 | name = "sbi-rt" 777 | version = "0.0.3" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "7fbaa69be1eedc61c426e6d489b2260482e928b465360576900d52d496a58bd0" 780 | dependencies = [ 781 | "sbi-spec", 782 | ] 783 | 784 | [[package]] 785 | name = "sbi-spec" 786 | version = "0.0.7" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "e6e36312fb5ddc10d08ecdc65187402baba4ac34585cb9d1b78522ae2358d890" 789 | 790 | [[package]] 791 | name = "scopeguard" 792 | version = "1.2.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 795 | 796 | [[package]] 797 | name = "scroll" 798 | version = "0.13.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" 801 | 802 | [[package]] 803 | name = "sha2" 804 | version = "0.10.9" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 807 | dependencies = [ 808 | "cfg-if", 809 | "cpufeatures", 810 | "digest", 811 | ] 812 | 813 | [[package]] 814 | name = "shlex" 815 | version = "1.3.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 818 | 819 | [[package]] 820 | name = "sptr" 821 | version = "0.3.2" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" 824 | 825 | [[package]] 826 | name = "strsim" 827 | version = "0.11.1" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 830 | 831 | [[package]] 832 | name = "subtle" 833 | version = "2.6.1" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 836 | 837 | [[package]] 838 | name = "syn" 839 | version = "2.0.106" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 842 | dependencies = [ 843 | "proc-macro2", 844 | "quote", 845 | "unicode-ident", 846 | ] 847 | 848 | [[package]] 849 | name = "sysinfo" 850 | version = "0.37.2" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" 853 | dependencies = [ 854 | "libc", 855 | "memchr", 856 | "ntapi", 857 | "objc2-core-foundation", 858 | "objc2-io-kit", 859 | "windows", 860 | ] 861 | 862 | [[package]] 863 | name = "take-static" 864 | version = "0.1.2" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "cf67eb0a80961fdb5df5e14b75c5b9fc93bdbf88b00d0260b7eec55041c4fe01" 867 | dependencies = [ 868 | "call-once", 869 | ] 870 | 871 | [[package]] 872 | name = "tar" 873 | version = "0.4.44" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" 876 | dependencies = [ 877 | "filetime", 878 | "libc", 879 | "xattr", 880 | ] 881 | 882 | [[package]] 883 | name = "time" 884 | version = "0.3.43" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" 887 | dependencies = [ 888 | "deranged", 889 | "num-conv", 890 | "powerfmt", 891 | "time-core", 892 | ] 893 | 894 | [[package]] 895 | name = "time-core" 896 | version = "0.1.6" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 899 | 900 | [[package]] 901 | name = "tock-registers" 902 | version = "0.10.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "b0293f99756f16ff352cc78c99673766a305bdb5ed7652e78df649e9967c885a" 905 | 906 | [[package]] 907 | name = "typenum" 908 | version = "1.18.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 911 | 912 | [[package]] 913 | name = "uart_16550" 914 | version = "0.4.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "94d293f51425981fdb1b766beae254dbb711a17e8c4b549dc69b9b7ee0d478d5" 917 | dependencies = [ 918 | "bitflags 2.9.4", 919 | "rustversion", 920 | "x86", 921 | ] 922 | 923 | [[package]] 924 | name = "ucs2" 925 | version = "0.3.3" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "df79298e11f316400c57ec268f3c2c29ac3c4d4777687955cd3d4f3a35ce7eba" 928 | dependencies = [ 929 | "bit_field", 930 | ] 931 | 932 | [[package]] 933 | name = "uefi" 934 | version = "0.36.1" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "71fe9058b73ee2b6559524af9e33199c13b2485ddbf3ad1181b68051cdc50c17" 937 | dependencies = [ 938 | "bitflags 2.9.4", 939 | "cfg-if", 940 | "log", 941 | "ptr_meta", 942 | "qemu-exit", 943 | "ucs2", 944 | "uefi-macros", 945 | "uefi-raw", 946 | "uguid", 947 | ] 948 | 949 | [[package]] 950 | name = "uefi-macros" 951 | version = "0.19.0" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "4687412b5ac74d245d5bfb1733ede50c31be19bf8a4b6a967a29b451bab49e67" 954 | dependencies = [ 955 | "proc-macro2", 956 | "quote", 957 | "syn", 958 | ] 959 | 960 | [[package]] 961 | name = "uefi-raw" 962 | version = "0.13.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "7f64fe59e11af447d12fd60a403c74106eb104309f34b4c6dbce6e927d97da9d" 965 | dependencies = [ 966 | "bitflags 2.9.4", 967 | "uguid", 968 | ] 969 | 970 | [[package]] 971 | name = "uguid" 972 | version = "2.2.1" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c" 975 | 976 | [[package]] 977 | name = "unicode-ident" 978 | version = "1.0.19" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 981 | 982 | [[package]] 983 | name = "untrusted" 984 | version = "0.9.0" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 987 | 988 | [[package]] 989 | name = "ureq" 990 | version = "3.1.2" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537" 993 | dependencies = [ 994 | "base64", 995 | "log", 996 | "percent-encoding", 997 | "rustls", 998 | "rustls-pemfile", 999 | "rustls-pki-types", 1000 | "ureq-proto", 1001 | "utf-8", 1002 | "webpki-roots", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "ureq-proto" 1007 | version = "0.5.2" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" 1010 | dependencies = [ 1011 | "base64", 1012 | "http", 1013 | "httparse", 1014 | "log", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "utf-8" 1019 | version = "0.7.6" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1022 | 1023 | [[package]] 1024 | name = "utf8parse" 1025 | version = "0.2.2" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1028 | 1029 | [[package]] 1030 | name = "version_check" 1031 | version = "0.9.5" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1034 | 1035 | [[package]] 1036 | name = "vm-fdt" 1037 | version = "0.3.0" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" 1040 | dependencies = [ 1041 | "hashbrown", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "volatile" 1046 | version = "0.4.6" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" 1049 | 1050 | [[package]] 1051 | name = "volatile" 1052 | version = "0.6.1" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "af8ca9a5d4debca0633e697c88269395493cebf2e10db21ca2dbde37c1356452" 1055 | dependencies = [ 1056 | "volatile-macro", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "volatile-macro" 1061 | version = "0.6.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "65c67ce935f3b4329e473ecaff7bab444fcdc3d1d19f8bae61fabfa90b84f93e" 1064 | dependencies = [ 1065 | "proc-macro2", 1066 | "quote", 1067 | "syn", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "wasi" 1072 | version = "0.11.1+wasi-snapshot-preview1" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1075 | 1076 | [[package]] 1077 | name = "webpki-roots" 1078 | version = "1.0.2" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" 1081 | dependencies = [ 1082 | "rustls-pki-types", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "winapi" 1087 | version = "0.3.9" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1090 | dependencies = [ 1091 | "winapi-i686-pc-windows-gnu", 1092 | "winapi-x86_64-pc-windows-gnu", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "winapi-i686-pc-windows-gnu" 1097 | version = "0.4.0" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1100 | 1101 | [[package]] 1102 | name = "winapi-x86_64-pc-windows-gnu" 1103 | version = "0.4.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1106 | 1107 | [[package]] 1108 | name = "windows" 1109 | version = "0.61.3" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 1112 | dependencies = [ 1113 | "windows-collections", 1114 | "windows-core", 1115 | "windows-future", 1116 | "windows-link 0.1.3", 1117 | "windows-numerics", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "windows-collections" 1122 | version = "0.2.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 1125 | dependencies = [ 1126 | "windows-core", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "windows-core" 1131 | version = "0.61.2" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 1134 | dependencies = [ 1135 | "windows-implement", 1136 | "windows-interface", 1137 | "windows-link 0.1.3", 1138 | "windows-result", 1139 | "windows-strings", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "windows-future" 1144 | version = "0.2.1" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 1147 | dependencies = [ 1148 | "windows-core", 1149 | "windows-link 0.1.3", 1150 | "windows-threading", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "windows-implement" 1155 | version = "0.60.0" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 1158 | dependencies = [ 1159 | "proc-macro2", 1160 | "quote", 1161 | "syn", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "windows-interface" 1166 | version = "0.59.1" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 1169 | dependencies = [ 1170 | "proc-macro2", 1171 | "quote", 1172 | "syn", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "windows-link" 1177 | version = "0.1.3" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 1180 | 1181 | [[package]] 1182 | name = "windows-link" 1183 | version = "0.2.0" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 1186 | 1187 | [[package]] 1188 | name = "windows-numerics" 1189 | version = "0.2.0" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 1192 | dependencies = [ 1193 | "windows-core", 1194 | "windows-link 0.1.3", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "windows-result" 1199 | version = "0.3.4" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 1202 | dependencies = [ 1203 | "windows-link 0.1.3", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "windows-strings" 1208 | version = "0.4.2" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 1211 | dependencies = [ 1212 | "windows-link 0.1.3", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "windows-sys" 1217 | version = "0.52.0" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1220 | dependencies = [ 1221 | "windows-targets 0.52.6", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "windows-sys" 1226 | version = "0.60.2" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1229 | dependencies = [ 1230 | "windows-targets 0.53.3", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "windows-sys" 1235 | version = "0.61.0" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" 1238 | dependencies = [ 1239 | "windows-link 0.2.0", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "windows-targets" 1244 | version = "0.52.6" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1247 | dependencies = [ 1248 | "windows_aarch64_gnullvm 0.52.6", 1249 | "windows_aarch64_msvc 0.52.6", 1250 | "windows_i686_gnu 0.52.6", 1251 | "windows_i686_gnullvm 0.52.6", 1252 | "windows_i686_msvc 0.52.6", 1253 | "windows_x86_64_gnu 0.52.6", 1254 | "windows_x86_64_gnullvm 0.52.6", 1255 | "windows_x86_64_msvc 0.52.6", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "windows-targets" 1260 | version = "0.53.3" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 1263 | dependencies = [ 1264 | "windows-link 0.1.3", 1265 | "windows_aarch64_gnullvm 0.53.0", 1266 | "windows_aarch64_msvc 0.53.0", 1267 | "windows_i686_gnu 0.53.0", 1268 | "windows_i686_gnullvm 0.53.0", 1269 | "windows_i686_msvc 0.53.0", 1270 | "windows_x86_64_gnu 0.53.0", 1271 | "windows_x86_64_gnullvm 0.53.0", 1272 | "windows_x86_64_msvc 0.53.0", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "windows-threading" 1277 | version = "0.1.0" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 1280 | dependencies = [ 1281 | "windows-link 0.1.3", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "windows_aarch64_gnullvm" 1286 | version = "0.52.6" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1289 | 1290 | [[package]] 1291 | name = "windows_aarch64_gnullvm" 1292 | version = "0.53.0" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1295 | 1296 | [[package]] 1297 | name = "windows_aarch64_msvc" 1298 | version = "0.52.6" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1301 | 1302 | [[package]] 1303 | name = "windows_aarch64_msvc" 1304 | version = "0.53.0" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1307 | 1308 | [[package]] 1309 | name = "windows_i686_gnu" 1310 | version = "0.52.6" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1313 | 1314 | [[package]] 1315 | name = "windows_i686_gnu" 1316 | version = "0.53.0" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1319 | 1320 | [[package]] 1321 | name = "windows_i686_gnullvm" 1322 | version = "0.52.6" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1325 | 1326 | [[package]] 1327 | name = "windows_i686_gnullvm" 1328 | version = "0.53.0" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1331 | 1332 | [[package]] 1333 | name = "windows_i686_msvc" 1334 | version = "0.52.6" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1337 | 1338 | [[package]] 1339 | name = "windows_i686_msvc" 1340 | version = "0.53.0" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1343 | 1344 | [[package]] 1345 | name = "windows_x86_64_gnu" 1346 | version = "0.52.6" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1349 | 1350 | [[package]] 1351 | name = "windows_x86_64_gnu" 1352 | version = "0.53.0" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1355 | 1356 | [[package]] 1357 | name = "windows_x86_64_gnullvm" 1358 | version = "0.52.6" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1361 | 1362 | [[package]] 1363 | name = "windows_x86_64_gnullvm" 1364 | version = "0.53.0" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1367 | 1368 | [[package]] 1369 | name = "windows_x86_64_msvc" 1370 | version = "0.52.6" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1373 | 1374 | [[package]] 1375 | name = "windows_x86_64_msvc" 1376 | version = "0.53.0" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1379 | 1380 | [[package]] 1381 | name = "x86" 1382 | version = "0.52.0" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" 1385 | dependencies = [ 1386 | "bit_field", 1387 | "bitflags 1.3.2", 1388 | "raw-cpuid", 1389 | ] 1390 | 1391 | [[package]] 1392 | name = "x86_64" 1393 | version = "0.15.4" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "f7841fa0098ceb15c567d93d3fae292c49e10a7662b4936d5f6a9728594555ba" 1396 | dependencies = [ 1397 | "bit_field", 1398 | "bitflags 2.9.4", 1399 | "const_fn", 1400 | "rustversion", 1401 | "volatile 0.4.6", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "xattr" 1406 | version = "1.5.1" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" 1409 | dependencies = [ 1410 | "libc", 1411 | "rustix", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "xshell" 1416 | version = "0.2.7" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" 1419 | dependencies = [ 1420 | "xshell-macros", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "xshell-macros" 1425 | version = "0.2.7" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" 1428 | 1429 | [[package]] 1430 | name = "xtask" 1431 | version = "0.1.0" 1432 | dependencies = [ 1433 | "anyhow", 1434 | "clap", 1435 | "llvm-tools", 1436 | "ovmf-prebuilt", 1437 | "sysinfo", 1438 | "xshell", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "zerocopy" 1443 | version = "0.8.27" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 1446 | dependencies = [ 1447 | "zerocopy-derive", 1448 | ] 1449 | 1450 | [[package]] 1451 | name = "zerocopy-derive" 1452 | version = "0.8.27" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 1455 | dependencies = [ 1456 | "proc-macro2", 1457 | "quote", 1458 | "syn", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "zeroize" 1463 | version = "1.8.1" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1466 | --------------------------------------------------------------------------------