diff --git a/.github/workflows/syscall_test.yml b/.github/workflows/syscall_test.yml index 82ebdb422..2f69546b1 100644 --- a/.github/workflows/syscall_test.yml +++ b/.github/workflows/syscall_test.yml @@ -16,6 +16,12 @@ jobs: - uses: actions/checkout@v3 - - name: Syscall Test - id: syscall_test - run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 + - name: Syscall Test (Multiboot2) + id: syscall_test_mb2 + run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=grub-multiboot2 + + - name: Syscall Test (Linux Boot Protocol) + id: syscall_test_lbp + run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=grub-linux + + # TODO: include the integration tests for Multiboot and MicroVM, which are not ready yet. diff --git a/Cargo.lock b/Cargo.lock index aed6c8df8..74feb13d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,30 +49,29 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -88,9 +87,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", @@ -98,9 +97,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arrayvec" @@ -180,15 +179,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cc" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" -dependencies = [ - "libc", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -197,20 +187,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.21" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", @@ -220,9 +209,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", @@ -232,9 +221,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "colorchoice" @@ -420,27 +409,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "ext-trait" version = "1.0.1" @@ -510,6 +478,12 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hash32" version = "0.2.1" @@ -553,12 +527,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - [[package]] name = "ident_case" version = "1.0.1" @@ -620,17 +588,6 @@ dependencies = [ "ghost", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] - [[package]] name = "itertools" version = "0.10.5" @@ -671,6 +628,8 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "glob", + "rand", ] [[package]] @@ -921,12 +880,6 @@ dependencies = [ "rle-decode-fast", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" - [[package]] name = "lock_api" version = "0.4.10" @@ -1065,6 +1018,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a69ee997a6282f8462abf1e0d8c38c965e968799e912b3bed8c9e8a28c2f9f" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1133,6 +1092,36 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "raw-cpuid" version = "10.7.0" @@ -1175,19 +1164,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" -dependencies = [ - "bitflags 2.3.3", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "rustversion" version = "1.0.14" @@ -1497,9 +1473,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1512,45 +1488,45 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index 4208ebe50..8781c7d06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ jinux-framebuffer = { path = "services/comps/framebuffer" } members = [ "build", "framework/jinux-frame", + "framework/libs/align_ext", "services/comps/virtio", "services/comps/input", "services/comps/block", diff --git a/Makefile b/Makefile index 399b9a0ab..6de05d6c8 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ # Make arguments and their defaults AUTO_SYSCALL_TEST ?= 0 +BOOT_METHOD ?= grub-multiboot2 BUILD_SYSCALL_TEST ?= 0 EMULATE_IOMMU ?= 0 -RUN_MICROVM ?= 0 ENABLE_KVM ?= 1 +GDB_CLIENT ?= 0 +GDB_SERVER ?= 0 INTEL_TDX ?= 0 +SKIP_GRUB_MENU ?= 0 # End of Make arguments KERNEL_CMDLINE := SHELL="/bin/sh" LOGNAME="root" HOME="/" USER="root" PATH="/bin" init=/usr/bin/busybox -- sh -l @@ -16,30 +19,48 @@ CARGO_KBUILD_ARGS := CARGO_KRUN_ARGS := -- '$(KERNEL_CMDLINE)' -ifeq ($(INTEL_TDX), 1) -CARGO_KBUILD_ARGS += --features intel_tdx -CARGO_KRUN_ARGS += --features intel_tdx +ifeq ($(AUTO_SYSCALL_TEST), 1) +BUILD_SYSCALL_TEST := 1 +endif + +CARGO_KRUN_ARGS += --boot-method="$(BOOT_METHOD)" + +ifeq ($(EMULATE_IOMMU), 1) +CARGO_KRUN_ARGS += --emulate-iommu endif ifeq ($(ENABLE_KVM), 1) CARGO_KRUN_ARGS += --enable-kvm endif -ifeq ($(EMULATE_IOMMU), 1) -CARGO_KRUN_ARGS += --emulate-iommu +ifeq ($(GDB_SERVER), 1) +ENABLE_KVM := 0 +CARGO_KRUN_ARGS += --halt-for-gdb endif -ifeq ($(RUN_MICROVM), 1) -CARGO_KRUN_ARGS += --run-microvm +ifeq ($(GDB_CLIENT), 1) +CARGO_KRUN_ARGS += --run-gdb-client endif -ifeq ($(AUTO_SYSCALL_TEST), 1) -BUILD_SYSCALL_TEST := 1 +ifeq ($(INTEL_TDX), 1) +CARGO_KBUILD_ARGS += --features intel_tdx +CARGO_KRUN_ARGS += --features intel_tdx +endif + +ifeq ($(SKIP_GRUB_MENU), 1) +CARGO_KRUN_ARGS += --skip-grub-menu endif # Pass make variables to all subdirectory makes export +export JINUX_BOOT_PROTOCOL=$(BOOT_PROTOCOL) + +# GNU toolchain variables +export AS := as +export CC := gcc +export OBJCOPY := objcopy + .PHONY: all setup build tools run test docs check clean all: build diff --git a/build.rs b/build.rs index eacbf15cd..8ca97eca0 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,27 @@ -use std::error::Error; +use std::{error::Error, path::PathBuf}; fn main() -> Result<(), Box> { - let linker_script_path = "framework/jinux-frame/src/arch/x86/boot/linker.ld"; - println!("cargo:rerun-if-changed={}", linker_script_path); - println!("cargo:rustc-link-arg=-T{}", linker_script_path); + let linker_script_path = get_source_dir() + .join("framework") + .join("jinux-frame") + .join("src") + .join("arch") + .join("x86") + .join("boot") + .join("linker.ld"); + println!( + "cargo:rerun-if-changed={}", + linker_script_path.to_str().unwrap() + ); + println!( + "cargo:rustc-link-arg=-T{}", + linker_script_path.to_str().unwrap() + ); println!("cargo:rerun-if-env-changed=CARGO_PKG_NAME"); - Ok(()) } + +fn get_source_dir() -> PathBuf { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + PathBuf::from(manifest_dir) +} diff --git a/build/Cargo.toml b/build/Cargo.toml index 96acfe5f9..baf5eb10d 100644 --- a/build/Cargo.toml +++ b/build/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] anyhow = "1.0.32" clap = { version = "4.3.19", features = ["derive"] } +glob = "0.3.1" +rand = "0.8.5" diff --git a/build/grub/grub.cfg.template b/build/grub/grub.cfg.template index 37123dde4..bbbda8351 100644 --- a/build/grub/grub.cfg.template +++ b/build/grub/grub.cfg.template @@ -2,11 +2,11 @@ # AUTOMATICALLY GENERATED FILE, DO NOT EDIT IF YOU KNOW WHAT YOU ARE DOING -set timeout_style=menu -set timeout=1 +set timeout_style=#GRUB_TIMEOUT_STYLE# +set timeout=#GRUB_TIMEOUT# menuentry 'jinux' { - multiboot2 /boot/jinux #KERNEL_COMMAND_LINE# - module2 --nounzip /boot/initramfs.cpio.gz + #GRUB_CMD_KERNEL# /boot/jinux #KERNEL_COMMAND_LINE# + #GRUB_CMD_INITRAMFS# /boot/initramfs.cpio.gz boot } diff --git a/build/src/machine/default.rs b/build/src/machine/default.rs index b2dd348bc..31e2fa204 100644 --- a/build/src/machine/default.rs +++ b/build/src/machine/default.rs @@ -1,9 +1,11 @@ use std::{ - fs, + fs::{self, File}, io::{Read, Write}, path::{Path, PathBuf}, }; +use glob::glob; + pub const MACHINE_ARGS: &[&str] = &["-machine", "q35,kernel-irqchip=split"]; pub const NOIOMMU_DEVICE_ARGS: &[&str] = &[ @@ -28,12 +30,23 @@ pub const IOMMU_DEVICE_ARGS: &[&str] = &[ "ioh3420,id=pcie.0,chassis=1", ]; -pub fn create_bootdev_image(path: PathBuf, kcmdline: &str) -> String { +#[derive(Debug, Clone, Copy)] +pub enum GrubBootProtocol { + Multiboot, + Multiboot2, + Linux, +} + +pub fn create_bootdev_image( + path: PathBuf, + grub_cfg: String, + protocol: GrubBootProtocol, +) -> PathBuf { let dir = path.parent().unwrap(); let name = path.file_name().unwrap().to_str().unwrap().to_string(); let iso_path = dir.join(name + ".iso").to_str().unwrap().to_string(); - // Clean up the image directory + // Clean up the image directory. if Path::new("target/iso_root").exists() { fs::remove_dir_all("target/iso_root").unwrap(); } @@ -41,19 +54,38 @@ pub fn create_bootdev_image(path: PathBuf, kcmdline: &str) -> String { // Copy the needed files into an ISO image. fs::create_dir_all("target/iso_root/boot/grub").unwrap(); - fs::copy(path.as_os_str(), "target/iso_root/boot/jinux").unwrap(); - generate_grub_cfg( - "build/grub/grub.cfg.template", - "target/iso_root/boot/grub/grub.cfg", - kcmdline, - ); fs::copy( "regression/build/initramfs.cpio.gz", "target/iso_root/boot/initramfs.cpio.gz", ) .unwrap(); - // Make the boot device .iso image + // Find the setup header in the build script output directory. + let out_dir = glob("target/x86_64-custom/debug/build/jinux-frame-*").unwrap(); + let header_bin = Path::new(out_dir.into_iter().next().unwrap().unwrap().as_path()) + .join("out/linux_header.bin"); + + // Deliver the kernel image to the boot directory. + match protocol { + GrubBootProtocol::Linux => { + // Make the `zimage`-compatible kernel image and place it in the boot directory. + make_zimage( + &Path::new("target/iso_root/boot/jinux"), + &path.as_path(), + &header_bin.as_path(), + ) + .unwrap(); + } + GrubBootProtocol::Multiboot | GrubBootProtocol::Multiboot2 => { + // Copy the kernel image into the boot directory. + fs::copy(&path, "target/iso_root/boot/jinux").unwrap(); + } + } + + // Write the grub.cfg file + fs::write("target/iso_root/boot/grub/grub.cfg", grub_cfg).unwrap(); + + // Make the boot device .iso image. let status = std::process::Command::new("grub-mkrescue") .arg("-o") .arg(&iso_path) @@ -65,24 +97,85 @@ pub fn create_bootdev_image(path: PathBuf, kcmdline: &str) -> String { panic!("Failed to create boot iso image.") } - iso_path + iso_path.into() } -fn generate_grub_cfg(template_filename: &str, target_filename: &str, kcmdline: &str) { +pub fn generate_grub_cfg( + template_filename: &str, + kcmdline: &str, + skip_grub_menu: bool, + protocol: GrubBootProtocol, +) -> String { let mut buffer = String::new(); - // Read the contents of the file + // Read the contents of the file. fs::File::open(template_filename) .unwrap() .read_to_string(&mut buffer) .unwrap(); - // Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value - let replaced_content = buffer.replace("#KERNEL_COMMAND_LINE#", kcmdline); + // Delete the first two lines that notes the file is a template file. + let buffer = buffer.lines().skip(2).collect::>().join("\n"); + // Set the timout style and timeout. + let buffer = buffer + .replace( + "#GRUB_TIMEOUT_STYLE#", + if skip_grub_menu { "hidden" } else { "menu" }, + ) + .replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "1" }); + // Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value. + let buffer = buffer.replace("#KERNEL_COMMAND_LINE#", kcmdline); + // Replace the grub commands according to the protocol selected. + let buffer = match protocol { + GrubBootProtocol::Multiboot => buffer + .replace("#GRUB_CMD_KERNEL#", "multiboot") + .replace("#GRUB_CMD_INITRAMFS#", "module --nounzip"), + GrubBootProtocol::Multiboot2 => buffer + .replace("#GRUB_CMD_KERNEL#", "multiboot2") + .replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"), + GrubBootProtocol::Linux => buffer + .replace("#GRUB_CMD_KERNEL#", "linux") + .replace("#GRUB_CMD_INITRAMFS#", "initrd"), + }; - // Write the modified content back to the file - fs::File::create(target_filename) - .unwrap() - .write_all(replaced_content.as_bytes()) - .unwrap(); + buffer +} + +fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) { + let size = value.len(); + assert_eq!( + &header[offset..offset + size], + vec![0xABu8; size].as_slice() + ); + header[offset..offset + size].copy_from_slice(value); +} + +fn make_zimage(path: &Path, kernel_path: &Path, header_path: &Path) -> std::io::Result<()> { + let mut header = Vec::new(); + File::open(header_path)?.read_to_end(&mut header)?; + // Pad the header to let the payload starts with 8-byte alignment. + header.resize((header.len() + 7) & !7, 0x00); + + let mut kernel = Vec::new(); + File::open(kernel_path)?.read_to_end(&mut kernel)?; + + let header_len = header.len(); + let kernel_len = kernel.len(); + + fill_header_field( + &mut header, + 0x248, /* payload_offset */ + &(header_len as u32).to_le_bytes(), + ); + fill_header_field( + &mut header, + 0x24C, /* payload_length */ + &(kernel_len as u32).to_le_bytes(), + ); + + let mut kernel_image = File::create(path)?; + kernel_image.write_all(&header)?; + kernel_image.write_all(&kernel)?; + + Ok(()) } diff --git a/build/src/machine/microvm.rs b/build/src/machine/microvm.rs index 8359329ac..8c26a19fc 100644 --- a/build/src/machine/microvm.rs +++ b/build/src/machine/microvm.rs @@ -21,7 +21,7 @@ pub const DEVICE_ARGS: &[&str] = &[ "virtio-net-device,netdev=net01", ]; -pub fn create_bootdev_image(path: PathBuf) -> String { +pub fn create_bootdev_image(path: PathBuf) -> PathBuf { let dir = path.parent().unwrap(); let name = path.file_name().unwrap().to_str().unwrap().to_string(); let elf_path = dir.join(name.clone()).to_str().unwrap().to_string(); @@ -70,5 +70,5 @@ pub fn create_bootdev_image(path: PathBuf) -> String { file.write_all(&bytes).unwrap(); file.flush().unwrap(); - strip_elf_path + strip_elf_path.into() } diff --git a/build/src/main.rs b/build/src/main.rs index 20b599ac9..fba55d081 100644 --- a/build/src/main.rs +++ b/build/src/main.rs @@ -1,9 +1,12 @@ -//! jinux-build is the Jinux runner script to ease the pain of running -//! and testing Jinux inside a QEMU VM, which should be called as the +//! jinux-runner is the Jinux runner script to ease the pain of running +//! and testing Jinux inside a QEMU VM. It should be built and run as the //! cargo runner: https://doc.rust-lang.org/cargo/reference/config.html //! -//! The runner generates the the filesystem image and the containing -//! boot device image. Then it invokes QEMU to boot Jinux. +//! The runner will generate the filesystem image for starting Jinux. If +//! we should use the runner in the default mode, which invokes QEMU with +//! a GRUB boot device image, the runner would be responsible for generating +//! the and the boot device image. It also supports directly boot the +//! kernel image without GRUB using the QEMU microvm mode. //! pub mod machine; @@ -31,6 +34,14 @@ struct Args { kcmdline: String, // Optional arguments. + /// Boot method. Can be one of the following items: + /// - `grub-multiboot`, + /// - `grub-multiboot2`, + /// - `grub-linux`, + /// - `microvm-multiboot`. + #[arg(long, default_value = "grub-multiboot2")] + boot_method: String, + /// Enable KVM when running QEMU. #[arg(long, default_value_t = false)] enable_kvm: bool, @@ -39,9 +50,17 @@ struct Args { #[arg(long, default_value_t = false)] emulate_iommu: bool, - /// Run Jinux as microvm mode. + /// Run QEMU as a GDB server. #[arg(long, default_value_t = false)] - run_microvm: bool, + halt_for_gdb: bool, + + /// Boot without displaying the GRUB menu. + #[arg(long, default_value_t = false)] + skip_grub_menu: bool, + + /// Run a GDB client instead of running the kernel. + #[arg(long, default_value_t = false)] + run_gdb_client: bool, } pub const COMMON_ARGS: &[&str] = &[ @@ -59,57 +78,114 @@ pub const COMMON_ARGS: &[&str] = &[ "none", "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", - "-netdev", - "user,id=net01,hostfwd=tcp::30133-:22,hostfwd=tcp::31088-:8080", "-object", "filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap", ]; +pub fn random_hostfwd_netdev_arg() -> String { + let start = 32768u16; + let end = 61000u16; + let port1 = rand::random::() % (end - 1 - start) + start; + let port2 = rand::random::() % (end - port1) + port1; + format!( + "user,id=net01,hostfwd=tcp::{}-:22,hostfwd=tcp::{}-:8080", + port1, port2 + ) +} + +pub const GDB_ARGS: &[&str] = &[ + "-chardev", + "socket,path=/tmp/jinux-gdb-socket,server=on,wait=off,id=gdb0", + "-gdb", + "chardev:gdb0", + "-S", +]; + fn main() { let args = Args::parse(); + if args.run_gdb_client { + let mut gdb_cmd = Command::new("gdb"); + // Adding the debug symbols from the kernel image. + // Alternatively, use "file /usr/lib/grub/i386-pc/boot.image" + // to load symbols from GRUB. + gdb_cmd + .arg("-ex") + .arg(format!("file {}", args.path.display())); + // Set the architecture, otherwise GDB will complain about. + gdb_cmd.arg("-ex").arg("set arch i386:x86-64:intel"); + // Connect to the GDB server. + gdb_cmd + .arg("-ex") + .arg("target remote /tmp/jinux-gdb-socket"); + println!("running:{:#?}", gdb_cmd); + gdb_cmd.status().unwrap(); + return; + } + let mut qemu_cmd = Command::new("qemu-system-x86_64"); - let mut qemu_args = COMMON_ARGS.to_vec(); + qemu_cmd.args(COMMON_ARGS); + + qemu_cmd.arg("-netdev"); + qemu_cmd.arg(random_hostfwd_netdev_arg().as_str()); + + if args.halt_for_gdb { + if args.enable_kvm { + println!("Runner: Can't enable KVM when running QEMU as a GDB server. Abort."); + return; + } + qemu_cmd.args(GDB_ARGS); + } if args.enable_kvm { - qemu_args.push("-enable-kvm"); + qemu_cmd.arg("-enable-kvm"); } // Specify machine type - if args.run_microvm { - qemu_args.extend(microvm::MACHINE_ARGS.to_vec().iter()); + if args.boot_method == "microvm-multiboot" { + qemu_cmd.args(microvm::MACHINE_ARGS); } else { - qemu_args.extend(default::MACHINE_ARGS.to_vec().iter()); + qemu_cmd.args(default::MACHINE_ARGS); } // Add device arguments - if args.run_microvm { - qemu_args.extend(microvm::DEVICE_ARGS.to_vec().iter()) + if args.boot_method == "microvm-multiboot" { + qemu_cmd.args(microvm::DEVICE_ARGS); } else if args.emulate_iommu { - qemu_args.extend(default::IOMMU_DEVICE_ARGS.to_vec().iter()); + qemu_cmd.args(default::IOMMU_DEVICE_ARGS); } else { - qemu_args.extend(default::NOIOMMU_DEVICE_ARGS.to_vec().iter()); + qemu_cmd.args(default::NOIOMMU_DEVICE_ARGS); } let fs_image = create_fs_image(args.path.as_path()); - qemu_args.push("-drive"); - qemu_args.push(fs_image.as_str()); + qemu_cmd.arg("-drive"); + qemu_cmd.arg(fs_image); - if args.run_microvm { + if args.boot_method == "microvm-multiboot" { let image = microvm::create_bootdev_image(args.path); qemu_cmd.arg("-kernel"); - qemu_cmd.arg(image.as_str()); + qemu_cmd.arg(image.as_os_str()); qemu_cmd.arg("-append"); qemu_cmd.arg(&args.kcmdline); qemu_cmd.arg("-initrd"); qemu_cmd.arg("regression/build/initramfs.cpio.gz"); } else { - let bootdev_image = default::create_bootdev_image(args.path, &args.kcmdline); + let boot_protocol = match args.boot_method.as_str() { + "grub-multiboot" => default::GrubBootProtocol::Multiboot, + "grub-multiboot2" => default::GrubBootProtocol::Multiboot2, + "grub-linux" => default::GrubBootProtocol::Linux, + _ => panic!("Unknown boot method: {}", args.boot_method), + }; + let grub_cfg = default::generate_grub_cfg( + "build/grub/grub.cfg.template", + &args.kcmdline, + args.skip_grub_menu, + boot_protocol, + ); + let bootdev_image = default::create_bootdev_image(args.path, grub_cfg, boot_protocol); qemu_cmd.arg("-cdrom"); - qemu_cmd.arg(bootdev_image.as_str()); + qemu_cmd.arg(bootdev_image.as_os_str()); } - qemu_cmd.args(qemu_args); - println!("running:{:#?}", qemu_cmd); let exit_status = qemu_cmd.status().unwrap(); diff --git a/framework/jinux-frame/build.rs b/framework/jinux-frame/build.rs new file mode 100644 index 000000000..6259b1eac --- /dev/null +++ b/framework/jinux-frame/build.rs @@ -0,0 +1,63 @@ +use std::{error::Error, io::Write, path::PathBuf}; + +fn main() -> Result<(), Box> { + build_linux_setup_header()?; + Ok(()) +} + +fn get_source_dir() -> PathBuf { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + PathBuf::from(manifest_dir) +} + +fn get_header_out_dir() -> PathBuf { + PathBuf::from(std::env::var("OUT_DIR").unwrap()) +} + +fn build_linux_setup_header() -> Result<(), Box> { + // Compile the header to raw binary. + let linux_boot_header_asm_path = get_source_dir() + .join("src") + .join("arch") + .join("x86") + .join("boot") + .join("linux_boot") + .join("header.S"); + println!( + "cargo:rerun-if-changed={}", + linux_boot_header_asm_path.to_str().unwrap() + ); + let linux_boot_header_elf_path = get_header_out_dir().join("linux_header.o"); + let gas = std::env::var("AS").unwrap(); + let mut cmd = std::process::Command::new(gas); + cmd.arg(linux_boot_header_asm_path); + cmd.arg("-o") + .arg(linux_boot_header_elf_path.to_str().unwrap()); + let output = cmd.output()?; + if !output.status.success() { + std::io::stdout().write_all(&output.stdout).unwrap(); + std::io::stderr().write_all(&output.stderr).unwrap(); + panic!( + "Failed to compile linux boot header:\n\tcommand `{:?}`\n\treturned {}", + cmd, output.status + ); + } + // Strip the elf header to get the raw header. + let linux_boot_header_bin_path = get_header_out_dir().join("linux_header.bin"); + let objcopy = std::env::var("OBJCOPY").unwrap(); + let mut cmd = std::process::Command::new(objcopy); + cmd.arg("-O").arg("binary"); + cmd.arg("-j").arg(".boot_compatibility_bin"); + cmd.arg(linux_boot_header_elf_path.to_str().unwrap()); + cmd.arg(linux_boot_header_bin_path.to_str().unwrap()); + let output = cmd.output()?; + if !output.status.success() { + std::io::stdout().write_all(&output.stdout).unwrap(); + std::io::stderr().write_all(&output.stderr).unwrap(); + panic!( + "Failed to strip linux boot header:\n\tcommand `{:?}`\n\treturned {}", + cmd, output.status + ); + } + Ok(()) +} diff --git a/framework/jinux-frame/src/arch/x86/boot/boot.S b/framework/jinux-frame/src/arch/x86/boot/boot.S index 6250325fb..e766afd20 100644 --- a/framework/jinux-frame/src/arch/x86/boot/boot.S +++ b/framework/jinux-frame/src/arch/x86/boot/boot.S @@ -4,68 +4,78 @@ .section ".boot", "awx" .code32 -// The entry point of our ELF target. -.global __boot -__boot: - jmp initial_boot_setup +ENTRYTYPE_MULTIBOOT = 1 +ENTRYTYPE_MULTIBOOT2 = 2 +ENTRYTYPE_LINUX_32 = 3 -// This is the GNU Multiboot 2 header. -// Reference: https://www.gnu.org/software/grub/manual/multiboot2/html_node/Index.html//Index - -// Macros for cleaner code in the header fields. -MB2_MAGIC = 0xE85250D6 -MB2_ARCHITECTURE = 0 // 32-bit (protected) mode of i386 -MB2_HEADERLEN = header_end - header_start -MB2_CHECKSUM = -(MB2_MAGIC + MB2_ARCHITECTURE + MB2_HEADERLEN) - -MB_MAGIC = 0x1BADB002 -MB_FLAGS = 0 -MB_CHECKSUM = -(MB_MAGIC + MB_FLAGS) - -header_start: - .align 8 - - .long MB2_MAGIC - .long MB2_ARCHITECTURE - .long MB2_HEADERLEN - .long MB2_CHECKSUM - - // Tag: information request - .align 8 -info_request: - .short 1 - .short 0 // Required - .long info_request_end - info_request - .long 6 // Memory map request - .long 15 // ACPI (new) request -info_request_end: - - // Tag: header end - .align 8 - .short 0 // type: tags end - .short 0 // flags - .long 8 // size -header_end: - -multiboot_header: - .align 8 - .long MB_MAGIC - .long MB_FLAGS - .long MB_CHECKSUM - -initial_boot_setup: +// The Linux 32-bit Boot Protocol entry point. +// Must be located at 0x100000, ABI immutable! +.code32 +.org 0x000 +.global __linux32_boot +__linux32_boot: cli cld // Set the kernel call stack. mov esp, offset boot_stack_top - // Save the multiboot magic and 64-bit physical address of multiboot info onto the stack. - push 0 // Upper 32-bits. - push eax - push 0 // Upper 32-bits. - push ebx + push 0 // upper 32-bits + push esi // boot_params ptr + push 0 // upper 32-bits + push ENTRYTYPE_LINUX_32 + jmp initial_boot_setup + +// The Linux 64-bit Boot Protocol entry point. +// Must be located at 0x100200, ABI immutable! +.code64 +.org 0x200 +.global __linux64_boot_tag +__linux64_boot_tag: + lea rax, [rip + __linux64_boot] // jump into Rust code + call rax + jmp halt // unreachable here + +// The multiboot entry point. +.code32 +.global __multiboot_boot +__multiboot_boot: + cli + cld + + // Set the kernel call stack. + mov esp, offset boot_stack_top + + push 0 // Upper 32-bits. + push eax // multiboot magic ptr + push 0 // Upper 32-bits. + push ebx // multiboot info ptr + push 0 // Upper 32-bits. + push ENTRYTYPE_MULTIBOOT + + jmp initial_boot_setup + +// The multiboot2 entry point. +.code32 +.global __multiboot2_boot +__multiboot2_boot: + cli + cld + + // Set the kernel call stack. + mov esp, offset boot_stack_top + + push 0 // Upper 32-bits. + push eax // multiboot magic ptr + push 0 // Upper 32-bits. + push ebx // multiboot info ptr + push 0 // Upper 32-bits. + push ENTRYTYPE_MULTIBOOT2 + + jmp initial_boot_setup + +initial_boot_setup: // Prepare for far return. We use a far return as a fence after setting GDT. mov eax, 24 push eax @@ -284,17 +294,38 @@ long_mode: cld rep stosb + // Call the corresponding Rust entrypoint according to the boot entrypoint + pop rax + cmp rax, ENTRYTYPE_MULTIBOOT2 + je entry_type_multiboot2 + cmp rax, ENTRYTYPE_LINUX_32 + je entry_type_pvh_elf + // Unreachable! + jmp halt + +entry_type_pvh_elf: + pop rdi // boot_params ptr + + // Clear the frame pointer to stop backtracing here. + xor rbp, rbp + +.extern __linux64_boot + lea rax, [rip + __linux64_boot] // jump into Rust code + call rax + jmp halt + +entry_type_multiboot2: pop rsi // the address of multiboot info pop rdi // multiboot magic // Clear the frame pointer to stop backtracing here. xor rbp, rbp -.extern __boot_entry - lea rax, [rip + __boot_entry] // jump into Rust code +.extern __multiboot2_entry + lea rax, [rip + __multiboot2_entry] // jump into Rust code call rax + jmp halt - // In case boot() returns. halt: cli hlt diff --git a/framework/jinux-frame/src/arch/x86/boot/linker.ld b/framework/jinux-frame/src/arch/x86/boot/linker.ld index ed3735d3f..663c030e3 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linker.ld +++ b/framework/jinux-frame/src/arch/x86/boot/linker.ld @@ -1,4 +1,4 @@ -ENTRY(__boot) +ENTRY(__linux64_boot) OUTPUT_ARCH(i386:x86-64) OUTPUT_FORMAT(elf64-x86-64) @@ -10,7 +10,9 @@ SECTIONS . = KERNEL_LMA; __kernel_start = .; - + + .multiboot_header : { KEEP(*(.multiboot_header)) } + .multiboot2_header : { KEEP(*(.multiboot2_header)) } .boot : { KEEP(*(.boot)) } . += KERNEL_VMA; @@ -47,5 +49,8 @@ SECTIONS __eh_frame_end = .; } + # The notes section are used to mark the PVH boot entry point, useful for QEMU and Xen + .notes : { *(.notes) } + __kernel_end = . - KERNEL_VMA; } diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/boot_params.rs b/framework/jinux-frame/src/arch/x86/boot/linux_boot/boot_params.rs new file mode 100644 index 000000000..c2fc1b417 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/boot_params.rs @@ -0,0 +1,281 @@ +//! The Linux Boot Protocol boot_params module. +//! +//! The bootloader will deliver the address of the `BootParams` struct +//! as the argument of the kernel entrypoint. So we must define a Linux +//! ABI compatible struct in Rust, despite that most of the fields are +//! currently not needed by Jinux. +//! + +#[repr(C, packed)] +pub(super) struct ScreenInfo { + pub(super) orig_x: u8, /* 0x00 */ + pub(super) orig_y: u8, /* 0x01 */ + pub(super) ext_mem_k: u16, /* 0x02 */ + pub(super) orig_video_page: u16, /* 0x04 */ + pub(super) orig_video_mode: u8, /* 0x06 */ + pub(super) orig_video_cols: u8, /* 0x07 */ + pub(super) flags: u8, /* 0x08 */ + pub(super) unused2: u8, /* 0x09 */ + pub(super) orig_video_ega_bx: u16, /* 0x0a */ + pub(super) unused3: u16, /* 0x0c */ + pub(super) orig_video_lines: u8, /* 0x0e */ + pub(super) orig_video_is_vga: u8, /* 0x0f */ + pub(super) orig_video_points: u16, /* 0x10 */ + + /* VESA graphic mode -- linear frame buffer */ + pub(super) lfb_width: u16, /* 0x12 */ + pub(super) lfb_height: u16, /* 0x14 */ + pub(super) lfb_depth: u16, /* 0x16 */ + pub(super) lfb_base: u32, /* 0x18 */ + pub(super) lfb_size: u32, /* 0x1c */ + pub(super) cl_magic: u16, + pub(super) cl_offset: u16, /* 0x20 */ + pub(super) lfb_linelength: u16, /* 0x24 */ + pub(super) red_size: u8, /* 0x26 */ + pub(super) red_pos: u8, /* 0x27 */ + pub(super) green_size: u8, /* 0x28 */ + pub(super) green_pos: u8, /* 0x29 */ + pub(super) blue_size: u8, /* 0x2a */ + pub(super) blue_pos: u8, /* 0x2b */ + pub(super) rsvd_size: u8, /* 0x2c */ + pub(super) rsvd_pos: u8, /* 0x2d */ + pub(super) vesapm_seg: u16, /* 0x2e */ + pub(super) vesapm_off: u16, /* 0x30 */ + pub(super) pages: u16, /* 0x32 */ + pub(super) vesa_attributes: u16, /* 0x34 */ + pub(super) capabilities: u32, /* 0x36 */ + pub(super) ext_lfb_base: u32, /* 0x3a */ + pub(super) _reserved: [u8; 2], /* 0x3e */ +} + +#[repr(C, packed)] +pub(super) struct ApmBiosInfo { + pub(super) version: u16, + pub(super) cseg: u16, + pub(super) offset: u32, + pub(super) cseg_16: u16, + pub(super) dseg: u16, + pub(super) flags: u16, + pub(super) cseg_len: u16, + pub(super) cseg_16_len: u16, + pub(super) dseg_len: u16, +} + +#[repr(C, packed)] +pub(super) struct IstInfo { + pub(super) signature: u32, + pub(super) command: u32, + pub(super) event: u32, + pub(super) perf_level: u32, +} + +#[repr(C, packed)] +pub(super) struct SysDescTable { + pub(super) length: u16, + pub(super) table: [u8; 14], +} + +#[repr(C, packed)] +pub(super) struct OlpcOfwHeader { + pub(super) ofw_magic: u32, /* OFW signature */ + pub(super) ofw_version: u32, + pub(super) cif_handler: u32, /* callback into OFW */ + pub(super) irq_desc_table: u32, +} + +#[repr(C)] +pub(super) struct EdidInfo { + pub(super) dummy: [u8; 128], +} + +#[repr(C)] +pub(super) struct EfiInfo { + pub(super) efi_loader_signature: u32, + pub(super) efi_systab: u32, + pub(super) efi_memdesc_size: u32, + pub(super) efi_memdesc_version: u32, + pub(super) efi_memmap: u32, + pub(super) efi_memmap_size: u32, + pub(super) efi_systab_hi: u32, + pub(super) efi_memmap_hi: u32, +} + +/// Magic stored in SetupHeader.boot_flag. +pub(super) const LINUX_BOOT_FLAG_MAGIC: u16 = 0xAA55; +/// Magic stored in SetupHeader.header. +pub(super) const LINUX_BOOT_HEADER_MAGIC: u32 = 0x53726448; + +/// Linux Boot Protocol Header. +/// +/// Originally defined in the linux source tree: +/// `linux/arch/x86/include/uapi/asm/bootparam.h` +#[repr(C, packed)] +pub(super) struct SetupHeader { + pub(super) setup_sects: u8, + pub(super) root_flags: u16, + pub(super) syssize: u32, + pub(super) ram_size: u16, + pub(super) vid_mode: u16, + pub(super) root_dev: u16, + pub(super) boot_flag: u16, + pub(super) jump: u16, + pub(super) header: u32, + pub(super) version: u16, + pub(super) realmode_swtch: u32, + pub(super) start_sys_seg: u16, + pub(super) kernel_version: u16, + pub(super) type_of_loader: u8, + pub(super) loadflags: u8, + pub(super) setup_move_size: u16, + pub(super) code32_start: u32, + pub(super) ramdisk_image: u32, + pub(super) ramdisk_size: u32, + pub(super) bootsect_kludge: u32, + pub(super) heap_end_ptr: u16, + pub(super) ext_loader_ver: u8, + pub(super) ext_loader_type: u8, + pub(super) cmd_line_ptr: u32, + pub(super) initrd_addr_max: u32, + pub(super) kernel_alignment: u32, + pub(super) relocatable_kernel: u8, + pub(super) min_alignment: u8, + pub(super) xloadflags: u16, + pub(super) cmdline_size: u32, + pub(super) hardware_subarch: u32, + pub(super) hardware_subarch_data: u64, + pub(super) payload_offset: u32, + pub(super) payload_length: u32, + pub(super) setup_data: u64, + pub(super) pref_address: u64, + pub(super) init_size: u32, + pub(super) handover_offset: u32, + pub(super) kernel_info_offset: u32, +} + +/// The E820 types known to the kernel. +/// +/// Originally defined in the linux source tree: +/// `linux/arch/x86/include/asm/e820/types.h` +#[derive(Copy, Clone)] +#[repr(u32)] +pub(super) enum E820Type { + Ram = 1, + Reserved = 2, + Acpi = 3, + Nvs = 4, + Unusable = 5, + Pmem = 7, + /* + * This is a non-standardized way to represent ADR or + * NVDIMM regions that persist over a reboot. + * + * The kernel will ignore their special capabilities + * unless the CONFIG_X86_PMEM_LEGACY=y option is set. + * + * ( Note that older platforms also used 6 for the same + * type of memory, but newer versions switched to 12 as + * 6 was assigned differently. Some time they will learn... ) + */ + Pram = 12, + /* + * Special-purpose memory is indicated to the system via the + * EFI_MEMORY_SP attribute. Define an e820 translation of this + * memory type for the purpose of reserving this range and + * marking it with the IORES_DESC_SOFT_RESERVED designation. + */ + SoftReserved = 0xefffffff, + /* + * Reserved RAM used by the kernel itself if + * CONFIG_INTEL_TXT=y is enabled, memory of this type + * will be included in the S3 integrity calculation + * and so should not include any memory that the BIOS + * might alter over the S3 transition: + */ + ReservedKern = 128, +} + +#[repr(C, packed)] +pub(super) struct BootE820Entry { + pub(super) addr: u64, + pub(super) size: u64, + pub(super) typ: E820Type, +} + +const E820_MAX_ENTRIES_ZEROPAGE: usize = 128; + +#[repr(C, packed)] +pub(super) struct EddDeviceParams { + // TODO: We currently have no plans to support the edd device, + // and we need unnamed fields (Rust RFC 2102) to implement this + // FFI neatly. So we put a dummy implementation here conforming + // to the BootParams struct ABI. + pub(super) _dummy: [u8; (0xeec - 0xd00) / 6 - 8], +} + +#[repr(C, packed)] +pub(super) struct EddInfo { + pub(super) device: u8, + pub(super) version: u8, + pub(super) interface_support: u16, + pub(super) legacy_max_cylinder: u16, + pub(super) legacy_max_head: u8, + pub(super) legacy_sectors_per_track: u8, + pub(super) params: EddDeviceParams, +} + +const EDD_MBR_SIG_MAX: usize = 16; +const EDDMAXNR: usize = 6; + +/// Linux 32/64-bit Boot Protocol parameter struct. +/// +/// Originally defined in the linux source tree: +/// `linux/arch/x86/include/uapi/asm/bootparam.h` +#[repr(C, packed)] +pub(super) struct BootParams { + pub(super) screen_info: ScreenInfo, /* 0x000 */ + pub(super) apm_bios_info: ApmBiosInfo, /* 0x040 */ + pub(super) _pad2: [u8; 4], /* 0x054 */ + pub(super) tboot_addr: u64, /* 0x058 */ + pub(super) ist_info: IstInfo, /* 0x060 */ + pub(super) acpi_rsdp_addr: u64, /* 0x070 */ + pub(super) _pad3: [u8; 8], /* 0x078 */ + pub(super) hd0_info: [u8; 16], /* obsolete! 0x080 */ + pub(super) hd1_info: [u8; 16], /* obsolete! 0x090 */ + pub(super) sys_desc_table: SysDescTable, /* obsolete! 0x0a0 */ + pub(super) olpc_ofw_header: OlpcOfwHeader, /* 0x0b0 */ + pub(super) ext_ramdisk_image: u32, /* 0x0c0 */ + pub(super) ext_ramdisk_size: u32, /* 0x0c4 */ + pub(super) ext_cmd_line_ptr: u32, /* 0x0c8 */ + pub(super) _pad4: [u8; 112], /* 0x0cc */ + pub(super) cc_blob_address: u32, /* 0x13c */ + pub(super) edid_info: EdidInfo, /* 0x140 */ + pub(super) efi_info: EfiInfo, /* 0x1c0 */ + pub(super) alt_mem_k: u32, /* 0x1e0 */ + pub(super) scratch: u32, /* Scratch field! 0x1e4 */ + pub(super) e820_entries: u8, /* 0x1e8 */ + pub(super) eddbuf_entries: u8, /* 0x1e9 */ + pub(super) edd_mbr_sig_buf_entries: u8, /* 0x1ea */ + pub(super) kbd_status: u8, /* 0x1eb */ + pub(super) secure_boot: u8, /* 0x1ec */ + pub(super) _pad5: [u8; 2], /* 0x1ed */ + /* + * The sentinel is set to a nonzero value (0xff) in header.S. + * + * A bootloader is supposed to only take setup_header and put + * it into a clean boot_params buffer. If it turns out that + * it is clumsy or too generous with the buffer, it most + * probably will pick up the sentinel variable too. The fact + * that this variable then is still 0xff will let kernel + * know that some variables in boot_params are invalid and + * kernel should zero out certain portions of boot_params. + */ + pub(super) sentinel: u8, /* 0x1ef */ + pub(super) _pad6: [u8; 1], /* 0x1f0 */ + pub(super) hdr: SetupHeader, /* setup header 0x1f1 */ + pub(super) _pad7: [u8; 0x290 - 0x1f1 - core::mem::size_of::()], + pub(super) edd_mbr_sig_buffer: [u32; EDD_MBR_SIG_MAX], /* 0x290 */ + pub(super) e820_table: [BootE820Entry; E820_MAX_ENTRIES_ZEROPAGE], /* 0x2d0 */ + pub(super) _pad8: [u8; 48], /* 0xcd0 */ + pub(super) eddbuf: [EddInfo; EDDMAXNR], /* 0xd00 */ + pub(super) _pad9: [u8; 276], /* 0xeec */ +} diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/header.S b/framework/jinux-frame/src/arch/x86/boot/linux_boot/header.S new file mode 100644 index 000000000..591e80bdd --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/header.S @@ -0,0 +1,115 @@ +// The compatibility file for the Linux x86 Boot Protocol. +// See https://www.kernel.org/doc/html/v5.6/x86/boot.html for +// more information on the Linux x86 Boot Protocol. + +.intel_syntax noprefix + +// The section name is used by the build script to strip and make +// the binary file. +.section ".boot_compatibility_bin", "awx" + +// The Linux x86 Boot Protocol header. +// +// Some of the fields filled with a 0xab* values should be filled +// by the runner, which is the only tool after building and can +// access the info of the payload. +// Jinux will use only a few of these fields, and some of them +// are filled by the loader and will be read by Jinux. + +.code16 + +sentinel: .byte 0xff, 0xff +.org 0x01f1 +hdr: +setup_sects: .byte 0 +root_flags: .word 1 +syssize: .long 0 +ram_size: .word 0 +vid_mode: .word 0xfffd +root_dev: .word 0 +boot_flag: .word 0xAA55 +jump: .byte 0xeb + .byte start_of_setup-jump +magic: .ascii "HdrS" + .word 0x020f +realmode_swtch: .word 0, 0 +start_sys_seg: .word 0 + .word 0 +type_of_loader: .byte 0 +loadflags: .byte (1 << 0) +setup_move_size: .word 0x8000 +code32_start: .long 0x100000 +ramdisk_image: .long 0 +ramdisk_size: .long 0 +bootsect_kludge: .long 0 +heap_end_ptr: .word 65535 +ext_loader_ver: .byte 0 +ext_loader_type: .byte 0 +cmd_line_ptr: .long 0 +initrd_addr_max: .long 0x7fffffff +kernel_alignment: .long 0x1000000 +relocatable_kernel: .byte 0 +min_alignment: .byte 0x10 +xloadflags: .word 0 # none of the flags supported +cmdline_size: .long 4096-1 +hardware_subarch: .long 0 +hardware_subarch_data: .quad 0 +payload_offset: .long 0xabababab # at 0x248/4, to be filled by the runner +payload_length: .long 0xabababab # at 0x24c/4, to be filled by the runner +setup_data: .quad 0 +pref_address: .quad 0 +init_size: .long 0 +handover_offset: .long 0 +kernel_info_offset: .long 0 + +// End of header. + +// Temporary real mode GDTR/GDT entries. +.align 16 +real_gdtr: + .word gdt_end - gdt - 1 + .quad gdt + +.align 16 +gdt: + .quad 0x0000000000000000 # 0: null descriptor + .quad 0x00cf8a000000ffff # 8: 32-bit system segment (4k sys ex rw) + .quad 0x00cf9a000000ffff # 16: 32-bit code/data segment (4k sys ex rw) +gdt_end: + +// 16-bit setup code starts here. +.code16 +start_of_setup: +// Enter 32-bit protected mode without paging. + // Disable interrupts. + cli + + // Enable a20 gate. + in al, 0x92 + or al, 2 + out 0x92, al + + // Load GDT. + lgdt [real_gdtr] + mov eax, cr0 + or eax, 1 + mov cr0, eax + + // Go to protected mode. + jmp start_of_setup32 + +// 32-bit setup code starts here. +.code32 +start_of_setup32: + // print to screen a debug message using out port 0x3f8. + mov dx, 0x3f8 + mov al, 'H' + out dx, al + mov al, 'e' + out dx, al + mov al, 'l' + out dx, al + mov al, 'l' + out dx, al + mov al, 'o' + out dx, al diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs b/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs new file mode 100644 index 000000000..eeec58e19 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs @@ -0,0 +1,151 @@ +//! The Linux 64-bit Boot Protocol supporting module. +//! + +mod boot_params; +use boot_params::E820Type; + +use crate::boot::{ + kcmdline::KCmdlineArg, + memory_region::{MemoryRegion, MemoryRegionType}, + BootloaderAcpiArg, BootloaderFramebufferArg, +}; +use crate::{config::PHYS_OFFSET, vm::paddr_to_vaddr}; + +use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; +use core::ffi::CStr; + +use spin::Once; + +static BOOT_PARAMS: Once = Once::new(); + +fn init_bootloader_name(bootloader_name: &'static Once) { + let hdr = &BOOT_PARAMS.get().unwrap().hdr; + // The bootloaders have assigned IDs in Linux, see + // https://www.kernel.org/doc/Documentation/x86/boot.txt + // for details. + let ext_str: String; + let name = match hdr.type_of_loader { + 0x0 => "LILO", // (0x00 reserved for pre-2.00 bootloader) + 0x1 => "Loadlin", + 0x2 => "bootsect-loader", // (0x20, all other values reserved) + 0x3 => "Syslinux", + 0x4 => "Etherboot/gPXE/iPXE", + 0x5 => "ELILO", + 0x7 => "GRUB", + 0x8 => "U-Boot", + 0x9 => "Xen", + 0xA => "Gujin", + 0xB => "Qemu", + 0xC => "Arcturus Networks uCbootloader", + 0xD => "kexec-tools", + 0xE => { + // Extended + ext_str = format!( + "Extended bootloader {}, version {}", + (hdr.ext_loader_type + 0x10), + (hdr.type_of_loader & 0x0f) + (hdr.ext_loader_ver << 4) + ); + &ext_str + } + 0xF => "Special", // (0xFF = undefined) + 0x10 => "Reserved", + 0x11 => "Minimal Linux Bootloader ", + 0x12 => "OVMF UEFI virtualization stack", + _ => "Unknown bootloader type!", + } + .to_owned(); + bootloader_name.call_once(|| name); +} + +fn init_kernel_commandline(kernel_cmdline: &'static Once) { + let cmdline_c_str: &CStr = + unsafe { CStr::from_ptr(BOOT_PARAMS.get().unwrap().hdr.cmd_line_ptr as *const i8) }; + let cmdline_str = cmdline_c_str.to_str().unwrap(); + kernel_cmdline.call_once(|| cmdline_str.into()); +} + +fn init_initramfs(initramfs: &'static Once<&'static [u8]>) { + let hdr = &BOOT_PARAMS.get().unwrap().hdr; + let ptr = hdr.ramdisk_image as usize; + // We must return a slice composed by VA since kernel should read everything in VA. + let base_va = if ptr < PHYS_OFFSET { + paddr_to_vaddr(ptr) + } else { + ptr + }; + let length = hdr.ramdisk_size as usize; + initramfs.call_once(|| unsafe { core::slice::from_raw_parts(base_va as *const u8, length) }); +} + +fn init_acpi_arg(acpi: &'static Once) { + acpi.call_once(|| { + BootloaderAcpiArg::Rsdp( + BOOT_PARAMS + .get() + .unwrap() + .acpi_rsdp_addr + .try_into() + .unwrap(), + ) + }); +} + +fn init_framebuffer_info(framebuffer_arg: &'static Once) { + let screen_info = &BOOT_PARAMS.get().unwrap().screen_info; + framebuffer_arg.call_once(|| BootloaderFramebufferArg { + address: screen_info.lfb_base as usize, + width: screen_info.lfb_width as usize, + height: screen_info.lfb_height as usize, + bpp: screen_info.lfb_depth as usize, + }); +} + +impl From for MemoryRegionType { + fn from(value: E820Type) -> Self { + match value { + E820Type::Ram => Self::Usable, + E820Type::Reserved => Self::Reserved, + E820Type::Acpi => Self::Reclaimable, + E820Type::Nvs => Self::NonVolatileSleep, + _ => Self::BadMemory, + } + } +} + +fn init_memory_regions(memory_regions: &'static Once>) { + let boot_params = &BOOT_PARAMS.get().unwrap(); + let num_entries = boot_params.e820_entries as usize; + let mut regions = Vec::::new(); + for e820_entry in &boot_params.e820_table[0..num_entries] { + regions.push(MemoryRegion::new( + e820_entry.addr as usize, + e820_entry.size as usize, + e820_entry.typ.into(), + )); + } + memory_regions.call_once(|| regions); +} + +// The entry point of kernel code, which should be defined by the package that +// uses jinux-frame. +extern "Rust" { + fn jinux_main() -> !; +} + +/// The entry point of Rust code called by the Linux 64-bit boot compatible bootloader. +/// It is the ELF entrypoint. +#[no_mangle] +unsafe extern "sysv64" fn __linux64_boot(params: boot_params::BootParams) -> ! { + assert_eq!({ params.hdr.boot_flag }, boot_params::LINUX_BOOT_FLAG_MAGIC); + assert_eq!({ params.hdr.header }, boot_params::LINUX_BOOT_HEADER_MAGIC); + BOOT_PARAMS.call_once(|| params); + crate::boot::register_boot_init_callbacks( + init_bootloader_name, + init_kernel_commandline, + init_initramfs, + init_acpi_arg, + init_framebuffer_info, + init_memory_regions, + ); + jinux_main(); +} diff --git a/framework/jinux-frame/src/arch/x86/boot/mod.rs b/framework/jinux-frame/src/arch/x86/boot/mod.rs index 21b39a7f1..584e07d5f 100644 --- a/framework/jinux-frame/src/arch/x86/boot/mod.rs +++ b/framework/jinux-frame/src/arch/x86/boot/mod.rs @@ -1,65 +1,25 @@ -//! The boot module defines the entrypoints of Jinux and the corresponding -//! headers for different bootloaders. +//! The x86 boot module defines the entrypoints of Jinux and +//! the corresponding headers for different x86 boot protocols. //! -//! We currently support Multiboot2. The support for Linux Boot Protocol is -//! on its way. +//! We directly support +//! +//! - Multiboot +//! - Multiboot2 +//! - Linux x86 Boot Protocol +//! +//! without any additional configurations. +//! +//! Jinux diffrentiates the boot protocol by the entry point +//! chosen by the boot loader. In each entry point function, +//! the universal callback registeration method from +//! `crate::boot` will be called. Thus the initialization of +//! boot information is transparent for the upper level kernel. //! +mod linux_boot; mod multiboot; mod multiboot2; use core::arch::global_asm; -use alloc::{string::String, vec::Vec}; -use spin::Once; - -use crate::boot::{ - kcmdline::KCmdlineArg, memory_region::MemoryRegion, BootloaderAcpiArg, BootloaderFramebufferArg, -}; - -use self::{ - multiboot::{multiboot_entry, MULTIBOOT_ENTRY_MAGIC}, - multiboot2::{multiboot2_entry, MULTIBOOT2_ENTRY_MAGIC}, -}; - -/// Initialize the global boot static varaiables in the boot module to allow -/// other modules to get the boot information. -pub fn init_boot_args( - bootloader_name: &'static Once, - kernel_cmdline: &'static Once, - initramfs: &'static Once<&'static [u8]>, - acpi: &'static Once, - framebuffer_arg: &'static Once, - memory_regions: &'static Once>, -) { - if multiboot::boot_by_multiboot() { - multiboot::init_boot_args( - bootloader_name, - kernel_cmdline, - initramfs, - acpi, - framebuffer_arg, - memory_regions, - ); - } else if multiboot2::boot_by_multiboot2() { - multiboot2::init_boot_args( - bootloader_name, - kernel_cmdline, - initramfs, - acpi, - framebuffer_arg, - memory_regions, - ); - } -} - global_asm!(include_str!("boot.S")); - -#[no_mangle] -unsafe extern "C" fn __boot_entry(boot_magic: u32, boot_params: u64) -> ! { - match boot_magic { - MULTIBOOT2_ENTRY_MAGIC => multiboot2_entry(boot_magic, boot_params), - MULTIBOOT_ENTRY_MAGIC => multiboot_entry(boot_magic, boot_params), - _ => panic!("Unknown boot magic:{:x?}", boot_magic), - } -} diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot/header.S b/framework/jinux-frame/src/arch/x86/boot/multiboot/header.S new file mode 100644 index 000000000..1c5ed4761 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot/header.S @@ -0,0 +1,17 @@ +// This is the GNU Multiboot header. +// Reference: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html +.section ".multiboot_header", "a" + +MB_MAGIC = 0x1BADB002 +MB_FLAGS = 0 +MB_CHECKSUM = -(MB_MAGIC + MB_FLAGS) + +.code32 +multiboot_header: + .align 8 + .long MB_MAGIC + .long MB_FLAGS + .long MB_CHECKSUM +multiboot_entry: +.global __multiboot_boot + jmp __multiboot_boot diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot.rs b/framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs similarity index 82% rename from framework/jinux-frame/src/arch/x86/boot/multiboot.rs rename to framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs index 2026c833a..c64fa6b16 100644 --- a/framework/jinux-frame/src/arch/x86/boot/multiboot.rs +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs @@ -1,5 +1,3 @@ -use core::mem::swap; - use alloc::{string::String, vec::Vec}; use multiboot2::MemoryAreaType; use spin::Once; @@ -8,37 +6,19 @@ use crate::{ arch::x86::kernel::acpi::AcpiMemoryHandler, boot::{ kcmdline::KCmdlineArg, - memory_region::{MemoryRegion, MemoryRegionType}, + memory_region::{non_overlapping_regions_from, MemoryRegion, MemoryRegionType}, BootloaderAcpiArg, BootloaderFramebufferArg, }, config::PHYS_OFFSET, vm::paddr_to_vaddr, }; +use core::arch::global_asm; + +global_asm!(include_str!("header.S")); + pub(super) const MULTIBOOT_ENTRY_MAGIC: u32 = 0x2BADB002; -/// Initialize the global boot static varaiables in the boot module to allow -/// other modules to get the boot information. -pub(super) fn init_boot_args( - bootloader_name: &'static Once, - kernel_cmdline: &'static Once, - initramfs: &'static Once<&'static [u8]>, - acpi: &'static Once, - framebuffer_arg: &'static Once, - memory_regions: &'static Once>, -) { - init_bootloader_name(bootloader_name); - init_kernel_commandline(kernel_cmdline); - init_initramfs(initramfs); - init_acpi_arg(acpi); - init_framebuffer_info(framebuffer_arg); - init_memory_regions(memory_regions); -} - -pub fn boot_by_multiboot() -> bool { - MB1_INFO.is_completed() -} - fn init_bootloader_name(bootloader_name: &'static Once) { bootloader_name.call_once(|| { let mut name = ""; @@ -135,12 +115,7 @@ fn init_framebuffer_info(framebuffer_arg: &'static Once>) { - // We should later use regions in `regions_unusable` to truncate all - // regions in `regions_usable`. - // The difference is that regions in `regions_usable` could be used by - // the frame allocator. - let mut regions_usable = Vec::::new(); - let mut regions_unusable = Vec::::new(); + let mut regions = Vec::::new(); // Add the regions in the multiboot protocol. let info = MB1_INFO.get().unwrap(); @@ -157,14 +132,7 @@ fn init_memory_regions(memory_regions: &'static Once>) { entry.length.try_into().unwrap(), area_type, ); - match area_type { - MemoryRegionType::Usable | MemoryRegionType::Reclaimable => { - regions_usable.push(region); - } - _ => { - regions_unusable.push(region); - } - } + regions.push(region); current += entry.size as usize + 4; } @@ -175,7 +143,7 @@ fn init_memory_regions(memory_regions: &'static Once>) { height: info.framebuffer_table.height as usize, bpp: info.framebuffer_table.bpp as usize, }; - regions_unusable.push(MemoryRegion::new( + regions.push(MemoryRegion::new( fb.address, (fb.width * fb.height * fb.bpp + 7) / 8, // round up when divide with 8 (bits/Byte) MemoryRegionType::Framebuffer, @@ -186,14 +154,13 @@ fn init_memory_regions(memory_regions: &'static Once>) { fn __kernel_start(); fn __kernel_end(); } - regions_unusable.push(MemoryRegion::new( + regions.push(MemoryRegion::new( __kernel_start as usize, __kernel_end as usize - __kernel_start as usize, MemoryRegionType::Kernel, )); // Add the initramfs area. - // These are physical addresses provided by the linker script. if info.mods_count != 0 { let modules_addr = info.mods_addr as usize; // We only use one module @@ -203,33 +170,15 @@ fn init_memory_regions(memory_regions: &'static Once>) { (*(paddr_to_vaddr(modules_addr + 4) as *const u32)) as usize, ) }; - regions_unusable.push(MemoryRegion::new( + regions.push(MemoryRegion::new( start, end - start, - MemoryRegionType::Reserved, + MemoryRegionType::Module, )); } - // `regions_*` are 2 rolling vectors since we are going to truncate - // the regions in a iterative manner. - let mut regions = Vec::::new(); - let regions_src = &mut regions_usable; - let regions_dst = &mut regions; - // Truncate the usable regions. - for &r_unusable in ®ions_unusable { - regions_dst.clear(); - for r_usable in &*regions_src { - regions_dst.append(&mut r_usable.truncate(&r_unusable)); - } - swap(regions_src, regions_dst); - } - - // Initialize with regions_unusable + regions_src - memory_regions.call_once(move || { - let mut all_regions = regions_unusable; - all_regions.append(&mut regions_usable); - all_regions - }); + // Initialize with non-overlapping regions. + memory_regions.call_once(move || non_overlapping_regions_from(regions.as_ref())); } /// Representation of Multiboot Information according to specification. @@ -405,8 +354,18 @@ extern "Rust" { static MB1_INFO: Once<&'static MultibootLegacyInfo> = Once::new(); -pub(super) unsafe fn multiboot_entry(boot_magic: u32, boot_params: u64) -> ! { +/// The entry point of Rust code called by inline asm. +#[no_mangle] +unsafe extern "sysv64" fn __multiboot_entry(boot_magic: u32, boot_params: u64) -> ! { assert_eq!(boot_magic, MULTIBOOT_ENTRY_MAGIC); MB1_INFO.call_once(|| &*(paddr_to_vaddr(boot_params as usize) as *const MultibootLegacyInfo)); + crate::boot::register_boot_init_callbacks( + init_bootloader_name, + init_kernel_commandline, + init_initramfs, + init_acpi_arg, + init_framebuffer_info, + init_memory_regions, + ); jinux_main(); } diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot2/header.S b/framework/jinux-frame/src/arch/x86/boot/multiboot2/header.S new file mode 100644 index 000000000..f9e87df93 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot2/header.S @@ -0,0 +1,44 @@ +// This is the GNU Multiboot 2 header. +// Reference: https://www.gnu.org/software/grub/manual/multiboot2/html_node/Index.html//Index +.section ".multiboot2_header", "a" +.code32 + +// Macros for cleaner code in the header fields. +MB2_MAGIC = 0xE85250D6 +MB2_ARCHITECTURE = 0 // 32-bit (protected) mode of i386 +MB2_HEADERLEN = header_end - header_start +MB2_CHECKSUM = -(MB2_MAGIC + MB2_ARCHITECTURE + MB2_HEADERLEN) + +header_start: + .align 8 + + .long MB2_MAGIC + .long MB2_ARCHITECTURE + .long MB2_HEADERLEN + .long MB2_CHECKSUM + + // Tag: entry address +entry_address_tag_start: + .short 3 + .short 1 // Optional + .long entry_address_tag_end - entry_address_tag_start +.extern __multiboot2_boot + .long __multiboot2_boot // entry_addr +entry_address_tag_end: + + // Tag: information request + .align 8 +info_request: + .short 1 + .short 0 // Required + .long info_request_end - info_request + .long 6 // Memory map request + .long 15 // ACPI (new) request +info_request_end: + + // Tag: header end + .align 8 + .short 0 // type: tags end + .short 0 // flags + .long 8 // size +header_end: diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot2.rs b/framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs similarity index 70% rename from framework/jinux-frame/src/arch/x86/boot/multiboot2.rs rename to framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs index 52e7c6fd1..5be485f3e 100644 --- a/framework/jinux-frame/src/arch/x86/boot/multiboot2.rs +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs @@ -6,20 +6,19 @@ use multiboot2::{BootInformation, BootInformationHeader, MemoryAreaType}; use crate::boot::{ kcmdline::KCmdlineArg, - memory_region::{MemoryRegion, MemoryRegionType}, + memory_region::{non_overlapping_regions_from, MemoryRegion, MemoryRegionType}, BootloaderAcpiArg, BootloaderFramebufferArg, }; -use core::mem::swap; use spin::Once; +use core::arch::global_asm; + +global_asm!(include_str!("header.S")); + use crate::{config::PHYS_OFFSET, vm::paddr_to_vaddr}; pub(super) const MULTIBOOT2_ENTRY_MAGIC: u32 = 0x36d76289; -pub(super) fn boot_by_multiboot2() -> bool { - MB2_INFO.is_completed() -} - static MB2_INFO: Once = Once::new(); fn init_bootloader_name(bootloader_name: &'static Once) { @@ -103,12 +102,7 @@ impl From for MemoryRegionType { } fn init_memory_regions(memory_regions: &'static Once>) { - // We should later use regions in `regions_unusable` to truncate all - // regions in `regions_usable`. - // The difference is that regions in `regions_usable` could be used by - // the frame allocator. - let mut regions_usable = Vec::::new(); - let mut regions_unusable = Vec::::new(); + let mut regions = Vec::::new(); // Add the regions returned by Grub. let memory_regions_tag = MB2_INFO @@ -126,14 +120,7 @@ fn init_memory_regions(memory_regions: &'static Once>) { (end - start).try_into().unwrap(), area_typ, ); - match area_typ { - MemoryRegionType::Usable | MemoryRegionType::Reclaimable => { - regions_usable.push(region); - } - _ => { - regions_unusable.push(region); - } - } + regions.push(region); } // Add the framebuffer region since Grub does not specify it. let fb_tag = MB2_INFO.get().unwrap().framebuffer_tag().unwrap().unwrap(); @@ -143,7 +130,7 @@ fn init_memory_regions(memory_regions: &'static Once>) { height: fb_tag.height() as usize, bpp: fb_tag.bpp() as usize, }; - regions_unusable.push(MemoryRegion::new( + regions.push(MemoryRegion::new( fb.address, (fb.width * fb.height * fb.bpp + 7) / 8, // round up when divide with 8 (bits/Byte) MemoryRegionType::Framebuffer, @@ -154,7 +141,7 @@ fn init_memory_regions(memory_regions: &'static Once>) { fn __kernel_start(); fn __kernel_end(); } - regions_unusable.push(MemoryRegion::new( + regions.push(MemoryRegion::new( __kernel_start as usize, __kernel_end as usize - __kernel_start as usize, MemoryRegionType::Kernel, @@ -162,51 +149,15 @@ fn init_memory_regions(memory_regions: &'static Once>) { // Add the boot module region since Grub does not specify it. let mb2_module_tag = MB2_INFO.get().unwrap().module_tags(); for m in mb2_module_tag { - regions_unusable.push(MemoryRegion::new( + regions.push(MemoryRegion::new( m.start_address() as usize, m.module_size() as usize, MemoryRegionType::Module, )); } - // `regions_*` are 2 rolling vectors since we are going to truncate - // the regions in a iterative manner. - let mut regions = Vec::::new(); - let regions_src = &mut regions_usable; - let regions_dst = &mut regions; - // Truncate the usable regions. - for &r_unusable in ®ions_unusable { - regions_dst.clear(); - for r_usable in &*regions_src { - regions_dst.append(&mut r_usable.truncate(&r_unusable)); - } - swap(regions_src, regions_dst); - } - - // Initialize with regions_unusable + regions_src - memory_regions.call_once(move || { - let mut all_regions = regions_unusable; - all_regions.append(regions_src); - all_regions - }); -} - -/// Initialize the global boot static varaiables in the boot module to allow -/// other modules to get the boot information. -pub fn init_boot_args( - bootloader_name: &'static Once, - kernel_cmdline: &'static Once, - initramfs: &'static Once<&'static [u8]>, - acpi: &'static Once, - framebuffer_arg: &'static Once, - memory_regions: &'static Once>, -) { - init_bootloader_name(bootloader_name); - init_kernel_commandline(kernel_cmdline); - init_initramfs(initramfs); - init_acpi_arg(acpi); - init_framebuffer_info(framebuffer_arg); - init_memory_regions(memory_regions); + // Initialize with non-overlapping regions. + memory_regions.call_once(move || non_overlapping_regions_from(regions.as_ref())); } // The entry point of kernel code, which should be defined by the package that @@ -216,10 +167,19 @@ extern "Rust" { } /// The entry point of Rust code called by inline asm. -pub(super) unsafe fn multiboot2_entry(boot_magic: u32, boot_params: u64) -> ! { +#[no_mangle] +unsafe extern "sysv64" fn __multiboot2_entry(boot_magic: u32, boot_params: u64) -> ! { assert_eq!(boot_magic, MULTIBOOT2_ENTRY_MAGIC); MB2_INFO.call_once(|| unsafe { BootInformation::load(boot_params as *const BootInformationHeader).unwrap() }); + crate::boot::register_boot_init_callbacks( + init_bootloader_name, + init_kernel_commandline, + init_initramfs, + init_acpi_arg, + init_framebuffer_info, + init_memory_regions, + ); jinux_main(); } diff --git a/framework/jinux-frame/src/boot/memory_region.rs b/framework/jinux-frame/src/boot/memory_region.rs index b68a5e49d..d7b7aa8c1 100644 --- a/framework/jinux-frame/src/boot/memory_region.rs +++ b/framework/jinux-frame/src/boot/memory_region.rs @@ -2,6 +2,7 @@ //! use alloc::{vec, vec::Vec}; +use core::mem::swap; /// The type of initial memory regions that are needed for the kernel. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] @@ -34,7 +35,7 @@ pub struct MemoryRegion { } impl MemoryRegion { - /// Construct a page aligned memory region. + /// Construct a valid memory region. pub fn new(base: usize, len: usize, typ: MemoryRegionType) -> Self { MemoryRegion { base, len, typ } } @@ -101,3 +102,46 @@ impl MemoryRegion { } } } + +/// Truncate regions, resulting in a set of regions that does not overlap. +/// +/// The truncation will be done according to the type of the regions, that +/// usable and reclaimable regions will be truncated by the unusable regions. +pub fn non_overlapping_regions_from(regions: &[MemoryRegion]) -> Vec { + // We should later use regions in `regions_unusable` to truncate all + // regions in `regions_usable`. + // The difference is that regions in `regions_usable` could be used by + // the frame allocator. + let mut regions_usable = Vec::::new(); + let mut regions_unusable = Vec::::new(); + + for r in regions { + match r.typ { + MemoryRegionType::Usable | MemoryRegionType::Reclaimable => { + regions_usable.push(*r); + } + _ => { + regions_unusable.push(*r); + } + } + } + + // `regions_*` are 2 rolling vectors since we are going to truncate + // the regions in a iterative manner. + let mut regions = Vec::::new(); + let regions_src = &mut regions_usable; + let regions_dst = &mut regions; + // Truncate the usable regions. + for &r_unusable in ®ions_unusable { + regions_dst.clear(); + for r_usable in &*regions_src { + regions_dst.append(&mut r_usable.truncate(&r_unusable)); + } + swap(regions_src, regions_dst); + } + + // Combine all the regions processed. + let mut all_regions = regions_unusable; + all_regions.append(&mut regions_usable); + all_regions +} diff --git a/framework/jinux-frame/src/boot/mod.rs b/framework/jinux-frame/src/boot/mod.rs index 78b9466f3..1e3ee9fca 100644 --- a/framework/jinux-frame/src/boot/mod.rs +++ b/framework/jinux-frame/src/boot/mod.rs @@ -48,11 +48,33 @@ macro_rules! define_global_static_boot_arguments { } )* - // Produce a init function call. The init function must - // be defined in the `arch::boot` module conforming to this - // definition. - fn arch_init_boot_args() { - crate::arch::boot::init_boot_args( $( &$upper, )* ); + struct BootInitCallBacks { + $( $lower: fn(&'static Once<$typ>) -> (), )* + } + + static BOOT_INIT_CALLBACKS: Once = Once::new(); + + /// The macro generated boot init callbacks registering interface. + /// + /// For the introduction of a new boot protocol, the entry point could be a novel + /// one. The entry point function should register all the boot initialization + /// methods before `jinux_main` is called. A boot initialization method takes a + /// reference of the global static boot information variable and initialize it, + /// so that the boot information it represents could be accessed in the kernel + /// anywhere. + /// + /// The reason why the entry point function is not designed to directly initialize + /// the boot information variables is simply that the heap is not initialized at + /// that moment. + pub fn register_boot_init_callbacks($( $lower: fn(&'static Once<$typ>) -> (), )* ) { + BOOT_INIT_CALLBACKS.call_once(|| { + BootInitCallBacks { $( $lower, )* } + }); + } + + fn call_all_boot_init_callbacks() { + let callbacks = &BOOT_INIT_CALLBACKS.get().unwrap(); + $( (callbacks.$lower)(&$upper); )* } }; } @@ -74,5 +96,5 @@ define_global_static_boot_arguments!( /// The initialization must be done after the heap is set and before physical /// mappings are cancelled. pub fn init() { - arch_init_boot_args(); + call_all_boot_init_callbacks(); } diff --git a/tools/docker/Dockerfile.ubuntu22.04 b/tools/docker/Dockerfile.ubuntu22.04 index cb021b5bc..2dcec624d 100644 --- a/tools/docker/Dockerfile.ubuntu22.04 +++ b/tools/docker/Dockerfile.ubuntu22.04 @@ -38,6 +38,7 @@ RUN apt update && apt-get install -y --no-install-recommends \ gdb \ grub-common \ grub-pc \ + grub-pc-dbg \ libssl-dev \ net-tools \ openssh-server \ @@ -68,8 +69,9 @@ RUN curl https://sh.rustup.rs -sSf | \ && cargo -V \ && rustup component add rust-src rustc-dev llvm-tools-preview -# Install mdbook -RUN cargo install mdbook +# Install cargo tools +RUN cargo install mdbook \ + && cargo install cargo-binutils # Add the path of jinux tools ENV PATH="/root/jinux/target/bin:${PATH}"