diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index fb871b1b..00000000 --- a/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[build] -target = "x86_64-bootloader.json" diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..a03fb80d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "i8086-bootloader.json" + +[unstable] +build-std = ["core"] \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6264eec..3d094b83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,53 +41,44 @@ jobs: - name: "Install Rustup Components" run: rustup component add rust-src llvm-tools-preview - - name: "Install cargo-xbuild" - run: cargo install cargo-xbuild --debug - name: "Install cargo-binutils" run: cargo install cargo-binutils --version 0.1.7 --debug - - run: cargo xbuild - working-directory: test-kernel - name: 'Build Test Kernel' - - name: 'Build Bootloader' - run: cargo xbuild --bin bootloader --features binary --release - env: - KERNEL: "test-kernel/target/x86_64-test-kernel/debug/test-kernel" - KERNEL_MANIFEST: "test-kernel/Cargo.toml" + run: cargo xbuild --release - name: 'Convert Bootloader ELF to Binary' - run: cargo objcopy -- -I elf64-x86-64 -O binary --binary-architecture=i386:x86-64 target/x86_64-bootloader/release/bootloader target/x86_64-bootloader/release/bootloader.bin - - # install QEMU - - name: Install QEMU (Linux) - run: sudo apt update && sudo apt install qemu-system-x86 - if: runner.os == 'Linux' - - name: Install QEMU (macOS) - run: brew install qemu - if: runner.os == 'macOS' - env: - HOMEBREW_NO_AUTO_UPDATE: 1 - HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 1 - HOMEBREW_NO_INSTALL_CLEANUP: 1 - - name: Install Scoop (Windows) - run: | - Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') - echo ::add-path::$HOME\scoop\shims - if: runner.os == 'Windows' - shell: pwsh - - name: Install QEMU (Windows) - run: scoop install qemu - if: runner.os == 'Windows' - shell: pwsh - - name: "Print QEMU Version" - run: qemu-system-x86_64 --version - - - name: 'Run Test Kernel with Bootloader' - run: | - qemu-system-x86_64 -drive format=raw,file=target/x86_64-bootloader/release/bootloader.bin -device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none - if [ $? -eq 123 ]; then (exit 0); else (exit 1); fi - shell: 'bash {0}' + run: cargo objcopy -- -I elf64-i386 -O binary --binary-architecture=i386:x86-64 target/i8086-bootloader/release/bootloader target/i8086-bootloader/release/image.bin + + # # install QEMU + # - name: Install QEMU (Linux) + # run: sudo apt update && sudo apt install qemu-system-x86 + # if: runner.os == 'Linux' + # - name: Install QEMU (macOS) + # run: brew install qemu + # if: runner.os == 'macOS' + # env: + # HOMEBREW_NO_AUTO_UPDATE: 1 + # HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 1 + # HOMEBREW_NO_INSTALL_CLEANUP: 1 + # - name: Install Scoop (Windows) + # run: | + # Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') + # echo ::add-path::$HOME\scoop\shims + # if: runner.os == 'Windows' + # shell: pwsh + # - name: Install QEMU (Windows) + # run: scoop install qemu + # if: runner.os == 'Windows' + # shell: pwsh + # - name: "Print QEMU Version" + # run: qemu-system-x86_64 --version + + # - name: 'Run Test Kernel with Bootloader' + # run: | + # qemu-system-x86_64 -drive format=raw,file=target/x86_64-bootloader/release/bootloader.bin -device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none + # if [ $? -eq 123 ]; then (exit 0); else (exit 1); fi + # shell: 'bash {0}' build_example_kernel: @@ -111,8 +102,6 @@ jobs: if: runner.os == 'macOS' - name: "Install Rustup Components" run: rustup component add rust-src llvm-tools-preview - - name: "Install cargo-xbuild" - run: cargo install cargo-xbuild --debug - name: 'Build Example Kernel' run: cargo xbuild working-directory: example-kernel diff --git a/.gitignore b/.gitignore index eccd7b4a..b5b49403 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/target/ +**/target/ **/*.rs.bk +**/*.bin \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 767178c0..5a9f7ea9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,105 +2,58 @@ # It is not intended for manual editing. [[package]] name = "bit_field" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bit_field" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" [[package]] name = "bitflags" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bootloader" -version = "0.9.2" +version = "0.1.0" dependencies = [ - "bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fixedvec 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "font8x8 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", - "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "llvm-tools", + "shared", ] [[package]] -name = "cast" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fixedvec" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "font8x8" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "llvm-tools" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "toml" -version = "0.5.1" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", + "spin", ] [[package]] -name = "usize_conversions" -version = "0.2.0" +name = "llvm-tools" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" [[package]] -name = "x86_64" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "shared" +version = "0.1.0" dependencies = [ - "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bit_field", + "bitflags", + "lazy_static", + "spin", + "volatile", ] [[package]] -name = "xmas-elf" -version = "0.6.2" +name = "spin" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "zero" -version = "0.1.2" +name = "volatile" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" -"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" -"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" -"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" -"checksum fixedvec 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b395ef2adf62bdeefcd1b59ad0dd2225c6c333ec79656ea79ac5285c46d051ea" -"checksum font8x8 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "44226c40489fb1d602344a1d8f1b544570c3435e396dda1eda7b5ef010d8f1be" -"checksum llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" -"checksum serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5626ac617da2f2d9c48af5515a21d5a480dbd151e01bb1c355e26a3e68113" -"checksum toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c96d7873fa7ef8bdeb3a9cda3ac48389b4154f32b9803b4bc26220b677b039" -"checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" -"checksum x86_64 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5d9e3e26fcb51976eafa310e8f2b7a5a83ae8c185443efe50cbc6534a4fffa0d" -"checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" -"checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" diff --git a/Cargo.toml b/Cargo.toml index 420553b6..dc78aac1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,53 +1,13 @@ [package] name = "bootloader" -version = "0.9.2" -authors = ["Philipp Oppermann "] -license = "MIT/Apache-2.0" -description = "An experimental pure-Rust x86 bootloader." -repository = "https://github.com/rust-osdev/bootloader" +version = "0.1.0" +authors = ["Ryland Morgan "] edition = "2018" +description = "An experimental bootloader, written in Rust" build = "build.rs" -[[bin]] -name = "bootloader" -required-features = ["binary"] - [dependencies] -xmas-elf = { version = "0.6.2", optional = true } -x86_64 = { version = "0.8.3", optional = true } -usize_conversions = { version = "0.2.0", optional = true } -fixedvec = { version = "0.2.4", optional = true } -bit_field = { version = "0.10.0", optional = true } - -[dependencies.font8x8] -version = "0.2.4" -default-features = false -features = ["unicode"] -optional = true +shared = { path = "src/shared" } [build-dependencies] -llvm-tools = { version = "0.1", optional = true } -toml = { version = "0.5.1", optional = true } - -[features] -default = [] -binary = ["xmas-elf", "x86_64", "usize_conversions", "fixedvec", "llvm-tools", "toml"] -vga_320x200 = ["font8x8"] -recursive_page_table = [] -map_physical_memory = [] -sse = ["bit_field"] - -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" -lto = false -debug = true - -[package.metadata.bootloader] -target = "x86_64-bootloader.json" - -[package.metadata.docs.rs] -features = [ "recursive_page_table", "map_physical_memory" ] -default-target = "x86_64-unknown-linux-gnu" +llvm-tools = "0.1" \ No newline at end of file diff --git a/bors.toml b/bors.toml deleted file mode 100644 index c8009028..00000000 --- a/bors.toml +++ /dev/null @@ -1,4 +0,0 @@ -status = [ - "Test", "Build Example Kernel", "Check Formatting" -] -delete_merged_branches = true diff --git a/build.rs b/build.rs index 7918c624..dcec196e 100644 --- a/build.rs +++ b/build.rs @@ -1,299 +1,156 @@ -#[cfg(not(feature = "binary"))] -fn main() {} +// This build script compiles our bootloader. Because of architecture differences we can't use the standard Rust dependency resolution. To get around this (and add some more seperation between different areas) we compile all of the subcrates as static libraries and link them like we would a C dependency -#[cfg(feature = "binary")] -#[derive(Default)] -struct BootloaderConfig { - physical_memory_offset: Option, - kernel_stack_address: Option, - kernel_stack_size: Option, - boot_info_address: Option, -} +// TODO - Reuse compilation artifacts so core isn't compiled so many times +use llvm_tools::{exe, LlvmTools}; +use std::env; +use std::path::Path; +use std::process::Command; -#[cfg(feature = "binary")] -fn parse_aligned_addr(key: &str, value: &str) -> u64 { - let num = if value.starts_with("0x") { - u64::from_str_radix(&value[2..], 16) - } else { - u64::from_str_radix(&value, 10) - }; +fn main() { + // Read environment variables set by cargo + let cargo_path = env::var("CARGO").expect("Missing CARGO environment variable"); + let cargo = Path::new(&cargo_path); - let num = num.expect(&format!( - "`{}` in the kernel manifest must be an integer (is `{}`)", - key, value - )); + let manifest_dir_path = + env::var("CARGO_MANIFEST_DIR").expect("Missing CARGO_MANIFEST_DIR environment variable"); + let manifest_dir = Path::new(&manifest_dir_path); - if num % 0x1000 != 0 { - panic!( - "`{}` in the kernel manifest must be aligned to 4KiB (is `{}`)", - key, value - ); - } else { - num - } -} + // Find the root project target dir + let current_dir = env::current_dir().expect("Couldn't get current directory"); + let target_dir_rel = manifest_dir.join("target"); + let target_dir = current_dir.join(target_dir_rel); -#[cfg(feature = "binary")] -fn parse_to_config(cfg: &mut BootloaderConfig, table: &toml::value::Table) { - use toml::Value; + // Find the objcopy binary + let llvm_tools = LlvmTools::new().expect("LLVM tools not found"); + let objcopy = llvm_tools + .tool(&exe("llvm-objcopy")) + .expect("llvm-objcopy not found"); + + // Build stage 3 + build_subproject( + Path::new("src/protected/stage_3"), + &["third_stage"], + "../i386-unknown-none.json", + &target_dir, + &objcopy, + &cargo, + ); - for (key, value) in table { - match (key.as_str(), value.clone()) { - ("kernel-stack-address", Value::Integer(i)) - | ("physical-memory-offset", Value::Integer(i)) - | ("boot-info-address", Value::Integer(i)) => { - panic!( - "`{0}` in the kernel manifest must be given as a string, \ - as toml does not support unsigned 64-bit integers (try `{0} = \"{1}\"`)", - key.as_str(), - i - ); - } - ("kernel-stack-address", Value::String(s)) => { - cfg.kernel_stack_address = Some(parse_aligned_addr(key.as_str(), &s)); - } - ("boot-info-address", Value::String(s)) => { - cfg.boot_info_address = Some(parse_aligned_addr(key.as_str(), &s)); - } - #[cfg(not(feature = "map_physical_memory"))] - ("physical-memory-offset", Value::String(_)) => { - panic!( - "`physical-memory-offset` is only supported when the `map_physical_memory` \ - feature of the crate is enabled" - ); - } - #[cfg(feature = "map_physical_memory")] - ("physical-memory-offset", Value::String(s)) => { - cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); - } - ("kernel-stack-size", Value::Integer(i)) => { - if i <= 0 { - panic!("`kernel-stack-size` in kernel manifest must be positive"); - } else { - cfg.kernel_stack_size = Some(i as u64); - } - } - (s, _) => { - panic!( - "unknown key '{}' in kernel manifest \ - - you may need to update the bootloader crate", - s - ); - } - } - } -} -#[cfg(feature = "binary")] -fn main() { - use std::{ - env, - fs::{self, File}, - io::Write, - path::{Path, PathBuf}, - process::{self, Command}, - }; - use toml::Value; + // Build the bootsector + build_subproject( + Path::new("src/real/bootsector"), + &[ + "_start", + "real_mode_println", + "no_int13h_extensions", + "dap_load_failed", + ], + "../i386-unknown-none-code16.json", + &target_dir, + &objcopy, + &cargo, + ); - let target = env::var("TARGET").expect("TARGET not set"); - if Path::new(&target) - .file_stem() - .expect("target has no file stem") - != "x86_64-bootloader" - { - panic!("The bootloader must be compiled for the `x86_64-bootloader.json` target."); - } + // Build stage 2 + build_subproject( + Path::new("src/real/stage_2"), + &["second_stage", "v8086_test"], + "../i386-unknown-none-code16.json", + &target_dir, + &objcopy, + &cargo, + ); - let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); - let kernel = PathBuf::from(match env::var("KERNEL") { - Ok(kernel) => kernel, - Err(_) => { - eprintln!( - "The KERNEL environment variable must be set for building the bootloader.\n\n\ - If you use `bootimage` for building you need at least version 0.7.0. You can \ - update `bootimage` by running `cargo install bootimage --force`." - ); - process::exit(1); - } - }); - let kernel_file_name = kernel - .file_name() - .expect("KERNEL has no valid file name") + // Inform cargo that we should rerun this on linker script changes + // + // This is NOT performed by default + println!("cargo:rerun-if-changed=linker.ld"); +} + +fn build_subproject( + subproject_dir: &Path, + global_symbols: &[&str], + target_file_path: &str, + root_target_dir: &Path, + objcopy: &Path, + cargo: &Path, +) { + let subproject_name = subproject_dir + .file_stem() + .expect("Couldn't get subproject name") .to_str() - .expect("kernel file name not valid utf8"); + .expect("Subproject Name is not valid UTF-8"); + let target_file = Path::new(&target_file_path) + .file_stem() + .expect("Couldn't get target file stem"); + + let target_dir = root_target_dir.join(&subproject_name); - // check that the kernel file exists + // We have to export at least 1 symbol assert!( - kernel.exists(), - format!("KERNEL does not exist: {}", kernel.display()) + global_symbols.len() > 0, + "must have at least one global symbol" ); - // get access to llvm tools shipped in the llvm-tools-preview rustup component - let llvm_tools = match llvm_tools::LlvmTools::new() { - Ok(tools) => tools, - Err(llvm_tools::Error::NotFound) => { - eprintln!("Error: llvm-tools not found"); - eprintln!("Maybe the rustup component `llvm-tools-preview` is missing?"); - eprintln!(" Install it through: `rustup component add llvm-tools-preview`"); - process::exit(1); - } - Err(err) => { - eprintln!("Failed to retrieve llvm-tools component: {:?}", err); - process::exit(1); - } - }; + // Use cargo in CARGO environment variable (set on build) + let mut build_cmd = Command::new(cargo); - // check that kernel executable has code in it - let llvm_size = llvm_tools - .tool(&llvm_tools::exe("llvm-size")) - .expect("llvm-size not found in llvm-tools"); - let mut cmd = Command::new(llvm_size); - cmd.arg(&kernel); - let output = cmd.output().expect("failed to run llvm-size"); - let output_str = String::from_utf8_lossy(&output.stdout); - let second_line_opt = output_str.lines().skip(1).next(); - let second_line = second_line_opt.expect("unexpected llvm-size line output"); - let text_size_opt = second_line.split_ascii_whitespace().next(); - let text_size = text_size_opt.expect("unexpected llvm-size output"); - if text_size == "0" { - panic!("Kernel executable has an empty text section. Perhaps the entry point was set incorrectly?\n\n\ - Kernel executable at `{}`\n", kernel.display()); - } + // Build inside the subproject + build_cmd.current_dir(&subproject_dir); + build_cmd.arg("build"); - // strip debug symbols from kernel for faster loading - let stripped_kernel_file_name = format!("kernel_stripped-{}", kernel_file_name); - let stripped_kernel = out_dir.join(&stripped_kernel_file_name); - let objcopy = llvm_tools - .tool(&llvm_tools::exe("llvm-objcopy")) - .expect("llvm-objcopy not found in llvm-tools"); - let mut cmd = Command::new(&objcopy); - cmd.arg("--strip-debug"); - cmd.arg(&kernel); - cmd.arg(&stripped_kernel); - let exit_status = cmd - .status() - .expect("failed to run objcopy to strip debug symbols"); - if !exit_status.success() { - eprintln!("Error: Stripping debug symbols failed"); - process::exit(1); - } + // Build in release mode if we're built in release mode + let build_profile = env::var("PROFILE").expect("Couldn't get cargo build profile"); - // wrap the kernel executable as binary in a new ELF file - let stripped_kernel_file_name_replaced = stripped_kernel_file_name - .replace('-', "_") - .replace('.', "_"); - let kernel_bin = out_dir.join(format!("kernel_bin-{}.o", kernel_file_name)); - let kernel_archive = out_dir.join(format!("libkernel_bin-{}.a", kernel_file_name)); - let mut cmd = Command::new(&objcopy); - cmd.arg("-I").arg("binary"); - cmd.arg("-O").arg("elf64-x86-64"); - cmd.arg("--binary-architecture=i386:x86-64"); - cmd.arg("--rename-section").arg(".data=.kernel"); - cmd.arg("--redefine-sym").arg(format!( - "_binary_{}_start=_kernel_start_addr", - stripped_kernel_file_name_replaced - )); - cmd.arg("--redefine-sym").arg(format!( - "_binary_{}_end=_kernel_end_addr", - stripped_kernel_file_name_replaced - )); - cmd.arg("--redefine-sym").arg(format!( - "_binary_{}_size=_kernel_size", - stripped_kernel_file_name_replaced - )); - cmd.current_dir(&out_dir); - cmd.arg(&stripped_kernel_file_name); - cmd.arg(&kernel_bin); - let exit_status = cmd.status().expect("failed to run objcopy"); - if !exit_status.success() { - eprintln!("Error: Running objcopy failed"); - process::exit(1); + if build_profile == "release" { + build_cmd.arg("--release"); } - // create an archive for linking - let ar = llvm_tools - .tool(&llvm_tools::exe("llvm-ar")) - .unwrap_or_else(|| { - eprintln!("Failed to retrieve llvm-ar component"); - eprint!("This component is available since nightly-2019-03-29,"); - eprintln!("so try updating your toolchain if you're using an older nightly"); - process::exit(1); - }); - let mut cmd = Command::new(ar); - cmd.arg("crs"); - cmd.arg(&kernel_archive); - cmd.arg(&kernel_bin); - let exit_status = cmd.status().expect("failed to run ar"); - if !exit_status.success() { - eprintln!("Error: Running ar failed"); - process::exit(1); - } + // Very verbose (build script output only shows if you use `-vv` or it fails anyway) + build_cmd.arg("-vv"); - // Parse the kernel's Cargo.toml which is given to us by bootimage - let mut bootloader_config = BootloaderConfig::default(); + // Cross-compile core (cargo-xbuild no longer needed) + build_cmd.arg("-Zbuild-std=core"); - match env::var("KERNEL_MANIFEST") { - Err(env::VarError::NotPresent) => { - panic!("The KERNEL_MANIFEST environment variable must be set for building the bootloader.\n\n\ - If you use `bootimage` for building you need at least version 0.7.7. You can \ - update `bootimage` by running `cargo install bootimage --force`."); - } - Err(env::VarError::NotUnicode(_)) => { - panic!("The KERNEL_MANIFEST environment variable contains invalid unicode") - } - Ok(path) => { - println!("cargo:rerun-if-changed={}", path); + // Use calculated target directory + build_cmd.arg(format!("--target-dir={}", &target_dir.display())); - let contents = fs::read_to_string(&path).expect(&format!( - "failed to read kernel manifest file (path: {})", - path - )); + // Use the passed target + build_cmd.arg("--target").arg(target_file_path); - let manifest = contents - .parse::() - .expect("failed to parse kernel's Cargo.toml"); + // Run the command and make sure it succeeds + let build_status = build_cmd.status().expect("Subcrate build failed!"); + assert!(build_status.success(), "Subcrate build failed!"); - let table = manifest - .get("package") - .and_then(|table| table.get("metadata")) - .and_then(|table| table.get("bootloader")) - .and_then(|table| table.as_table()); + // Compute the path to the binary + let binary_dir = target_dir.join(&target_file).join("release"); + let binary_path = binary_dir.join(format!("lib{}.a", &subproject_name)); - if let Some(table) = table { - parse_to_config(&mut bootloader_config, table); - } - } + // Use passed objcopy + let mut objcopy_cmd = Command::new(objcopy); + + // Localize all symbols except those passed + for symbol in global_symbols { + objcopy_cmd.arg("-G").arg(symbol); } - // Configure constants for the bootloader - // We leave some variables as Option rather than hardcoding their defaults so that they - // can be calculated dynamically by the bootloader. - let file_path = out_dir.join("bootloader_config.rs"); - let mut file = File::create(file_path).expect("failed to create bootloader_config.rs"); - file.write_all( - format!( - "const PHYSICAL_MEMORY_OFFSET: Option = {:?}; - const KERNEL_STACK_ADDRESS: Option = {:?}; - const KERNEL_STACK_SIZE: u64 = {}; - const BOOT_INFO_ADDRESS: Option = {:?};", - bootloader_config.physical_memory_offset, - bootloader_config.kernel_stack_address, - bootloader_config.kernel_stack_size.unwrap_or(512), // size is in number of pages - bootloader_config.boot_info_address, - ) - .as_bytes(), - ) - .expect("write to bootloader_config.rs failed"); + // Pass the binary as argument + objcopy_cmd.arg(binary_path); - // pass link arguments to rustc - println!("cargo:rustc-link-search=native={}", out_dir.display()); - println!( - "cargo:rustc-link-lib=static=kernel_bin-{}", - kernel_file_name - ); + // Run the command and make sure it succeeds + let objcopy_status = objcopy_cmd.status().expect("Objcopy failed!"); + assert!(objcopy_status.success(), "Objcopy failed!"); + + // Emit flags to the linker + // + // Staticlibs can't be used as normal dependencies, they have to be linked by a build script + println!("cargo:rustc-link-search=native={}", &binary_dir.display()); + println!("cargo:rustc-link-lib=static={}", &subproject_name); - println!("cargo:rerun-if-env-changed=KERNEL"); - println!("cargo:rerun-if-env-changed=KERNEL_MANIFEST"); - println!("cargo:rerun-if-changed={}", kernel.display()); - println!("cargo:rerun-if-changed=build.rs"); + // Inform cargo to rerun on source changes + // + // Cargo doesn't understand that the subcrates are part of the project because of how we build them, we have to tell it ourselves + println!("cargo:rerun-if-changed={}", &target_file_path); + println!("cargo:rerun-if-changed={}", &subproject_dir.display()); } diff --git a/i8086-bootloader.json b/i8086-bootloader.json new file mode 100644 index 00000000..b818ae42 --- /dev/null +++ b/i8086-bootloader.json @@ -0,0 +1,26 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i386-unknown-none-code16", + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation_model": "static", + "eliminate_frame_pointer": true, + "pre-link-args": { + "ld.lld": [ + "--script=linker.ld" + ] + } +} \ No newline at end of file diff --git a/linker.ld b/linker.ld index 4e58320a..44fbb9d1 100644 --- a/linker.ld +++ b/linker.ld @@ -2,49 +2,55 @@ ENTRY(_start) SECTIONS { . = 0x500; - /* buffer for loading the kernel */ - _kernel_buffer = .; - . += 512; - /* page tables */ - . = ALIGN(0x1000); - __page_table_start = .; - _p4 = .; - . += 0x1000; - _p3 = .; - . += 0x1000; - _p2 = .; - . += 0x1000; - _p1 = .; - . += 0x1000; - __page_table_end = .; - __bootloader_start = .; - _memory_map = .; - . += 0x1000; - _stack_start = .; . = 0x7c00; _stack_end = .; - .bootloader : + .bootsector : { - /* first stage */ - *(.boot-first-stage) + _bootloader_start = .; + + *(.bootstrap) + *bootsector* + } + + /* We write the magic number in the linker script to cause a link-time error if the bootloader overflows the 512 byte limit */ + . = 0x7c00 + 510; + .magic : + { + SHORT(0xaa55) + } + + .rest_of_bootloader : + { + _rest_of_bootloader_start = .; - /* rest of bootloader */ - _rest_of_bootloader_start_addr = .; - *(.boot) - *(.context_switch) *(.text .text.*) - *(.rodata .rodata.*) *(.data .data.*) - *(.got) + *(.rodata .rodata.*) + *(.bss .bss.*) + *(.got .got.plt) + . = ALIGN(512); - _rest_of_bootloader_end_addr = .; - __bootloader_end = .; + + _rest_of_bootloader_end = .; } - .kernel : + /* Cause link time error if bootloader overflows 1MB limit */ + . = 0x100000; + + _bootloader_end = .; + + _protected_mode_stack_start = .; + . += 0x1000; + _protected_mode_stack_end = .; + + /DISCARD/ : { - KEEP(*(.kernel)) + /* + Weird section that always puts itself at the start and causes the binary to be non-bootable + Only seems to appear on some platforms, and we can find no use of it in our circumstances + */ + *(.eh_frame*) } -} +} \ No newline at end of file diff --git a/src/boot_info.rs b/src/boot_info.rs deleted file mode 100644 index f50cf2a5..00000000 --- a/src/boot_info.rs +++ /dev/null @@ -1,31 +0,0 @@ -use core::slice; - -use bootloader::bootinfo::{E820MemoryRegion, MemoryMap, MemoryRegion, MemoryRegionType}; -use usize_conversions::usize_from; -use x86_64::VirtAddr; - -pub(crate) fn create_from(memory_map_addr: VirtAddr, entry_count: u64) -> MemoryMap { - let memory_map_start_ptr = usize_from(memory_map_addr.as_u64()) as *const E820MemoryRegion; - let e820_memory_map = - unsafe { slice::from_raw_parts(memory_map_start_ptr, usize_from(entry_count)) }; - - let mut memory_map = MemoryMap::new(); - for region in e820_memory_map { - memory_map.add_region(MemoryRegion::from(*region)); - } - - memory_map.sort(); - - let mut iter = memory_map.iter_mut().peekable(); - while let Some(region) = iter.next() { - if let Some(next) = iter.peek() { - if region.range.end_frame_number > next.range.start_frame_number - && region.region_type == MemoryRegionType::Usable - { - region.range.end_frame_number = next.range.start_frame_number; - } - } - } - - memory_map -} diff --git a/src/bootinfo/memory_map.rs b/src/bootinfo/memory_map.rs deleted file mode 100644 index bd9eb7fb..00000000 --- a/src/bootinfo/memory_map.rs +++ /dev/null @@ -1,236 +0,0 @@ -use core::fmt; -use core::ops::{Deref, DerefMut}; - -const PAGE_SIZE: u64 = 4096; - -const MAX_MEMORY_MAP_SIZE: usize = 64; - -/// A map of the physical memory regions of the underlying machine. -#[repr(C)] -pub struct MemoryMap { - entries: [MemoryRegion; MAX_MEMORY_MAP_SIZE], - // u64 instead of usize so that the structure layout is platform - // independent - next_entry_index: u64, -} - -#[doc(hidden)] -#[allow(clippy::new_without_default)] -impl MemoryMap { - pub fn new() -> Self { - MemoryMap { - entries: [MemoryRegion::empty(); MAX_MEMORY_MAP_SIZE], - next_entry_index: 0, - } - } - - pub fn add_region(&mut self, region: MemoryRegion) { - assert!( - self.next_entry_index() < MAX_MEMORY_MAP_SIZE, - "too many memory regions in memory map" - ); - self.entries[self.next_entry_index()] = region; - self.next_entry_index += 1; - self.sort(); - } - - pub fn sort(&mut self) { - use core::cmp::Ordering; - - self.entries.sort_unstable_by(|r1, r2| { - if r1.range.is_empty() { - Ordering::Greater - } else if r2.range.is_empty() { - Ordering::Less - } else { - let ordering = r1 - .range - .start_frame_number - .cmp(&r2.range.start_frame_number); - - if ordering == Ordering::Equal { - r1.range.end_frame_number.cmp(&r2.range.end_frame_number) - } else { - ordering - } - } - }); - if let Some(first_zero_index) = self.entries.iter().position(|r| r.range.is_empty()) { - self.next_entry_index = first_zero_index as u64; - } - } - - fn next_entry_index(&self) -> usize { - self.next_entry_index as usize - } -} - -impl Deref for MemoryMap { - type Target = [MemoryRegion]; - - fn deref(&self) -> &Self::Target { - &self.entries[0..self.next_entry_index()] - } -} - -impl DerefMut for MemoryMap { - fn deref_mut(&mut self) -> &mut Self::Target { - let next_index = self.next_entry_index(); - &mut self.entries[0..next_index] - } -} - -impl fmt::Debug for MemoryMap { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.iter()).finish() - } -} - -/// Represents a region of physical memory. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub struct MemoryRegion { - /// The range of frames that belong to the region. - pub range: FrameRange, - /// The type of the region. - pub region_type: MemoryRegionType, -} - -#[doc(hidden)] -impl MemoryRegion { - pub fn empty() -> Self { - MemoryRegion { - range: FrameRange { - start_frame_number: 0, - end_frame_number: 0, - }, - region_type: MemoryRegionType::Empty, - } - } -} - -/// A range of frames with an exclusive upper bound. -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub struct FrameRange { - /// The frame _number_ of the first 4KiB frame in the region. - /// - /// This convert this frame number to a physical address, multiply it with the - /// page size (4KiB). - pub start_frame_number: u64, - /// The frame _number_ of the first 4KiB frame that does no longer belong to the region. - /// - /// This convert this frame number to a physical address, multiply it with the - /// page size (4KiB). - pub end_frame_number: u64, -} - -impl FrameRange { - /// Create a new FrameRange from the passed start_addr and end_addr. - /// - /// The end_addr is exclusive. - pub fn new(start_addr: u64, end_addr: u64) -> Self { - let last_byte = end_addr - 1; - FrameRange { - start_frame_number: start_addr / PAGE_SIZE, - end_frame_number: (last_byte / PAGE_SIZE) + 1, - } - } - - /// Returns true if the frame range contains no frames. - pub fn is_empty(&self) -> bool { - self.start_frame_number == self.end_frame_number - } - - /// Returns the physical start address of the memory region. - pub fn start_addr(&self) -> u64 { - self.start_frame_number * PAGE_SIZE - } - - /// Returns the physical end address of the memory region. - pub fn end_addr(&self) -> u64 { - self.end_frame_number * PAGE_SIZE - } -} - -impl fmt::Debug for FrameRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "FrameRange({:#x}..{:#x})", - self.start_addr(), - self.end_addr() - ) - } -} - -/// Represents possible types for memory regions. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub enum MemoryRegionType { - /// Unused memory, can be freely used by the kernel. - Usable, - /// Memory that is already in use. - InUse, - /// Memory reserved by the hardware. Not usable. - Reserved, - /// ACPI reclaimable memory - AcpiReclaimable, - /// ACPI NVS memory - AcpiNvs, - /// Area containing bad memory - BadMemory, - /// Memory used for loading the kernel. - Kernel, - /// Memory used for the kernel stack. - KernelStack, - /// Memory used for creating page tables. - PageTable, - /// Memory used by the bootloader. - Bootloader, - /// Frame at address zero. - /// - /// (shouldn't be used because it's easy to make mistakes related to null pointers) - FrameZero, - /// An empty region with size 0 - Empty, - /// Memory used for storing the boot information. - BootInfo, - /// Memory used for storing the supplied package - Package, - /// Additional variant to ensure that we can add more variants in the future without - /// breaking backwards compatibility. - #[doc(hidden)] - NonExhaustive, -} - -#[doc(hidden)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub struct E820MemoryRegion { - pub start_addr: u64, - pub len: u64, - pub region_type: u32, - pub acpi_extended_attributes: u32, -} - -impl From for MemoryRegion { - fn from(region: E820MemoryRegion) -> MemoryRegion { - let region_type = match region.region_type { - 1 => MemoryRegionType::Usable, - 2 => MemoryRegionType::Reserved, - 3 => MemoryRegionType::AcpiReclaimable, - 4 => MemoryRegionType::AcpiNvs, - 5 => MemoryRegionType::BadMemory, - t => panic!("invalid region type {}", t), - }; - MemoryRegion { - range: FrameRange::new(region.start_addr, region.start_addr + region.len), - region_type, - } - } -} - -extern "C" { - fn _improper_ctypes_check(_boot_info: MemoryMap); -} diff --git a/src/bootinfo/mod.rs b/src/bootinfo/mod.rs deleted file mode 100644 index 16f6c4c1..00000000 --- a/src/bootinfo/mod.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Provides boot information to the kernel. - -#![deny(improper_ctypes)] - -pub use self::memory_map::*; - -mod memory_map; - -/// This structure represents the information that the bootloader passes to the kernel. -/// -/// The information is passed as an argument to the entry point: -/// -/// ```ignore -/// pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { -/// // […] -/// } -/// ``` -/// -/// Note that no type checking occurs for the entry point function, so be careful to -/// use the correct argument types. To ensure that the entry point function has the correct -/// signature, use the [`entry_point`] macro. -#[derive(Debug)] -#[repr(C)] -pub struct BootInfo { - /// A map of the physical memory regions of the underlying machine. - /// - /// The bootloader queries this information from the BIOS/UEFI firmware and translates this - /// information to Rust types. It also marks any memory regions that the bootloader uses in - /// the memory map before passing it to the kernel. Regions marked as usable can be freely - /// used by the kernel. - pub memory_map: MemoryMap, - /// The virtual address of the recursively mapped level 4 page table. - #[cfg(feature = "recursive_page_table")] - pub recursive_page_table_addr: u64, - /// The offset into the virtual address space where the physical memory is mapped. - /// - /// Physical addresses can be converted to virtual addresses by adding this offset to them. - /// - /// The mapping of the physical memory allows to access arbitrary physical frames. Accessing - /// frames that are also mapped at other virtual addresses can easily break memory safety and - /// cause undefined behavior. Only frames reported as `USABLE` by the memory map in the `BootInfo` - /// can be safely accessed. - #[cfg(feature = "map_physical_memory")] - pub physical_memory_offset: u64, - tls_template: TlsTemplate, - _non_exhaustive: u8, // `()` is not FFI safe -} - -impl BootInfo { - /// Create a new boot information structure. This function is only for internal purposes. - #[allow(unused_variables)] - #[doc(hidden)] - pub fn new( - memory_map: MemoryMap, - tls_template: Option, - recursive_page_table_addr: u64, - physical_memory_offset: u64, - ) -> Self { - let tls_template = tls_template.unwrap_or(TlsTemplate { - start_addr: 0, - file_size: 0, - mem_size: 0, - }); - BootInfo { - memory_map, - tls_template, - #[cfg(feature = "recursive_page_table")] - recursive_page_table_addr, - #[cfg(feature = "map_physical_memory")] - physical_memory_offset, - _non_exhaustive: 0, - } - } - - /// Returns information about the thread local storage segment of the kernel. - /// - /// Returns `None` if the kernel has no thread local storage segment. - /// - /// (The reason this is a method instead of a normal field is that `Option` - /// is not FFI-safe.) - pub fn tls_template(&self) -> Option { - if self.tls_template.mem_size > 0 { - Some(self.tls_template) - } else { - None - } - } -} - -/// Information about the thread local storage (TLS) template. -/// -/// This template can be used to set up thread local storage for threads. For -/// each thread, a new memory location of size `mem_size` must be initialized. -/// Then the first `file_size` bytes of this template needs to be copied to the -/// location. The additional `mem_size - file_size` bytes must be initialized with -/// zero. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub struct TlsTemplate { - /// The virtual start address of the thread local storage template. - pub start_addr: u64, - /// The number of data bytes in the template. - /// - /// Corresponds to the length of the `.tdata` section. - pub file_size: u64, - /// The total number of bytes that the TLS segment should have in memory. - /// - /// Corresponds to the combined length of the `.tdata` and `.tbss` sections. - pub mem_size: u64, -} - -extern "C" { - fn _improper_ctypes_check(_boot_info: BootInfo); -} diff --git a/src/e820.s b/src/e820.s deleted file mode 100644 index 114de231..00000000 --- a/src/e820.s +++ /dev/null @@ -1,55 +0,0 @@ -.section .boot, "awx" -.intel_syntax noprefix -.code16 - -# From http://wiki.osdev.org/Detecting_Memory_(x86)#Getting_an_E820_Memory_Map - -# use the INT 0x15, eax= 0xE820 BIOS function to get a memory map -# inputs: es:di -> destination buffer for 24 byte entries -# outputs: bp = entry count, trashes all registers except esi -do_e820: - xor ebx, ebx # ebx must be 0 to start - xor bp, bp # keep an entry count in bp - mov edx, 0x0534D4150 # Place "SMAP" into edx - mov eax, 0xe820 - mov dword ptr es:[di + 20], 1 # force a valid ACPI 3.X entry - mov ecx, 24 # ask for 24 bytes - int 0x15 - jc .failed # carry set on first call means "unsupported function" - mov edx, 0x0534D4150 # Some BIOSes apparently trash this register? - cmp eax, edx # on success, eax must have been reset to "SMAP" - jne .failed - test ebx, ebx # ebx = 0 implies list is only 1 entry long (worthless) - je .failed - jmp .jmpin -.e820lp: - mov eax, 0xe820 # eax, ecx get trashed on every int 0x15 call - mov dword ptr es:[di + 20], 1 # force a valid ACPI 3.X entry - mov ecx, 24 # ask for 24 bytes again - int 0x15 - jc .e820f # carry set means "end of list already reached" - mov edx, 0x0534D4150 # repair potentially trashed register -.jmpin: - jcxz .skipent # skip any 0 length entries - cmp cl, 20 # got a 24 byte ACPI 3.X response? - jbe .notext - test byte ptr es:[di + 20], 1 # if so: is the "ignore this data" bit clear? - je .skipent -.notext: - mov ecx, es:[di + 8] # get lower uint32_t of memory region length - or ecx, es:[di + 12] # "or" it with upper uint32_t to test for zero - jz .skipent # if length uint64_t is 0, skip entry - inc bp # got a good entry: ++count, move to next storage spot - add di, 24 -.skipent: - test ebx, ebx # if ebx resets to 0, list is complete - jne .e820lp -.e820f: - mov [mmap_ent], bp # store the entry count - clc # there is "jc" on end of list to this point, so the carry must be cleared - ret -.failed: - stc # "function unsupported" error exit - ret - -mmap_ent: .word 0 diff --git a/src/frame_allocator.rs b/src/frame_allocator.rs deleted file mode 100644 index 5630702f..00000000 --- a/src/frame_allocator.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::{frame_range, phys_frame_range}; -use bootloader::bootinfo::{MemoryMap, MemoryRegion, MemoryRegionType}; -use x86_64::structures::paging::{frame::PhysFrameRange, PhysFrame, UnusedPhysFrame}; - -pub(crate) struct FrameAllocator<'a> { - pub memory_map: &'a mut MemoryMap, -} - -impl<'a> FrameAllocator<'a> { - pub(crate) fn allocate_frame( - &mut self, - region_type: MemoryRegionType, - ) -> Option { - // try to find an existing region of same type that can be enlarged - let mut iter = self.memory_map.iter_mut().peekable(); - while let Some(region) = iter.next() { - if region.region_type == region_type { - if let Some(next) = iter.peek() { - if next.range.start_frame_number == region.range.end_frame_number - && next.region_type == MemoryRegionType::Usable - && !next.range.is_empty() - { - let frame = - unsafe { UnusedPhysFrame::new(phys_frame_range(region.range).end) }; - region.range.end_frame_number += 1; - iter.next().unwrap().range.start_frame_number += 1; - return Some(frame); - } - } - } - } - - fn split_usable_region<'a, I>(iter: &mut I) -> Option<(UnusedPhysFrame, PhysFrameRange)> - where - I: Iterator, - { - for region in iter { - if region.region_type != MemoryRegionType::Usable { - continue; - } - if region.range.is_empty() { - continue; - } - - let physframe = phys_frame_range(region.range).start; - let unused_frame = unsafe { UnusedPhysFrame::new(physframe) }; - region.range.start_frame_number += 1; - return Some((unused_frame, PhysFrame::range(physframe, physframe + 1))); - } - None - } - - let result = if region_type == MemoryRegionType::PageTable { - // prevent fragmentation when page tables are allocated in between - split_usable_region(&mut self.memory_map.iter_mut().rev()) - } else { - split_usable_region(&mut self.memory_map.iter_mut()) - }; - - if let Some((frame, range)) = result { - self.memory_map.add_region(MemoryRegion { - range: frame_range(range), - region_type, - }); - Some(frame) - } else { - None - } - } - - /// Marks the passed region in the memory map. - /// - /// Panics if a non-usable region (e.g. a reserved region) overlaps with the passed region. - pub(crate) fn mark_allocated_region(&mut self, region: MemoryRegion) { - for r in self.memory_map.iter_mut() { - if region.range.start_frame_number >= r.range.end_frame_number { - continue; - } - if region.range.end_frame_number <= r.range.start_frame_number { - continue; - } - - if r.region_type != MemoryRegionType::Usable { - panic!( - "region {:x?} overlaps with non-usable region {:x?}", - region, r - ); - } - - if region.range.start_frame_number == r.range.start_frame_number { - if region.range.end_frame_number < r.range.end_frame_number { - // Case: (r = `r`, R = `region`) - // ----rrrrrrrrrrr---- - // ----RRRR----------- - r.range.start_frame_number = region.range.end_frame_number; - self.memory_map.add_region(region); - } else { - // Case: (r = `r`, R = `region`) - // ----rrrrrrrrrrr---- - // ----RRRRRRRRRRRRRR- - *r = region; - } - } else if region.range.start_frame_number > r.range.start_frame_number { - if region.range.end_frame_number < r.range.end_frame_number { - // Case: (r = `r`, R = `region`) - // ----rrrrrrrrrrr---- - // ------RRRR--------- - let mut behind_r = r.clone(); - behind_r.range.start_frame_number = region.range.end_frame_number; - r.range.end_frame_number = region.range.start_frame_number; - self.memory_map.add_region(behind_r); - self.memory_map.add_region(region); - } else { - // Case: (r = `r`, R = `region`) - // ----rrrrrrrrrrr---- - // -----------RRRR---- or - // -------------RRRR-- - r.range.end_frame_number = region.range.start_frame_number; - self.memory_map.add_region(region); - } - } else { - // Case: (r = `r`, R = `region`) - // ----rrrrrrrrrrr---- - // --RRRR------------- - r.range.start_frame_number = region.range.end_frame_number; - self.memory_map.add_region(region); - } - return; - } - panic!("region {:x?} is not a usable memory region", region); - } -} diff --git a/src/level4_entries.rs b/src/level4_entries.rs deleted file mode 100644 index 60ace846..00000000 --- a/src/level4_entries.rs +++ /dev/null @@ -1,45 +0,0 @@ -use core::convert::TryInto; -use fixedvec::FixedVec; -use x86_64::{ - structures::paging::{Page, PageTableIndex}, - VirtAddr, -}; -use xmas_elf::program::ProgramHeader64; - -pub struct UsedLevel4Entries { - entry_state: [bool; 512], // whether an entry is in use by the kernel -} - -impl UsedLevel4Entries { - pub fn new(segments: &FixedVec) -> Self { - let mut used = UsedLevel4Entries { - entry_state: [false; 512], - }; - - used.entry_state[0] = true; // TODO: Can we do this dynamically? - - for segment in segments { - let start_page: Page = Page::containing_address(VirtAddr::new(segment.virtual_addr)); - let end_page: Page = - Page::containing_address(VirtAddr::new(segment.virtual_addr + segment.mem_size)); - - for p4_index in u64::from(start_page.p4_index())..=u64::from(end_page.p4_index()) { - used.entry_state[p4_index as usize] = true; - } - } - - used - } - - pub fn get_free_entry(&mut self) -> PageTableIndex { - let (idx, entry) = self - .entry_state - .iter_mut() - .enumerate() - .find(|(_, &mut entry)| entry == false) - .expect("no usable level 4 entries found"); - - *entry = true; - PageTableIndex::new(idx.try_into().unwrap()) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 90d8eebf..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! This library part of the bootloader allows kernels to retrieve information from the bootloader. -//! -//! To combine your kernel with the bootloader crate you need a tool such -//! as [`bootimage`](https://github.com/rust-osdev/bootimage). See the -//! [_Writing an OS in Rust_](https://os.phil-opp.com/minimal-rust-kernel/#creating-a-bootimage) -//! blog for an explanation. - -#![no_std] -#![warn(missing_docs)] - -pub use crate::bootinfo::BootInfo; - -pub mod bootinfo; - -#[cfg(target_arch = "x86")] -compile_error!( - "This crate currently does not support 32-bit protected mode. \ - See https://github.com/rust-osdev/bootloader/issues/70 for more information." -); - -#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] -compile_error!("This crate only supports the x86_64 architecture."); - -/// Defines the entry point function. -/// -/// The function must have the signature `fn(&'static BootInfo) -> !`. -/// -/// This macro just creates a function named `_start`, which the linker will use as the entry -/// point. The advantage of using this macro instead of providing an own `_start` function is -/// that the macro ensures that the function and argument types are correct. -#[macro_export] -macro_rules! entry_point { - ($path:path) => { - #[export_name = "_start"] - pub extern "C" fn __impl_start(boot_info: &'static $crate::bootinfo::BootInfo) -> ! { - // validate the signature of the program entry point - let f: fn(&'static $crate::bootinfo::BootInfo) -> ! = $path; - - f(boot_info) - } - }; -} diff --git a/src/main.rs b/src/main.rs index ce7bcc50..606b78e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,403 +1,7 @@ -#![feature(lang_items)] -#![feature(global_asm)] -#![feature(step_trait)] -#![feature(llvm_asm)] -#![feature(nll)] -#![feature(const_fn)] #![no_std] #![no_main] -#[cfg(not(target_os = "none"))] -compile_error!("The bootloader crate must be compiled for the `x86_64-bootloader.json` target"); +mod panic; -use bootloader::bootinfo::{BootInfo, FrameRange}; -use core::convert::TryInto; -use core::panic::PanicInfo; -use core::{mem, slice}; -use fixedvec::alloc_stack; -use usize_conversions::usize_from; -use x86_64::instructions::tlb; -use x86_64::structures::paging::{ - frame::PhysFrameRange, page_table::PageTableEntry, Mapper, Page, PageTable, PageTableFlags, - PageTableIndex, PhysFrame, RecursivePageTable, Size2MiB, Size4KiB, UnusedPhysFrame, -}; -use x86_64::{PhysAddr, VirtAddr}; - -// The bootloader_config.rs file contains some configuration constants set by the build script: -// PHYSICAL_MEMORY_OFFSET: The offset into the virtual address space where the physical memory -// is mapped if the `map_physical_memory` feature is activated. -// -// KERNEL_STACK_ADDRESS: The virtual address of the kernel stack. -// -// KERNEL_STACK_SIZE: The number of pages in the kernel stack. -include!(concat!(env!("OUT_DIR"), "/bootloader_config.rs")); - -global_asm!(include_str!("stage_1.s")); -global_asm!(include_str!("stage_2.s")); -global_asm!(include_str!("e820.s")); -global_asm!(include_str!("stage_3.s")); - -#[cfg(feature = "vga_320x200")] -global_asm!(include_str!("video_mode/vga_320x200.s")); -#[cfg(not(feature = "vga_320x200"))] -global_asm!(include_str!("video_mode/vga_text_80x25.s")); - -unsafe fn context_switch(boot_info: VirtAddr, entry_point: VirtAddr, stack_pointer: VirtAddr) -> ! { - llvm_asm!("call $1; ${:private}.spin.${:uid}: jmp ${:private}.spin.${:uid}" :: - "{rsp}"(stack_pointer), "r"(entry_point), "{rdi}"(boot_info) :: "intel"); - ::core::hint::unreachable_unchecked() -} - -mod boot_info; -mod frame_allocator; -mod level4_entries; -mod page_table; -mod printer; -#[cfg(feature = "sse")] -mod sse; - -pub struct IdentityMappedAddr(PhysAddr); - -impl IdentityMappedAddr { - fn phys(&self) -> PhysAddr { - self.0 - } - - fn virt(&self) -> VirtAddr { - VirtAddr::new(self.0.as_u64()) - } - - fn as_u64(&self) -> u64 { - self.0.as_u64() - } -} - -// Symbols defined in `linker.ld` -extern "C" { - static mmap_ent: usize; - static _memory_map: usize; - static _kernel_start_addr: usize; - static _kernel_end_addr: usize; - static _kernel_size: usize; - static __page_table_start: usize; - static __page_table_end: usize; - static __bootloader_end: usize; - static __bootloader_start: usize; - static _p4: usize; -} - -#[no_mangle] -pub unsafe extern "C" fn stage_4() -> ! { - // Set stack segment - llvm_asm!("mov bx, 0x0 - mov ss, bx" ::: "bx" : "intel"); - - let kernel_start = 0x400000; - let kernel_size = &_kernel_size as *const _ as u64; - let memory_map_addr = &_memory_map as *const _ as u64; - let memory_map_entry_count = (mmap_ent & 0xff) as u64; // Extract lower 8 bits - let page_table_start = &__page_table_start as *const _ as u64; - let page_table_end = &__page_table_end as *const _ as u64; - let bootloader_start = &__bootloader_start as *const _ as u64; - let bootloader_end = &__bootloader_end as *const _ as u64; - let p4_physical = &_p4 as *const _ as u64; - - bootloader_main( - IdentityMappedAddr(PhysAddr::new(kernel_start)), - kernel_size, - VirtAddr::new(memory_map_addr), - memory_map_entry_count, - PhysAddr::new(page_table_start), - PhysAddr::new(page_table_end), - PhysAddr::new(bootloader_start), - PhysAddr::new(bootloader_end), - PhysAddr::new(p4_physical), - ) -} - -fn bootloader_main( - kernel_start: IdentityMappedAddr, - kernel_size: u64, - memory_map_addr: VirtAddr, - memory_map_entry_count: u64, - page_table_start: PhysAddr, - page_table_end: PhysAddr, - bootloader_start: PhysAddr, - bootloader_end: PhysAddr, - p4_physical: PhysAddr, -) -> ! { - use bootloader::bootinfo::{MemoryRegion, MemoryRegionType}; - use fixedvec::FixedVec; - use xmas_elf::program::{ProgramHeader, ProgramHeader64}; - - printer::Printer.clear_screen(); - - let mut memory_map = boot_info::create_from(memory_map_addr, memory_map_entry_count); - - let max_phys_addr = memory_map - .iter() - .map(|r| r.range.end_addr()) - .max() - .expect("no physical memory regions found"); - - // Extract required information from the ELF file. - let mut preallocated_space = alloc_stack!([ProgramHeader64; 32]); - let mut segments = FixedVec::new(&mut preallocated_space); - let entry_point; - { - let kernel_start_ptr = usize_from(kernel_start.as_u64()) as *const u8; - let kernel = unsafe { slice::from_raw_parts(kernel_start_ptr, usize_from(kernel_size)) }; - let elf_file = xmas_elf::ElfFile::new(kernel).unwrap(); - xmas_elf::header::sanity_check(&elf_file).unwrap(); - - entry_point = elf_file.header.pt2.entry_point(); - - for program_header in elf_file.program_iter() { - match program_header { - ProgramHeader::Ph64(header) => segments - .push(*header) - .expect("does not support more than 32 program segments"), - ProgramHeader::Ph32(_) => panic!("does not support 32 bit elf files"), - } - } - } - - // Mark used virtual addresses - let mut level4_entries = level4_entries::UsedLevel4Entries::new(&segments); - - // Enable support for the no-execute bit in page tables. - enable_nxe_bit(); - - // Create a recursive page table entry - let recursive_index = PageTableIndex::new(level4_entries.get_free_entry().try_into().unwrap()); - let mut entry = PageTableEntry::new(); - entry.set_addr( - p4_physical, - PageTableFlags::PRESENT | PageTableFlags::WRITABLE, - ); - - // Write the recursive entry into the page table - let page_table = unsafe { &mut *(p4_physical.as_u64() as *mut PageTable) }; - page_table[recursive_index] = entry; - tlb::flush_all(); - - let recursive_page_table_addr = Page::from_page_table_indices( - recursive_index, - recursive_index, - recursive_index, - recursive_index, - ) - .start_address(); - let page_table = unsafe { &mut *(recursive_page_table_addr.as_mut_ptr()) }; - let mut rec_page_table = - RecursivePageTable::new(page_table).expect("recursive page table creation failed"); - - // Create a frame allocator, which marks allocated frames as used in the memory map. - let mut frame_allocator = frame_allocator::FrameAllocator { - memory_map: &mut memory_map, - }; - - // Mark already used memory areas in frame allocator. - { - let zero_frame: PhysFrame = PhysFrame::from_start_address(PhysAddr::new(0)).unwrap(); - frame_allocator.mark_allocated_region(MemoryRegion { - range: frame_range(PhysFrame::range(zero_frame, zero_frame + 1)), - region_type: MemoryRegionType::FrameZero, - }); - let bootloader_start_frame = PhysFrame::containing_address(bootloader_start); - let bootloader_end_frame = PhysFrame::containing_address(bootloader_end - 1u64); - let bootloader_memory_area = - PhysFrame::range(bootloader_start_frame, bootloader_end_frame + 1); - frame_allocator.mark_allocated_region(MemoryRegion { - range: frame_range(bootloader_memory_area), - region_type: MemoryRegionType::Bootloader, - }); - let kernel_start_frame = PhysFrame::containing_address(kernel_start.phys()); - let kernel_end_frame = - PhysFrame::containing_address(kernel_start.phys() + kernel_size - 1u64); - let kernel_memory_area = PhysFrame::range(kernel_start_frame, kernel_end_frame + 1); - frame_allocator.mark_allocated_region(MemoryRegion { - range: frame_range(kernel_memory_area), - region_type: MemoryRegionType::Kernel, - }); - let page_table_start_frame = PhysFrame::containing_address(page_table_start); - let page_table_end_frame = PhysFrame::containing_address(page_table_end - 1u64); - let page_table_memory_area = - PhysFrame::range(page_table_start_frame, page_table_end_frame + 1); - frame_allocator.mark_allocated_region(MemoryRegion { - range: frame_range(page_table_memory_area), - region_type: MemoryRegionType::PageTable, - }); - } - - // Unmap the ELF file. - let kernel_start_page: Page = Page::containing_address(kernel_start.virt()); - let kernel_end_page: Page = - Page::containing_address(kernel_start.virt() + kernel_size - 1u64); - for page in Page::range_inclusive(kernel_start_page, kernel_end_page) { - rec_page_table.unmap(page).expect("dealloc error").1.flush(); - } - - // Map a page for the boot info structure - let boot_info_page = { - let page: Page = match BOOT_INFO_ADDRESS { - Some(addr) => Page::containing_address(VirtAddr::new(addr)), - None => Page::from_page_table_indices( - level4_entries.get_free_entry(), - PageTableIndex::new(0), - PageTableIndex::new(0), - PageTableIndex::new(0), - ), - }; - let frame = frame_allocator - .allocate_frame(MemoryRegionType::BootInfo) - .expect("frame allocation failed"); - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - unsafe { - page_table::map_page( - page, - frame, - flags, - &mut rec_page_table, - &mut frame_allocator, - ) - } - .expect("Mapping of bootinfo page failed") - .flush(); - page - }; - - // If no kernel stack address is provided, map the kernel stack after the boot info page - let kernel_stack_address = match KERNEL_STACK_ADDRESS { - Some(addr) => Page::containing_address(VirtAddr::new(addr)), - None => boot_info_page + 1, - }; - - // Map kernel segments. - let kernel_memory_info = page_table::map_kernel( - kernel_start.phys(), - kernel_stack_address, - KERNEL_STACK_SIZE, - &segments, - &mut rec_page_table, - &mut frame_allocator, - ) - .expect("kernel mapping failed"); - - let physical_memory_offset = if cfg!(feature = "map_physical_memory") { - let physical_memory_offset = PHYSICAL_MEMORY_OFFSET.unwrap_or_else(|| { - // If offset not manually provided, find a free p4 entry and map memory here. - // One level 4 entry spans 2^48/512 bytes (over 500gib) so this should suffice. - assert!(max_phys_addr < (1 << 48) / 512); - Page::from_page_table_indices_1gib( - level4_entries.get_free_entry(), - PageTableIndex::new(0), - ) - .start_address() - .as_u64() - }); - - let virt_for_phys = - |phys: PhysAddr| -> VirtAddr { VirtAddr::new(phys.as_u64() + physical_memory_offset) }; - - let start_frame = PhysFrame::::containing_address(PhysAddr::new(0)); - let end_frame = PhysFrame::::containing_address(PhysAddr::new(max_phys_addr)); - - for frame in PhysFrame::range_inclusive(start_frame, end_frame) { - let page = Page::containing_address(virt_for_phys(frame.start_address())); - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - unsafe { - page_table::map_page( - page, - UnusedPhysFrame::new(frame), - flags, - &mut rec_page_table, - &mut frame_allocator, - ) - } - .expect("Mapping of bootinfo page failed") - .flush(); - } - - physical_memory_offset - } else { - 0 // Value is unused by BootInfo::new, so this doesn't matter - }; - - // Construct boot info structure. - let mut boot_info = BootInfo::new( - memory_map, - kernel_memory_info.tls_segment, - recursive_page_table_addr.as_u64(), - physical_memory_offset, - ); - boot_info.memory_map.sort(); - - // Write boot info to boot info page. - let boot_info_addr = boot_info_page.start_address(); - unsafe { boot_info_addr.as_mut_ptr::().write(boot_info) }; - - // Make sure that the kernel respects the write-protection bits, even when in ring 0. - enable_write_protect_bit(); - - if cfg!(not(feature = "recursive_page_table")) { - // unmap recursive entry - rec_page_table - .unmap(Page::::containing_address( - recursive_page_table_addr, - )) - .expect("error deallocating recursive entry") - .1 - .flush(); - mem::drop(rec_page_table); - } - - #[cfg(feature = "sse")] - sse::enable_sse(); - - let entry_point = VirtAddr::new(entry_point); - unsafe { context_switch(boot_info_addr, entry_point, kernel_memory_info.stack_end) }; -} - -fn enable_nxe_bit() { - use x86_64::registers::control::{Efer, EferFlags}; - unsafe { Efer::update(|efer| *efer |= EferFlags::NO_EXECUTE_ENABLE) } -} - -fn enable_write_protect_bit() { - use x86_64::registers::control::{Cr0, Cr0Flags}; - unsafe { Cr0::update(|cr0| *cr0 |= Cr0Flags::WRITE_PROTECT) }; -} - -#[panic_handler] #[no_mangle] -pub fn panic(info: &PanicInfo) -> ! { - use core::fmt::Write; - write!(printer::Printer, "{}", info).unwrap(); - loop {} -} - -#[lang = "eh_personality"] -#[no_mangle] -pub extern "C" fn eh_personality() { - loop {} -} - -#[no_mangle] -pub extern "C" fn _Unwind_Resume() { - loop {} -} - -fn phys_frame_range(range: FrameRange) -> PhysFrameRange { - PhysFrameRange { - start: PhysFrame::from_start_address(PhysAddr::new(range.start_addr())).unwrap(), - end: PhysFrame::from_start_address(PhysAddr::new(range.end_addr())).unwrap(), - } -} - -fn frame_range(range: PhysFrameRange) -> FrameRange { - FrameRange::new( - range.start.start_address().as_u64(), - range.end.start_address().as_u64(), - ) -} +fn bootloader_no_optimize() {} diff --git a/src/page_table.rs b/src/page_table.rs deleted file mode 100644 index 8f943afb..00000000 --- a/src/page_table.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::frame_allocator::FrameAllocator; -use bootloader::bootinfo::MemoryRegionType; -use bootloader::bootinfo::TlsTemplate; -use fixedvec::FixedVec; -use x86_64::structures::paging::mapper::{MapToError, MapperFlush, UnmapError}; -use x86_64::structures::paging::{ - self, Mapper, Page, PageSize, PageTableFlags, PhysFrame, RecursivePageTable, Size4KiB, - UnusedPhysFrame, -}; -use x86_64::{align_up, PhysAddr, VirtAddr}; -use xmas_elf::program::{self, ProgramHeader64}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MemoryInfo { - pub stack_end: VirtAddr, - pub tls_segment: Option, -} - -#[derive(Debug)] -pub enum MapKernelError { - Mapping(MapToError), - MultipleTlsSegments, -} - -impl From for MapKernelError { - fn from(e: MapToError) -> Self { - MapKernelError::Mapping(e) - } -} - -pub(crate) fn map_kernel( - kernel_start: PhysAddr, - stack_start: Page, - stack_size: u64, - segments: &FixedVec, - page_table: &mut RecursivePageTable, - frame_allocator: &mut FrameAllocator, -) -> Result { - let mut tls_segment = None; - for segment in segments { - let tls = map_segment(segment, kernel_start, page_table, frame_allocator)?; - if let Some(tls) = tls { - if tls_segment.replace(tls).is_some() { - return Err(MapKernelError::MultipleTlsSegments); - } - } - } - - // Create a stack - let stack_start = stack_start + 1; // Leave the first page unmapped as a 'guard page' - let stack_end = stack_start + stack_size; // stack_size is in pages - - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - let region_type = MemoryRegionType::KernelStack; - - for page in Page::range(stack_start, stack_end) { - let frame = frame_allocator - .allocate_frame(region_type) - .ok_or(MapToError::FrameAllocationFailed)?; - unsafe { map_page(page, frame, flags, page_table, frame_allocator)? }.flush(); - } - - Ok(MemoryInfo { - stack_end: stack_end.start_address(), - tls_segment, - }) -} - -pub(crate) fn map_segment( - segment: &ProgramHeader64, - kernel_start: PhysAddr, - page_table: &mut RecursivePageTable, - frame_allocator: &mut FrameAllocator, -) -> Result, MapToError> { - let typ = segment.get_type().unwrap(); - match typ { - program::Type::Load => { - let mem_size = segment.mem_size; - let file_size = segment.file_size; - let file_offset = segment.offset; - let phys_start_addr = kernel_start + file_offset; - let virt_start_addr = VirtAddr::new(segment.virtual_addr); - - let start_page: Page = Page::containing_address(virt_start_addr); - let start_frame = PhysFrame::containing_address(phys_start_addr); - let end_frame = PhysFrame::containing_address(phys_start_addr + file_size - 1u64); - - let flags = segment.flags; - let mut page_table_flags = PageTableFlags::PRESENT; - if !flags.is_execute() { - page_table_flags |= PageTableFlags::NO_EXECUTE - }; - if flags.is_write() { - page_table_flags |= PageTableFlags::WRITABLE - }; - - for frame in PhysFrame::range_inclusive(start_frame, end_frame) { - let offset = frame - start_frame; - let page = start_page + offset; - unsafe { - map_page( - page, - UnusedPhysFrame::new(frame), - page_table_flags, - page_table, - frame_allocator, - )? - } - .flush(); - } - - if mem_size > file_size { - // .bss section (or similar), which needs to be zeroed - let zero_start = virt_start_addr + file_size; - let zero_end = virt_start_addr + mem_size; - if zero_start.as_u64() & 0xfff != 0 { - // A part of the last mapped frame needs to be zeroed. This is - // not possible since it could already contains parts of the next - // segment. Thus, we need to copy it before zeroing. - - // TODO: search for a free page dynamically - let temp_page: Page = Page::containing_address(VirtAddr::new(0xfeeefeee000)); - let new_frame = frame_allocator - .allocate_frame(MemoryRegionType::Kernel) - .ok_or(MapToError::FrameAllocationFailed)?; - unsafe { - map_page( - temp_page.clone(), - UnusedPhysFrame::new(new_frame.clone()), - page_table_flags, - page_table, - frame_allocator, - )? - } - .flush(); - - type PageArray = [u64; Size4KiB::SIZE as usize / 8]; - - let last_page = Page::containing_address(virt_start_addr + file_size - 1u64); - let last_page_ptr = last_page.start_address().as_ptr::(); - let temp_page_ptr = temp_page.start_address().as_mut_ptr::(); - - unsafe { - // copy contents - temp_page_ptr.write(last_page_ptr.read()); - } - - // remap last page - if let Err(e) = page_table.unmap(last_page.clone()) { - return Err(match e { - UnmapError::ParentEntryHugePage => MapToError::ParentEntryHugePage, - UnmapError::PageNotMapped => unreachable!(), - UnmapError::InvalidFrameAddress(_) => unreachable!(), - }); - } - - unsafe { - map_page( - last_page, - new_frame, - page_table_flags, - page_table, - frame_allocator, - )? - } - .flush(); - } - - // Map additional frames. - let start_page: Page = Page::containing_address(VirtAddr::new(align_up( - zero_start.as_u64(), - Size4KiB::SIZE, - ))); - let end_page = Page::containing_address(zero_end); - for page in Page::range_inclusive(start_page, end_page) { - let frame = frame_allocator - .allocate_frame(MemoryRegionType::Kernel) - .ok_or(MapToError::FrameAllocationFailed)?; - unsafe { - map_page(page, frame, page_table_flags, page_table, frame_allocator)? - } - .flush(); - } - - // zero - for offset in file_size..mem_size { - let addr = virt_start_addr + offset; - unsafe { addr.as_mut_ptr::().write(0) }; - } - } - - Ok(None) - } - program::Type::Tls => Ok(Some(TlsTemplate { - start_addr: segment.virtual_addr, - mem_size: segment.mem_size, - file_size: segment.file_size, - })), - _ => Ok(None), - } -} - -pub(crate) unsafe fn map_page<'a, S>( - page: Page, - phys_frame: UnusedPhysFrame, - flags: PageTableFlags, - page_table: &mut RecursivePageTable<'a>, - frame_allocator: &mut FrameAllocator, -) -> Result, MapToError> -where - S: PageSize, - RecursivePageTable<'a>: Mapper, -{ - struct PageTableAllocator<'a, 'b: 'a>(&'a mut FrameAllocator<'b>); - - unsafe impl<'a, 'b> paging::FrameAllocator for PageTableAllocator<'a, 'b> { - fn allocate_frame(&mut self) -> Option> { - self.0.allocate_frame(MemoryRegionType::PageTable) - } - } - - page_table.map_to( - page, - phys_frame, - flags, - &mut PageTableAllocator(frame_allocator), - ) -} diff --git a/src/panic.rs b/src/panic.rs new file mode 100644 index 00000000..d72cd0d1 --- /dev/null +++ b/src/panic.rs @@ -0,0 +1,11 @@ +use core::panic::PanicInfo; +use shared::{println, instructions}; + +#[panic_handler] +pub fn panic(info: &PanicInfo) -> ! { + println!("[Panic] {}", info); + + loop { + instructions::hlt() + } +} diff --git a/src/printer/mod.rs b/src/printer/mod.rs deleted file mode 100644 index 726bb765..00000000 --- a/src/printer/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(not(feature = "vga_320x200"))] -pub use self::vga_text_80x25::*; - -#[cfg(feature = "vga_320x200")] -pub use self::vga_320x200::*; - -#[cfg(feature = "vga_320x200")] -mod vga_320x200; - -#[cfg(not(feature = "vga_320x200"))] -mod vga_text_80x25; diff --git a/src/printer/vga_320x200.rs b/src/printer/vga_320x200.rs deleted file mode 100644 index fb1e36af..00000000 --- a/src/printer/vga_320x200.rs +++ /dev/null @@ -1,80 +0,0 @@ -use core::fmt::{Result, Write}; -use core::sync::atomic::{AtomicUsize, Ordering}; - -const VGA_BUFFER: *mut u8 = 0xa0000 as *mut _; -const SCREEN_WIDTH: usize = 320; -const SCREEN_HEIGHT: usize = 200; - -// must not be 0 so that we don't have a .bss section -pub static X_POS: AtomicUsize = AtomicUsize::new(1); -pub static Y_POS: AtomicUsize = AtomicUsize::new(1); - -pub struct Printer; - -impl Printer { - pub fn clear_screen(&mut self) { - for i in 0..(SCREEN_WIDTH * SCREEN_HEIGHT) { - unsafe { - VGA_BUFFER.offset(i as isize).write_volatile(0); - } - } - - X_POS.store(0, Ordering::SeqCst); - Y_POS.store(0, Ordering::SeqCst); - } - - fn newline(&mut self) { - let y_pos = Y_POS.fetch_add(8, Ordering::SeqCst); - X_POS.store(0, Ordering::SeqCst); - if y_pos >= SCREEN_HEIGHT { - self.clear_screen(); - } - } - - fn write_char(&mut self, c: char) { - use font8x8::UnicodeFonts; - - if c == '\n' { - self.newline(); - return; - } - - let x_pos = X_POS.fetch_add(8, Ordering::SeqCst); - let y_pos = Y_POS.load(Ordering::SeqCst); - - match c { - ' '..='~' => { - let rendered = font8x8::BASIC_FONTS - .get(c) - .expect("character not found in basic font"); - for (y, byte) in rendered.iter().enumerate() { - for (x, bit) in (0..8).enumerate() { - if *byte & (1 << bit) == 0 { - continue; - } - let color = 0xf; - let idx = (y_pos + y) * SCREEN_WIDTH + x_pos + x; - unsafe { - VGA_BUFFER.offset(idx as isize).write_volatile(color); - } - } - } - } - _ => panic!("unprintable character"), - } - - if x_pos + 8 >= SCREEN_WIDTH { - self.newline(); - } - } -} - -impl Write for Printer { - fn write_str(&mut self, s: &str) -> Result { - for c in s.chars() { - self.write_char(c); - } - - Ok(()) - } -} diff --git a/src/printer/vga_text_80x25.rs b/src/printer/vga_text_80x25.rs deleted file mode 100644 index fd689987..00000000 --- a/src/printer/vga_text_80x25.rs +++ /dev/null @@ -1,37 +0,0 @@ -use core::fmt::{Result, Write}; -use core::sync::atomic::{AtomicUsize, Ordering}; - -const VGA_BUFFER: *mut u8 = 0xb8000 as *mut _; -const SCREEN_SIZE: usize = 80 * 25; - -// must not be 0 so that we don't have a .bss section -pub static CURRENT_OFFSET: AtomicUsize = AtomicUsize::new(160); - -pub struct Printer; - -impl Printer { - pub fn clear_screen(&mut self) { - for i in 0..SCREEN_SIZE { - unsafe { - VGA_BUFFER.offset(i as isize).write_volatile(0); - } - } - - CURRENT_OFFSET.store(0, Ordering::Relaxed); - } -} - -impl Write for Printer { - fn write_str(&mut self, s: &str) -> Result { - for byte in s.bytes() { - let index = CURRENT_OFFSET.fetch_add(2, Ordering::Relaxed) as isize; - - unsafe { - VGA_BUFFER.offset(index).write_volatile(byte); - VGA_BUFFER.offset(index + 1).write_volatile(0x4f); - } - } - - Ok(()) - } -} diff --git a/src/protected/Cargo.lock b/src/protected/Cargo.lock new file mode 100644 index 00000000..e36ab8d9 --- /dev/null +++ b/src/protected/Cargo.lock @@ -0,0 +1,53 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bit_field" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "shared" +version = "0.1.0" +dependencies = [ + "bit_field", + "bitflags", + "lazy_static", + "spin", + "volatile", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "stage_3" +version = "0.1.0" +dependencies = [ + "lazy_static", + "shared", +] + +[[package]] +name = "volatile" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29" diff --git a/src/protected/Cargo.toml b/src/protected/Cargo.toml new file mode 100644 index 00000000..426d6ae9 --- /dev/null +++ b/src/protected/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] +members = [ + "stage_3" +] + +[profile.release] +opt-level = "z" +panic = "abort" +lto = true + +[profile.dev] +opt-level = "z" +panic = "abort" +lto = true +debug = true \ No newline at end of file diff --git a/src/protected/i386-unknown-none.json b/src/protected/i386-unknown-none.json new file mode 100644 index 00000000..38796c83 --- /dev/null +++ b/src/protected/i386-unknown-none.json @@ -0,0 +1,21 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i386-unknown-none", + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation_model": "static", + "eliminate_frame_pointer": true +} \ No newline at end of file diff --git a/src/protected/stage_3/Cargo.toml b/src/protected/stage_3/Cargo.toml new file mode 100644 index 00000000..fbc5784e --- /dev/null +++ b/src/protected/stage_3/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stage_3" +version = "0.1.0" +authors = ["Ryland Morgan "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "stage_3" +crate-type = ["staticlib"] + +[dependencies] +shared = { path = "../../shared" } + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] diff --git a/src/protected/stage_3/src/interrupts.rs b/src/protected/stage_3/src/interrupts.rs new file mode 100644 index 00000000..b0655828 --- /dev/null +++ b/src/protected/stage_3/src/interrupts.rs @@ -0,0 +1,57 @@ +use shared::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use crate::println; +use lazy_static::lazy_static; + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + + //idt.segment_not_present.set_handler_fn(segment_not_present_handler); + idt.breakpoint.set_handler_fn(breakpoint_handler); + + idt.general_protection_fault.set_handler_fn(general_protection_fault_handler); + idt.double_fault.set_handler_fn(double_fault_handler); + + idt + }; +} + + +pub fn init_idt() { + // Seems like we have to manually initialize it first for some reason, otherwise it crashes + ::lazy_static::initialize(&IDT); + IDT.load(); +} + +extern "x86-interrupt" fn breakpoint_handler( + stack_frame: &mut InterruptStackFrame) +{ + println!("[Bootloader] [IDT] Breakpoint Hit @ {}:{}", stack_frame.cs, stack_frame.eip); +} + +extern "x86-interrupt" fn double_fault_handler( + stack_frame: &mut InterruptStackFrame, _error_code: u32) -> ! +{ + panic!("[Bootloader] [IDT] Double Fault!"); +} + +extern "x86-interrupt" fn segment_not_present_handler( + stack_frame: &mut InterruptStackFrame, error_code: u32) +{ + println!("[Bootloader] [IDT] #NP {} ({})", stack_frame.eip, error_code); + loop {}; +} + +extern "x86-interrupt" fn general_protection_fault_handler( + stack_frame: &mut InterruptStackFrame, error_code: u32) +{ + println!("{:?}", stack_frame); + // VM Bit + if stack_frame.eflags & (1 << 17) == (1 << 17) { +// loop {}; +// v8086_handler(stack_frame); + println!("VM Bit Set"); + } + println!("[Bootloader] [IDT] GPF {} ({})", stack_frame.eip, error_code); + loop {}; +} \ No newline at end of file diff --git a/src/protected/stage_3/src/ivt.rs b/src/protected/stage_3/src/ivt.rs new file mode 100644 index 00000000..470ee16e --- /dev/null +++ b/src/protected/stage_3/src/ivt.rs @@ -0,0 +1,13 @@ +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct InterruptVectorTable { + // Goes up to 255 + pub entries: [Entry; 32], +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct Entry { + pub segment: u32, + pub offset: u32, +} \ No newline at end of file diff --git a/src/protected/stage_3/src/lib.rs b/src/protected/stage_3/src/lib.rs new file mode 100644 index 00000000..36286f69 --- /dev/null +++ b/src/protected/stage_3/src/lib.rs @@ -0,0 +1,51 @@ +#![no_std] +#![feature(abi_x86_interrupt, asm, global_asm)] + +use shared::println; +use shared::instructions; +use shared::linker_symbol; + +mod interrupts; +mod panic; +mod ivt; +mod v8086; + +use v8086::{Monitor, Stack}; + +#[no_mangle] +pub extern "C" fn third_stage() -> ! { + println!("[Bootloader] [32] Stage 3"); + + unsafe { + let ptr = 0x110000 as *mut u32; + *ptr = 0xdeadbeef; + } + + println!("[Bootloader] [32] > 1MB"); + + // Load the TSS + unsafe { + instructions::ltr(0x2B) + }; + + println!("[Bootloader] [32] Loaded TSS"); + + interrupts::init_idt(); + + println!("[Bootloader] [32] Loaded IDT"); + + let stack = Stack::new(linker_symbol!(_stack_start), 0x2B); + let monitor = Monitor::new(stack); + let function_address = linker_symbol!(v8086_test); + + println!("Entering V8086"); + + unsafe { + //enter_v8086(); + monitor.start(function_address); + } + + println!("User mode returned"); + + loop {}; +} \ No newline at end of file diff --git a/src/protected/stage_3/src/panic.rs b/src/protected/stage_3/src/panic.rs new file mode 100644 index 00000000..8e85d28d --- /dev/null +++ b/src/protected/stage_3/src/panic.rs @@ -0,0 +1,12 @@ +use core::panic::PanicInfo; +use shared::println; +use shared::instructions; + +#[panic_handler] +pub fn panic(info: &PanicInfo) -> ! { + println!("[Panic] {}", info); + + loop { + instructions::hlt() + } +} diff --git a/src/protected/stage_3/src/v8086/enter_v8086.s b/src/protected/stage_3/src/v8086/enter_v8086.s new file mode 100644 index 00000000..775f99fc --- /dev/null +++ b/src/protected/stage_3/src/v8086/enter_v8086.s @@ -0,0 +1,33 @@ +.intel_syntax noprefix +.code32 +/* _enter_v8086(eip: u32) */ +_enter_v8086: + # Store EIP + pop eax + + # gs, fs, ds, es + xor ebx, ebx + + push ebx + push ebx + push ebx + push ebx + + # ss, esp + push ebx + push ebx + + # eflags + mov ebx, ((1 << 17) | (1 << 1)) + push ebx + + # cs, eip + xor ebx, ebx + + push ebx + push eax + + iret + +_spin: + jmp _spin \ No newline at end of file diff --git a/src/protected/stage_3/src/v8086/macros.rs b/src/protected/stage_3/src/v8086/macros.rs new file mode 100644 index 00000000..f7219f93 --- /dev/null +++ b/src/protected/stage_3/src/v8086/macros.rs @@ -0,0 +1,4 @@ +/// Converts a segment:offset pair to a logical address +macro_rules! seg_off_to_log { + ($segment:ident, $offset:ident) => {($segment & 0xFFFF) * 16 + $offset}; +} diff --git a/src/protected/stage_3/src/v8086/mod.rs b/src/protected/stage_3/src/v8086/mod.rs new file mode 100644 index 00000000..e3e314c3 --- /dev/null +++ b/src/protected/stage_3/src/v8086/mod.rs @@ -0,0 +1,314 @@ +//! Virtual 8086 Mode monitor and entry code +use core::mem; + +#[macro_use] +mod macros; + +const PFX_ES: u16 = 0x001; +const PFX_CS: u16 = 0x002; +const PFX_SS: u16 = 0x004; +const PFX_DS: u16 = 0x008; +const PFX_FS: u16 = 0x010; +const PFX_GS: u16 = 0x020; + +const PFX_OP32: u16 = 0x040; +const PFX_ADDR32: u16 = 0x080; +const PFX_LOCK: u16 = 0x100; +const PFX_REPNE: u16 = 0x200; +const PFX_REP: u16 = 0x400; + +// V8086 Entry Code +global_asm!(include_str!("enter_v8086.s")); + +extern "C" { + fn _enter_v8086(eip: u32); +} + +/// Trait for integers usable as peek / poke / pop / push / etc. +trait IntegerValue: Copy {} +impl IntegerValue for u8 {} +impl IntegerValue for u16 {} +impl IntegerValue for u32 {} + +/// Stack info +#[derive(Debug)] +pub struct Stack { + segment: u32, + offset: u32 +} + +impl Stack { + pub fn new(segment: u32, offset: u32) -> Stack { + Stack { + segment: segment, + offset: offset + } + } +} + +/// Registers +#[derive(Debug)] +#[repr(C)] +#[repr(packed)] +pub struct Registers { + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + + ds: u32, + es: u32, + fs: u32, + gs: u32, + + eip: u32, + cs: u32, + eflags: u32, + user_esp: u32, + user_ss: u32, + + v_es: u32, + v_ds: u32, + v_fs: u32, + v_gs: u32 +} + +impl Registers { + /// Creates a new set of registers (all zero) + pub fn new() -> Registers { + Registers { + edi: 0, + esi: 0, + ebp: 0, + esp: 0, + ebx: 0, + edx: 0, + ecx: 0, + eax: 0, + + ds: 0, + es: 0, + fs: 0, + gs: 0, + + eip: 0, + cs: 0, + eflags: 0, + user_esp: 0, + user_ss: 0, + + v_es: 0, + v_ds: 0, + v_fs: 0, + v_gs: 0, + } + } + + /// Saves the current state of CPU registers into ourselves + pub unsafe extern "C" fn save(&mut self) { + // TODO + + } +} + +#[repr(C)] +#[repr(packed)] +struct V86Registers { + pub gs: u32, + pub fs: u32, + pub ds: u32, + pub es: u32, + + pub ss: u32, + pub esp: u32, + + pub eflags: u32, + + pub cs: u32, + pub eip: u32 +} + +/// The V8086 Monitor itself +#[derive(Debug)] +pub struct Monitor { + pub stack: Stack, + pub registers: Registers +} + +impl Monitor { + /// Creates a new Monitor + pub fn new(stack: Stack) -> Monitor { + Monitor { + stack: stack, + registers: Registers::new() + } + } + + /// Enters V86 mode at the specified address + pub unsafe fn start(&self, address: u32) { + let mut registers = V86Registers { + gs: 8 * 4, + fs: 8 * 4, + ds: 8 * 4, + es: 8 * 4, + ss: 8 * 4, + esp: self.stack.offset, + eflags: 0b01000000000011110100000000000000, + cs: 8 * 3, + eip: address + }; + + crate::println!("Address {}", address); + + _enter_v8086(address); + } + + /// Reads the memory offset at the provided seg:addr + pub unsafe fn peek(&self, segment: u32, offset: u32) -> T { + let logical = seg_off_to_log!(segment, offset); + + let ptr = logical as *const T; + let value = *ptr; + value + } + + /// Writes to the memory offset at the provided seg:addr + pub unsafe fn poke(&self, segment: u32, offset: u32, value: T) { + let logical = seg_off_to_log!(segment, offset); + + let ptr = logical as *mut T; + *ptr = value; + } + + /// Reads one byte from the current EIP and increments it + pub unsafe fn fetch(&mut self) -> u8 { + let value = self.peek(self.registers.cs, self.registers.eip); + self.registers.eip = (self.registers.eip + 1) & 0xFFFF; + + value + } + + /// Pops a value from the v86 stack + pub unsafe fn pop(&mut self) -> T { + let value = self.peek(self.stack.segment, self.stack.offset); + self.stack.offset = (self.stack.offset + (mem::size_of_val(&value) as u32)) & 0xFFFF; + + value + } + + /// Pushes a value to the v86 stack + pub unsafe fn push(&mut self, value: T) { + self.stack.offset = (self.stack.offset - (mem::size_of_val(&value) as u32)) & 0xFFFF; + self.poke(self.stack.segment, self.stack.offset, value); + } + + /// Handles an interrupt using the BIOS IVT + pub unsafe fn handle_interrupt(&mut self, int_number: u32) { + // Push return IP, CS and EFLAGS onto the V86 stack + self.push(self.registers.eflags); + self.push(self.registers.cs); + self.push(self.registers.eip); + + // Disable Interrupts + self.registers.eflags &= !0x200; + + // Load new CS and IP from the IVT + let ivt_offset = int_number * 4; + self.registers.eip = (self.registers.eip & !0xFFFF) | self.peek::(0, ivt_offset); + self.registers.cs = self.peek(0, ivt_offset + 2); + } + + /// Executes an instruction + pub unsafe fn emulate(&mut self) { + let inital_eip = self.registers.eip; + let mut prefix = 0; + let mut instruction = 0; + + loop { + instruction = self.fetch(); + + match instruction { + // Segment prefixes + 0x26 => prefix |= PFX_ES, + 0x2e => prefix |= PFX_CS, + 0x36 => prefix |= PFX_SS, + 0x3e => prefix |= PFX_DS, + 0x64 => prefix |= PFX_FS, + 0x65 => prefix |= PFX_GS, + + 0x66 => prefix |= PFX_OP32, + 0x67 => prefix |= PFX_ADDR32, + 0xF0 => prefix |= PFX_LOCK, + 0xF2 => prefix |= PFX_REPNE, + 0xF3 => prefix |= PFX_REP, + _ => break, + } + }; + + match instruction { + // PUSHF + 0x9C => { + if (prefix & PFX_OP32) == PFX_OP32 { + self.push(self.registers.eflags); + } else { + self.push(self.registers.eflags as u16); + } + }, + + // POPF + 0x9D => { + if (prefix & PFX_OP32) == PFX_OP32 { + if self.registers.esp > 0xFFFC { + return; + } + + self.registers.eflags = self.pop(); + } else { + if self.registers.esp > 0xFFFE { + return; + } + + self.registers.eflags = (self.registers.eflags & 0xFFFF0000) | self.pop::(); + } + }, + + // INT nn + 0xCD => { + let interrupt_number = self.fetch() as u32; + self.handle_interrupt(interrupt_number); + }, + + // IRET + 0xCF => { + if (prefix & PFX_OP32) == PFX_OP32 { + if self.registers.esp > 0xFFF4 { + return; + } + + self.registers.eip = self.pop(); + self.registers.cs = self.pop(); + self.registers.eflags = self.pop(); + } else { + if self.registers.esp > 0xFFFA { + return; + } + + self.registers.eip = self.pop::() as u32; + self.registers.cs = self.pop::() as u32; + self.registers.eflags = self.pop::() as u32; + } + }, + + // CLI & STI + 0xFA => self.registers.eflags &= !0x200, + 0xFB => self.registers.eflags |= 0x200, + + // Other + _ => panic!("Unimplemented V8086 Instruction") + } + } +} \ No newline at end of file diff --git a/src/protected/stage_3/src/v8086_old.rs b/src/protected/stage_3/src/v8086_old.rs new file mode 100644 index 00000000..2a23f3cc --- /dev/null +++ b/src/protected/stage_3/src/v8086_old.rs @@ -0,0 +1,212 @@ +pub const PREFIX_ES: u8 = 0x001; +pub const PREFIX_CS: u8 = 0x002; +pub const PREFIX_SS: u8 = 0x004; +pub const PREFIX_DS: u8 = 0x008; +pub const PREFIX_FS: u8 = 0x010; +pub const PREFIX_GS: u8 = 0x020; + +pub const PREFIX_OP32: u8 = 0x040; +pub const PREFIX_ADDR32: u8 = 0x080; +pub const PREFIX_LOCK: u8 = 0x100; +pub const PREFIX_REPNE: u8 = 0x200; +pub const PREFIX_REP: u8 = 0x400; + +#[derive(Clone, Debug)] +#[repr(C, packed)] +pub struct Registers { + pub edi: u32, + pub esi: u32, + pub ebp: u32, + pub esp: u32, + pub ebx: u32, + pub ecx: u32, + pub edx: u32, + pub ds : u32, + pub es : u32, + pub fs : u32, + pub gs : u32, + pub eip: u32, + pub cs : u32, + pub eflags: u32, + pub user_esp: u32, + pub user_ss: u32, + pub v_es: u32, + pub v_ds: u32, + pub v_fs: u32, + pub v_gs: u32 +} + +/// Converts a segment:offset address into a linear one +pub fn segmented_to_linear(segment: u16, offset: u16) -> u32 { + (segment & 0xFFFF) as u32 * 16 + (offset as u32) +} + +/// Reads a byte at segment:offset in memory +pub unsafe fn peekb(segment: u16, offset: u16) -> u8 { + let ptr = segmented_to_linear(segment, offset) as *const u8; + *ptr +} + +/// Reads a word at segment:offset in memory +pub unsafe fn peekw(segment: u16, offset: u16) -> u16 { + let ptr = segmented_to_linear(segment, offset) as *const u16; + *ptr +} + +/// Reads a long at segment:offset in memory +pub unsafe fn peekl(segment: u16, offset: u16) -> u32 { + let ptr = segmented_to_linear(segment, offset) as *const u32; + *ptr +} + +/// Writes a byte at segment:offset in memory +pub unsafe fn pokeb(segment: u16, offset: u16, value: u8) { + let ptr = segmented_to_linear(segment, offset) as *mut u8; + *ptr = value; +} + +/// Writes a word at segment:offset in memory +pub unsafe fn pokew(segment: u16, offset: u16, value: u16) { + let ptr = segmented_to_linear(segment, offset) as *mut u16; + *ptr = value; +} + +/// Writes a long at segment:offset in memory +pub unsafe fn pokel(segment: u16, offset: u16, value: u32) { + let ptr = segmented_to_linear(segment, offset) as *mut u32; + *ptr = value; +} + +/// Fetches one byte from v86 memory at the IP and advances the instruction pointer +pub unsafe fn fetchb(registers: &mut Registers) -> u8 { + let byte = peekb(registers.cs as u16, registers.eip as u16); + registers.eip = (registers.eip + 1) & 0xFFFF; + + byte +} + +/// Pushes a word onto the stack +pub unsafe fn pushw(registers: &mut Registers, value: u16) { + registers.user_esp = (registers.user_esp - 2) & 0xFFFF; + pokew(registers.user_ss as u16, registers.user_esp as u16, value); +} + +/// Pops a word from the stack +pub unsafe fn popw(registers: &mut Registers) -> u16 { + let ret = peekw(registers.user_ss as u16, registers.user_esp as u16); + registers.user_esp = (registers.user_esp + 2) & 0xFFFF; + + ret +} + +/// Pushes a long onto the stack +pub unsafe fn pushl(registers: &mut Registers, value: u32) { + registers.user_esp = (registers.user_esp - 2) & 0xFFFF; + pokel(registers.user_ss as u16, registers.user_esp as u16, value); +} + +/// Pops a long from the stack +pub unsafe fn popl(registers: &mut Registers) -> u32 { + let ret = peekl(registers.user_ss as u16, registers.user_esp as u16); + registers.user_esp = (registers.user_esp + 2) & 0xFFFF; + + ret +} + +/// Handles an interrupt in V86 mode +pub unsafe fn int(registers: &mut Registers, int_number: u16) { + // Push return IP, CS and FLAGS onto V86 stack + pushw(registers, registers.eflags as u16); + pushw(registers, registers.cs as u16); + pushw(registers, registers.eip as u16); + + // Disable interrupts + registers.eflags &= !0x200; + + // Load new CS and IP from IVT + registers.eip = ((registers.eip & !0xFFFF) as u16 | peekw(0, int_number * 4)) as u32; + registers.cs = peekw(0, (int_number * 4) + 2) as u32; +} + +pub unsafe fn emulate(registers: &mut Registers) { + let init_eip = registers.eip; + let mut prefix = 0; + let mut instruction = fetchb(registers); + + loop { + match instruction { + 0x26 => prefix |= PREFIX_ES, + 0x2E => prefix |= PREFIX_CS, + 0x36 => prefix |= PREFIX_SS, + 0x3E => prefix |= PREFIX_DS, + 0x64 => prefix |= PREFIX_ES, + 0x65 => prefix |= PREFIX_GS, + + 0x66 => prefix |= PREFIX_OP32, + 0x67 => prefix |= PREFIX_ADDR32, + 0xF0 => prefix |= PREFIX_LOCK, + 0xF2 => prefix |= PREFIX_REPNE, + 0xF3 => prefix |= PREFIX_REP, + + _ => break, + }; + }; + + match instruction { + // PUSHF + 0x9C => { + if (prefix & PREFIX_OP32) == PREFIX_OP32 { + pushl(registers, registers.eflags); + } else { + pushw(registers, registers.eflags as u16); + } + }, + + // POPF + 0x9D => { + if (prefix & PREFIX_OP32) == PREFIX_OP32 { + if registers.user_esp > 0xFFFC { + panic!("[V8086] Invalid Stack"); + } else { + registers.eflags = popl(registers); + } + } else { + if registers.user_esp > 0xFFFE { + panic!("[V8086] Invalid Stack"); + } else { + registers.eflags = ((registers.eflags & 0xFFFF0000) as u16 | popw(registers)) as u32; + } + } + }, + + // INT nn + 0xCD => { + let interrupt_id = fetchb(registers); + int(registers, interrupt_id as u16); + }, + + // IRET + 0xCF => { + if (prefix & PREFIX_OP32) == PREFIX_OP32 { + if registers.user_esp > 0xFFF4 { + panic!("[V8086] Invalid Stack"); + } else { + registers.eip = popl(registers); + registers.cs = popl(registers); + registers.eflags = (registers.eflags & 0xFFFF0000) | popl(registers); + } + } else { + if registers.user_esp > 0xFFFA { + panic!("[V8086] Invalid Stack"); + } else { + registers.eip = popw(registers) as u32; + registers.cs = popw(registers) as u32; + registers.eflags = ((registers.eflags & 0xFFFF0000) as u16 | popw(registers)) as u32; + } + } + }, + + 0xE4 | 0xE6 | 0xE5 | 0xE7 | 0x6C | 0x6E | 0xEC | 0xEE | 0x6D | 0x6F | 0xED | 0xEF | 0xFA | 0xFB => panic!("I/O Operation Performed"), + _ => panic!("Invalid instruction in v86 mode") + }; +} diff --git a/src/real/Cargo.lock b/src/real/Cargo.lock new file mode 100644 index 00000000..f4e0e87c --- /dev/null +++ b/src/real/Cargo.lock @@ -0,0 +1,60 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bit_field" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bootsector" +version = "0.1.0" +dependencies = [ + "shared", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "shared" +version = "0.1.0" +dependencies = [ + "bit_field", + "bitflags", + "lazy_static", + "spin", + "volatile", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "stage_2" +version = "0.1.0" +dependencies = [ + "lazy_static", + "shared", +] + +[[package]] +name = "volatile" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29" diff --git a/src/real/Cargo.toml b/src/real/Cargo.toml new file mode 100644 index 00000000..ce659165 --- /dev/null +++ b/src/real/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +members = [ + "bootsector", + "stage_2" +] + +[profile.release] +opt-level = "z" +panic = "abort" +lto = true + +[profile.dev] +opt-level = "z" +panic = "abort" +lto = true +debug = true + +[profile.release.package.bootsector] +opt-level = "s" +debug = false +codegen-units = 1 \ No newline at end of file diff --git a/src/real/bootsector/Cargo.toml b/src/real/bootsector/Cargo.toml new file mode 100644 index 00000000..d109ed04 --- /dev/null +++ b/src/real/bootsector/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "bootsector" +version = "0.1.0" +authors = ["Ryland Morgan "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "bootsector" +crate-type = ["staticlib"] + +[dependencies] +shared = { path = "../../shared" } diff --git a/src/real/bootsector/src/bootstrap.s b/src/real/bootsector/src/bootstrap.s new file mode 100644 index 00000000..9e995e7f --- /dev/null +++ b/src/real/bootsector/src/bootstrap.s @@ -0,0 +1,28 @@ +.section .bootstrap, "awx" +.global _start +.intel_syntax noprefix +.code16 + +_start: + # Zero segment registers + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # Setup the stack + lea ebx, _stack_end + mov esp, ebx + + # Push the drive number as first argument + push dx + + # Call rust + call rust_start + +spin: + cli + hlt + jmp spin \ No newline at end of file diff --git a/src/real/bootsector/src/console.rs b/src/real/bootsector/src/console.rs new file mode 100644 index 00000000..3a2e8c67 --- /dev/null +++ b/src/real/bootsector/src/console.rs @@ -0,0 +1,25 @@ +#[no_mangle] +pub fn real_mode_println(s: &[u8]) { + print(s); + print_char(b'\n'); +} + +pub fn print(s: &[u8]) { + let mut i = 0; + + while i < s.len() { + print_char(s[i]); + i += 1; + } +} + +#[inline(always)] +pub fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10", + in("ax") ax, + options(nostack) + ); + } +} diff --git a/src/real/bootsector/src/errors.rs b/src/real/bootsector/src/errors.rs new file mode 100644 index 00000000..0218239a --- /dev/null +++ b/src/real/bootsector/src/errors.rs @@ -0,0 +1,18 @@ +use super::console::real_mode_println; +use shared::instructions; + +#[no_mangle] +extern "C" fn dap_load_failed() -> ! { + real_mode_println(b"[!] DAP Load Failed"); + loop { + instructions::hlt() + } +} + +#[no_mangle] +extern "C" fn no_int13h_extensions() -> ! { + real_mode_println(b"[!] No int13h Extensions"); + loop { + instructions::hlt() + } +} diff --git a/src/real/bootsector/src/lib.rs b/src/real/bootsector/src/lib.rs new file mode 100644 index 00000000..017b42fe --- /dev/null +++ b/src/real/bootsector/src/lib.rs @@ -0,0 +1,54 @@ +#![feature(asm, global_asm)] +#![no_std] +#![allow(dead_code)] + +mod console; +mod errors; + +use self::console::real_mode_println; +use core::panic::PanicInfo; +use shared::{dap, linker_symbol, instructions}; + +extern "C" { + fn second_stage() -> !; +} +global_asm!(include_str!("bootstrap.s")); + +#[no_mangle] +extern "C" fn rust_start(disk_number: u16) -> ! { + real_mode_println(b"[Bootloader] [16] Bootsector"); + + check_int13h_extensions(disk_number); + + let dap = dap::DiskAddressPacket::new( + linker_symbol!(_rest_of_bootloader_start) as u16, + (linker_symbol!(_rest_of_bootloader_start) - linker_symbol!(_bootloader_start)) as u64, + linker_symbol!(_rest_of_bootloader_end) - linker_symbol!(_rest_of_bootloader_start), + ); + + unsafe { + dap.perform_load(disk_number); + second_stage(); + }; +} + +fn check_int13h_extensions(disk_number: u16) { + unsafe { + asm!(" + int 0x13 + jc no_int13h_extensions", + + in("ax") 0x41, in("bx") 0x55aa, in("dx") disk_number, + options(nostack) + ) + } +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + real_mode_println(b"[Panic]"); + + loop { + instructions::hlt() + } +} diff --git a/src/real/i386-unknown-none-code16.json b/src/real/i386-unknown-none-code16.json new file mode 100644 index 00000000..35bd2784 --- /dev/null +++ b/src/real/i386-unknown-none-code16.json @@ -0,0 +1,21 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i386-unknown-none-code16", + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation_model": "static", + "eliminate_frame_pointer": true +} \ No newline at end of file diff --git a/src/real/stage_2/Cargo.toml b/src/real/stage_2/Cargo.toml new file mode 100644 index 00000000..a897522a --- /dev/null +++ b/src/real/stage_2/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stage_2" +version = "0.1.0" +authors = ["Ryland Morgan "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "stage_2" +crate-type = ["staticlib"] + +[dependencies] +shared = { path = "../../shared" } + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] diff --git a/src/real/stage_2/src/lib.rs b/src/real/stage_2/src/lib.rs new file mode 100644 index 00000000..f728cb74 --- /dev/null +++ b/src/real/stage_2/src/lib.rs @@ -0,0 +1,73 @@ +#![feature(global_asm, asm)] +#![no_std] + +use shared::linker_symbol; +use shared::println; +use shared::structures::gdt::{Descriptor, GlobalDescriptorTable, TaskStateSegment}; +use lazy_static::lazy_static; + +mod v8086_code; +mod panic; + +extern "C" { + fn protected_mode_switch() -> !; +} + +lazy_static! { + static ref TSS: TaskStateSegment = { + use core::mem::size_of; + + let mut tss = TaskStateSegment::new(); + + tss.privilege_stack_table[0].esp = linker_symbol!(_protected_mode_stack_end); + tss.privilege_stack_table[0].ss = 2 * 8; // Kernel data segment is 3rd segment (null, code, data) + tss.iomap_base = size_of::() as u16; + + tss + }; + + static ref GDT: GlobalDescriptorTable = { + let mut gdt = GlobalDescriptorTable::new(); + + // Set up kernel segments + gdt.add_entry(Descriptor::kernel_code_segment()); + gdt.add_entry(Descriptor::kernel_data_segment()); + + // Set up user segments + gdt.add_entry(Descriptor::user_code_segment()); + gdt.add_entry(Descriptor::user_data_segment()); + + // Set up the TSS + gdt.add_entry(Descriptor::tss_segment(&*TSS)); + + gdt + }; +} + +global_asm!(include_str!("protected_mode.s")); + +#[no_mangle] +pub fn second_stage() -> ! { + println!("[Bootloader] [16] Stage 2"); + + enable_a20(); + + unsafe { + GDT.load(); + + println!("[Bootloader] [16] Loaded GDT"); + + protected_mode_switch(); + } +} + +fn enable_a20() { + unsafe { + asm!("in {0}, 0x92 + or {0}, 2 + out 0x92, {0}", + out(reg) _, + options(nostack) + ); + } +} diff --git a/src/real/stage_2/src/panic.rs b/src/real/stage_2/src/panic.rs new file mode 100644 index 00000000..8e85d28d --- /dev/null +++ b/src/real/stage_2/src/panic.rs @@ -0,0 +1,12 @@ +use core::panic::PanicInfo; +use shared::println; +use shared::instructions; + +#[panic_handler] +pub fn panic(info: &PanicInfo) -> ! { + println!("[Panic] {}", info); + + loop { + instructions::hlt() + } +} diff --git a/src/real/stage_2/src/protected_mode.s b/src/real/stage_2/src/protected_mode.s new file mode 100644 index 00000000..45d89f39 --- /dev/null +++ b/src/real/stage_2/src/protected_mode.s @@ -0,0 +1,23 @@ +.intel_syntax noprefix +.code16 + +protected_mode_switch: + cli + + mov eax, cr0 + or al, 1 + mov cr0, eax + + push 0x8 + lea eax, [protected_mode] + push eax + retf + +.code32 +protected_mode: + mov bx, 0x10 + + mov ds, bx + mov es, bx + + jmp third_stage \ No newline at end of file diff --git a/src/real/stage_2/src/v8086_code.rs b/src/real/stage_2/src/v8086_code.rs new file mode 100644 index 00000000..ecdeaaf2 --- /dev/null +++ b/src/real/stage_2/src/v8086_code.rs @@ -0,0 +1,34 @@ +//! V86 code (has to compiled for real mode) +pub fn println(s: &[u8]) { + print(s); + print_char(b'\n'); +} + +pub fn print(s: &[u8]) { + let mut i = 0; + + while i < s.len() { + print_char(s[i]); + i += 1; + } +} + +#[inline(always)] +pub fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10", + in("ax") ax, + options(nostack) + ); + } +} + +#[no_mangle] +pub extern "C" fn v8086_test() { + unsafe { asm!("mov bx, 0xcafe"); } + unsafe { + // asm!("int 0x10", in("ax") 0x41 | 0x0e00, options(nostack)); + } + loop {}; +} \ No newline at end of file diff --git a/src/shared/Cargo.toml b/src/shared/Cargo.toml new file mode 100644 index 00000000..794151f3 --- /dev/null +++ b/src/shared/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "shared" +version = "0.1.0" +authors = ["Ryland Morgan "] +edition = "2018" + +[dependencies] +bitflags = "1.2.1" +bit_field = "0.10.0" +volatile = "0.2" +spin = "0.5" + +[dependencies.lazy_static] +version = "1" +features = ["spin_no_std"] + +[profile.release] +opt-level = "z" +panic = "abort" +lto = true + +[profile.dev] +opt-level = "z" +panic = "abort" +lto = true +debug = true \ No newline at end of file diff --git a/src/shared/src/console.rs b/src/shared/src/console.rs new file mode 100644 index 00000000..530d48ea --- /dev/null +++ b/src/shared/src/console.rs @@ -0,0 +1,171 @@ +use core::fmt; +use lazy_static::lazy_static; +use spin::Mutex; +use volatile::Volatile; + +lazy_static! { + /// A global `Writer` instance that can be used for printing to the VGA text buffer. + /// + /// Used by the `print!` and `println!` macros. + pub static ref WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::LightGray, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }); +} + +/// The standard color palette in VGA text mode. +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +/// A combination of a foreground and a background color. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +struct ColorCode(u8); + +impl ColorCode { + /// Create a new `ColorCode` with the given foreground and background colors. + fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} + +/// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +/// The height of the text buffer (normally 25 lines). +const BUFFER_HEIGHT: usize = 25; +/// The width of the text buffer (normally 80 columns). +const BUFFER_WIDTH: usize = 80; + +/// A structure representing the VGA text buffer. +#[repr(transparent)] +struct Buffer { + chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +/// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`. +/// +/// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the +/// `core::fmt::Write` trait. +pub struct Writer { + column_position: usize, + color_code: ColorCode, + buffer: &'static mut Buffer, +} + +impl Writer { + /// Writes an ASCII byte to the buffer. + /// + /// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte => { + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + + let row = BUFFER_HEIGHT - 1; + let col = self.column_position; + + let color_code = self.color_code; + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + + /// Writes the given ASCII string to the buffer. + /// + /// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Does **not** + /// support strings with non-ASCII characters, since they can't be printed in the VGA text + /// mode. + fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + // printable ASCII byte or newline + 0x20..=0x7e | b'\n' => self.write_byte(byte), + // not part of printable ASCII range + _ => self.write_byte(0xfe), + } + } + } + + /// Shifts all lines one line up and clears the last row. + fn new_line(&mut self) { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(BUFFER_HEIGHT - 1); + self.column_position = 0; + } + + /// Clears a row by overwriting it with blank characters. + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + ascii_character: b' ', + color_code: self.color_code, + }; + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } +} + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +/// Like the `print!` macro in the standard library, but prints to the VGA text buffer. +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::console::_print(format_args!($($arg)*))); +} + +/// Like the `println!` macro in the standard library, but prints to the VGA text buffer. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +/// Prints the given formatted string to the VGA text buffer through the global `WRITER` instance. +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +} \ No newline at end of file diff --git a/src/shared/src/dap.rs b/src/shared/src/dap.rs new file mode 100644 index 00000000..e529b367 --- /dev/null +++ b/src/shared/src/dap.rs @@ -0,0 +1,41 @@ +#[repr(packed)] +#[allow(dead_code)] +pub struct DiskAddressPacket { + /// Size of the DAP structure + packet_size: u8, + /// always zero + zero: u8, + /// Number of sectors to transfer + number_of_sectors: u16, + /// Offset to memory buffer + offset: u16, + /// Segment of memory buffer + segment: u16, + /// Start logical block address + start_lba: u64, +} + +impl DiskAddressPacket { + #[inline(always)] + pub fn new(memory_buffer_start: u16, file_offset: u64, bytes: u32) -> Self { + Self { + packet_size: 0x10, + zero: 0, + number_of_sectors: (bytes / 512) as u16, + offset: memory_buffer_start, + segment: 0, + start_lba: file_offset / 512, + } + } + + #[inline(always)] + pub unsafe fn perform_load(&self, disk_number: u16) { + let self_addr = self as *const Self as u16; + asm!(" + int 0x13 + jc dap_load_failed", + in("si") self_addr, in("ax") 0x4200, in("dx") disk_number, out("bx") _, + options(nostack) + ); + } +} diff --git a/src/shared/src/instructions.rs b/src/shared/src/instructions.rs new file mode 100644 index 00000000..ad72227d --- /dev/null +++ b/src/shared/src/instructions.rs @@ -0,0 +1,72 @@ +global_asm!(include_str!("instructions.s")); + +/// Performs a retf instruction, jumping to cs:eip +/// +/// # Unsafety +/// We make no guarantees that the cs and eip are valid, nor that they contain executable code +#[inline(always)] +pub unsafe fn retf(cs: u16, eip: u32) { + asm!("push {0:x} + push {1} + retf", + in(reg) cs, in(reg) eip); +} + +/// Performs an iret instruction, jumping to cs:eip (as well as setting the stack to ss:esp and setting eflags) +/// +/// # Unsafety +/// We make no guarantees that any of the parameters are valid +extern "C" { + pub fn iret(ss: u32, esp: u32, cs: u32, eip: u32, eflags: u32); +} + +/* +#[inline(always)] +pub unsafe fn iret(ss: u32, esp: u32, cs: u32, eip: u32, eflags: u32) { + use crate::println; + println!("ss - {} esp - {} cs - {} eip - {} eflags - {}", ss, esp, cs, eip, eflags); + asm!("push {ss:e} + push {esp:e} + push {eflags:e} + push {cs:e} + push {eip:e} + iret", + ss = in(reg) ss, esp = in(reg) esp, cs = in(reg) cs, eip = in(reg) eip, eflags = in(reg) eflags + ); +}*/ + +/// Reads EFlags +#[inline] +pub fn read_eflags() -> u32 { + let mut eflags: u32; + + unsafe { + asm!( + "pushfd + pop {}", + out(reg) eflags, options(nomem, preserves_flags) + ) + }; + + eflags +} + +/// Loads a new value into the task state register +/// +/// # Unsafety +/// A bad value will cause undefined behaviour +#[inline(always)] +pub unsafe fn ltr(task_state: u16) { + asm!("ltr {0:x}", + in(reg) task_state, + options(nostack) + ); +} + +/// Halts the processor +#[inline(always)] +pub fn hlt() { + unsafe { + asm!("hlt", options(nostack, nomem)); + } +} \ No newline at end of file diff --git a/src/shared/src/instructions.s b/src/shared/src/instructions.s new file mode 100644 index 00000000..6b8b8228 --- /dev/null +++ b/src/shared/src/instructions.s @@ -0,0 +1,11 @@ +.intel_syntax noprefix + +iret: + mov ebp, esp + + push dword PTR [ebp+4] + push dword PTR [ebp+8] + push dword PTR [ebp+12] + push dword PTR [ebp+16] + push dword PTR [ebp+20] + iret \ No newline at end of file diff --git a/src/shared/src/lib.rs b/src/shared/src/lib.rs new file mode 100644 index 00000000..21602c06 --- /dev/null +++ b/src/shared/src/lib.rs @@ -0,0 +1,13 @@ +#![feature(abi_x86_interrupt)] +#![feature(const_fn)] +#![feature(asm, global_asm)] +#![no_std] + +pub mod console; +pub mod dap; +#[macro_use] +pub mod macros; +pub mod structures; +pub mod instructions; + +pub mod memory_operations; \ No newline at end of file diff --git a/src/shared/src/macros.rs b/src/shared/src/macros.rs new file mode 100644 index 00000000..1b0f53bf --- /dev/null +++ b/src/shared/src/macros.rs @@ -0,0 +1,13 @@ +#[macro_export] +macro_rules! linker_symbol { + ($symbol_name:ident) => {unsafe { + let symbol_value: u32; + + asm!( + concat!("lea {}, ", stringify!($symbol_name)), + out(reg) symbol_value + ); + + symbol_value + }}; +} diff --git a/src/shared/src/memory_operations.rs b/src/shared/src/memory_operations.rs new file mode 100644 index 00000000..5c637d4a --- /dev/null +++ b/src/shared/src/memory_operations.rs @@ -0,0 +1,53 @@ +#[no_mangle] +pub unsafe extern fn memcpy(dest: *mut u8, src: *const u8, + n: usize) -> *mut u8 { + let mut i = 0; + while i < n { + *dest.offset(i as isize) = *src.offset(i as isize); + i += 1; + } + return dest; +} + +#[no_mangle] +pub unsafe extern fn memmove(dest: *mut u8, src: *const u8, + n: usize) -> *mut u8 { + if src < dest as *const u8 { // copy from end + let mut i = n; + while i != 0 { + i -= 1; + *dest.offset(i as isize) = *src.offset(i as isize); + } + } else { // copy from beginning + let mut i = 0; + while i < n { + *dest.offset(i as isize) = *src.offset(i as isize); + i += 1; + } + } + return dest; +} + +#[no_mangle] +pub unsafe extern fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 { + let mut i = 0; + while i < n { + *s.offset(i as isize) = c as u8; + i += 1; + } + return s; +} + +#[no_mangle] +pub unsafe extern fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { + let mut i = 0; + while i < n { + let a = *s1.offset(i as isize); + let b = *s2.offset(i as isize); + if a != b { + return a as i32 - b as i32 + } + i += 1; + } + return 0; +} \ No newline at end of file diff --git a/src/shared/src/structures/gdt.rs b/src/shared/src/structures/gdt.rs new file mode 100644 index 00000000..89494371 --- /dev/null +++ b/src/shared/src/structures/gdt.rs @@ -0,0 +1,282 @@ +use bit_field::BitField; +use bitflags::bitflags; + +#[derive(Debug, Clone)] +pub struct GlobalDescriptorTable { + pub table: [u64; 8], + next_free: usize, +} + +impl GlobalDescriptorTable { + /// Creates an empty GDT. + #[inline] + pub const fn new() -> GlobalDescriptorTable { + GlobalDescriptorTable { + table: [0; 8], + next_free: 1, + } + } + + /// Adds the given segment descriptor to the GDT, returning the segment selector. + /// + /// Panics if the GDT has no free entries left. + #[inline] + pub fn add_entry(&mut self, entry: Descriptor) -> u16 { + let index = self.push(entry.0); + + index as u16 + } + + /// Loads the GDT in the CPU using the `lgdt` instruction. This does **not** alter any of the + /// segment registers; you **must** (re)load them yourself. + #[inline] + pub unsafe fn load(&self) { + use core::mem::size_of; + + /// A struct describing a pointer to a descriptor table (GDT / IDT). + /// This is in a format suitable for giving to 'lgdt' or 'lidt'. + #[derive(Debug, Clone, Copy)] + #[repr(C, packed)] + struct DescriptorTablePointer { + /// Size of the DT. + pub limit: u16, + /// Pointer to the memory region containing the DT. + pub base: u32, + } + + let ptr = DescriptorTablePointer { + base: self.table.as_ptr() as u32, + limit: (self.table.len() * size_of::() - 1) as u16, + }; + + use crate::println; + println!("GDT -"); + println!(" {:#08x}", self.table[0]); + println!(" {:#08x}", self.table[1]); + println!(" {:#08x}", self.table[2]); + println!(" {:#08x}", self.table[3]); + println!(" {:#08x}", self.table[4]); + println!(" {:#08x}", self.table[5]); + println!(" {:#08x}", self.table[6]); + println!(" {:#08x}", self.table[7]); + + asm!("lgdt [{}]", + in(reg) &ptr, + options(nostack) + ); + } + + #[inline] + fn push(&mut self, value: u64) -> usize { + if self.next_free < self.table.len() { + let index = self.next_free; + self.table[index] = value; + self.next_free += 1; + + index + } else { + panic!("GDT full"); + } + } +} + +#[derive(Debug, Clone)] +pub struct Descriptor(u64); + +bitflags! { + /// Flags for a GDT descriptor. Not all flags are valid for all descriptor types. + pub struct DescriptorFlags: u64 { + /// The CPU sets this value to one when the segment is accessed + const ACCESSED = 1 << 40; + /// For data segments, this flag sets the segment as writable. For code + /// segments, it defines whether the segment is readable. + const READABLE_WRITABLE = 1 << 41; + /// Marks a code segment as “conforming”. This influences the privilege checks that + /// occur on control transfers. + const CONFORMING = 1 << 42; + /// This flag must be set for code segments. + const EXECUTABLE = 1 << 43; + /// This flag must be set for user segments (in contrast to system segments). + const USER_SEGMENT = 1 << 44; + /// Must be set for any segment, causes a segment not present exception if not set. + const PRESENT = 1 << 47; + /// Must be set for long mode code segments. + const LONG_MODE = 1 << 53; + + /// The DPL for this descriptor is Ring 3 + const DPL_RING_3 = 3 << 45; + + /// Is this segment available for use + const AVAILABLE = 1 << 52; + + /// If set, this page is a 32 bit descriptor + const SIZE = 1 << 54; + + /// If set, limit is in 4k pages + const GRANULARITY = 1 << 55; + } +} + +impl Descriptor { + /// Creates a null descriptor + #[inline] + pub fn null_descriptor() -> Descriptor { + Descriptor(0) + } + + /// Creates a segment descriptor for a protected mode kernel code segment. + #[inline] + pub fn kernel_code_segment() -> Descriptor { + use self::DescriptorFlags as Flags; + + let flags = + Flags::USER_SEGMENT | Flags::PRESENT | Flags::READABLE_WRITABLE | Flags::ACCESSED | Flags::SIZE | Flags::EXECUTABLE; + + Descriptor(flags.bits()).with_flat_limit() + } + + /// Creates a segment descriptor for a protected mode kernel data segment. + #[inline] + pub fn kernel_data_segment() -> Descriptor { + use self::DescriptorFlags as Flags; + + let flags = + Flags::USER_SEGMENT | Flags::PRESENT | Flags::READABLE_WRITABLE | Flags::ACCESSED | Flags::SIZE; + Descriptor(flags.bits()).with_flat_limit() + } + + /// Creates a segment descriptor for a protected mode ring 3 data segment. + #[inline] + pub fn user_data_segment() -> Descriptor { + use self::DescriptorFlags as Flags; + + let flags = + Flags::USER_SEGMENT | Flags::PRESENT | Flags::READABLE_WRITABLE | Flags::ACCESSED | Flags::DPL_RING_3; + + Descriptor(flags.bits()).with_flat_limit() + } + + /// Creates a segment descriptor for a protected mode ring 3 code segment. + #[inline] + pub fn user_code_segment() -> Descriptor { + use self::DescriptorFlags as Flags; + + let flags = + Flags::USER_SEGMENT | Flags::PRESENT | Flags::READABLE_WRITABLE | Flags::ACCESSED | Flags::EXECUTABLE | Flags::DPL_RING_3; + + Descriptor(flags.bits()).with_flat_limit() + } + + /// Creates a TSS system descriptor for the given TSS. + #[inline] + pub fn tss_segment(tss: &TaskStateSegment) -> Descriptor { + use self::DescriptorFlags as Flags; + use core::mem::size_of; + + let ptr = tss as *const _ as u64; + + + let mut val: u64 = (Flags::PRESENT | Flags::EXECUTABLE | Flags::ACCESSED | Flags::SIZE | Flags::DPL_RING_3).bits(); + + // base + val.set_bits(16..40, ptr.get_bits(0..24)); + val.set_bits(56..64, ptr.get_bits(24..32)); + + // limit (the `-1` in needed since the bound is inclusive) + val.set_bits(0..16, ((size_of::() - 1) as u64).get_bits(0..16)); + + Descriptor(val) + } + + fn with_flat_limit(mut self) -> Self { + // limit_low + self.0.set_bits(0..16, 0xffff); + + // limit high + // self.0.set_bits(48..52, 0xff); + self.0.set_bit(48, true); + self.0.set_bit(49, true); + self.0.set_bit(50, true); + self.0.set_bit(51, true); + + // granularity + self.0 |= DescriptorFlags::GRANULARITY.bits(); + + self + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct TaskStateSegment { + /// Used for hardware task switching + prev_tss: u32, + /// The full 64-bit canonical forms of the stack pointers (RSP) for privilege levels 0-2. + pub privilege_stack_table: [Stack; 3], + + cr3: u32, + eip: u32, + eflags: u32, + eax: u32, + ecx: u32, + edx: u32, + ebx: u32, + esp: u32, + ebp: u32, + esi: u32, + edi: u32, + es: u32, + cs: u32, + ss: u32, + ds: u32, + fs: u32, + gs: u32, + ldt: u32, + trap: u16, + pub iomap_base: u16, +} + +impl TaskStateSegment { + /// Creates a new TSS with zeroed privilege and interrupt stack table and a zero + /// `iomap_base`. + #[inline] + pub const fn new() -> TaskStateSegment { + TaskStateSegment { + privilege_stack_table: [Stack::zero(); 3], + iomap_base: 0, + prev_tss: 0, + cr3: 0, + eip: 0, + eflags: 0, + eax: 0, + ecx: 0, + edx: 0, + ebx: 0, + esp: 0, + ebp: 0, + esi: 0, + edi: 0, + es: 0, + cs: 0, + ss: 0, + ds: 0, + fs: 0, + gs: 0, + ldt: 0, + trap: 0, + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct Stack { + pub esp: u32, + pub ss: u32, +} + +impl Stack { + const fn zero() -> Self { + Stack { esp: 0, ss: 0 } + } +} diff --git a/src/shared/src/structures/idt.rs b/src/shared/src/structures/idt.rs new file mode 100644 index 00000000..c540b6d2 --- /dev/null +++ b/src/shared/src/structures/idt.rs @@ -0,0 +1,224 @@ +use bit_field::BitField; +use core::marker::PhantomData; + +/// An Interrupt Descriptor Table with 32 entries. +#[derive(Clone)] +#[repr(C, align(16))] +pub struct InterruptDescriptorTable { + pub divide_error: Entry, + pub debug: Entry, + pub non_maskable_interrupt: Entry, + pub breakpoint: Entry, + pub overflow: Entry, + pub bound_range_exceeded: Entry, + pub invalid_opcode: Entry, + pub device_not_available: Entry, + pub double_fault: Entry, + coprocessor_segment_overrun: Entry, + pub invalid_tss: Entry, + pub segment_not_present: Entry, + pub stack_segment_fault: Entry, + pub general_protection_fault: Entry, + pub page_fault: Entry, + reserved_1: Entry, + pub x87_floating_point: Entry, + pub alignment_check: Entry, + pub machine_check: Entry, + pub simd_floating_point: Entry, + pub virtualization: Entry, + reserved_2: [Entry; 9], + pub security_exception: Entry, + reserved_3: Entry, +} + +impl InterruptDescriptorTable { + /// Creates a new IDT filled with non-present entries. + #[inline] + pub fn new() -> InterruptDescriptorTable { + let idt = InterruptDescriptorTable { + divide_error: Entry::missing(), + debug: Entry::missing(), + non_maskable_interrupt: Entry::missing(), + breakpoint: Entry::missing(), + overflow: Entry::missing(), + bound_range_exceeded: Entry::missing(), + invalid_opcode: Entry::missing(), + device_not_available: Entry::missing(), + double_fault: Entry::missing(), + coprocessor_segment_overrun: Entry::missing(), + invalid_tss: Entry::missing(), + segment_not_present: Entry::missing(), + stack_segment_fault: Entry::missing(), + general_protection_fault: Entry::missing(), + page_fault: Entry::missing(), + reserved_1: Entry::missing(), + x87_floating_point: Entry::missing(), + alignment_check: Entry::missing(), + machine_check: Entry::missing(), + simd_floating_point: Entry::missing(), + virtualization: Entry::missing(), + reserved_2: [Entry::missing(); 9], + security_exception: Entry::missing(), + reserved_3: Entry::missing(), + }; + + idt + } + + /// Loads the IDT in the CPU using the `lidt` command. + pub fn load(&'static self) { + unsafe { self.load_unsafe() } + } + + /// Loads the IDT in the CPU using the `lidt` command. + /// + /// # Safety + /// + /// As long as it is the active IDT, you must ensure that: + /// + /// - `self` is never destroyed. + /// - `self` always stays at the same memory location. It is recommended to wrap it in + /// a `Box`. + /// + pub unsafe fn load_unsafe(&self) { + use core::mem::size_of; + + let ptr = DescriptorTablePointer { + base: self as *const _ as u32, + limit: (size_of::() - 1) as u16, + }; + + asm!("lidt [{}]", + in(reg) &ptr, + options(nostack) + ); + } +} + +/// A struct describing a pointer to a descriptor table (GDT / IDT). +/// This is in a format suitable for giving to 'lgdt' or 'lidt'. +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct DescriptorTablePointer { + /// Size of the DT. + pub limit: u16, + /// Pointer to the memory region containing the DT. + pub base: u32, +} + +/// An Interrupt Descriptor Table entry. +/// +/// The generic parameter can either be `HandlerFunc` or `HandlerFuncWithErrCode`, depending +/// on the interrupt vector. +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +pub struct Entry { + offset_low: u16, + gdt_selector: u16, + zero: u8, + options: EntryOptions, + offset_high: u16, + phantom: PhantomData, +} + +impl Entry { + /// Creates a non-present IDT entry (but sets the must-be-one bits). + #[inline] + pub fn missing() -> Self { + Entry { + gdt_selector: 0, + offset_low: 0, + offset_high: 0, + zero: 0, + options: EntryOptions::minimal(), + phantom: PhantomData, + } + } + /// Set the handler address for the IDT entry and sets the present bit. + /// + /// For the code selector field, this function uses the code segment selector currently + /// active in the CPU. + /// + /// The function returns a mutable reference to the entry's options that allows + /// further customization. + #[inline] + pub fn set_handler_addr(&mut self, addr: u32) -> &mut EntryOptions { + self.offset_low = addr as u16; + self.offset_high = (addr >> 16) as u16; + + let segment: u16; + + unsafe { asm!("mov {:x}, cs", + out(reg) segment, + options(nostack, nomem) + ) }; + + self.gdt_selector = segment; + + self.options.set_present(true); + &mut self.options + } +} + +macro_rules! impl_set_handler_fn { + ($h:ty) => { + impl Entry<$h> { + /// Set the handler function for the IDT entry and sets the present bit. + /// + /// For the code selector field, this function uses the code segment selector currently + /// active in the CPU. + /// + /// The function returns a mutable reference to the entry's options that allows + /// further customization. + #[inline] + pub fn set_handler_fn(&mut self, handler: $h) -> &mut EntryOptions { + self.set_handler_addr(handler as u32) + } + } + }; +} + +impl_set_handler_fn!(HandlerFunc); +impl_set_handler_fn!(HandlerFuncWithErrCode); +impl_set_handler_fn!(DivergingHandlerFunc); +impl_set_handler_fn!(DivergingHandlerFuncWithErrCode); + +/// Represents the options field of an IDT entry. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct EntryOptions(u8); + +impl EntryOptions { + /// Creates a minimal options field with all the must-be-one bits set. + #[inline] + const fn minimal() -> Self { + EntryOptions(0b1110) + } + + /// Set or reset the preset bit. + #[inline] + pub fn set_present(&mut self, present: bool) -> &mut Self { + self.0.set_bit(7, present); + self + } +} + +/// A handler function for an interrupt or an exception without error code. +pub type HandlerFunc = extern "x86-interrupt" fn(&mut InterruptStackFrame); +/// A handler function for an exception that pushes an error code. +pub type HandlerFuncWithErrCode = + extern "x86-interrupt" fn(&mut InterruptStackFrame, error_code: u32); +/// A handler function that must not return, e.g. for a machine check exception. +pub type DivergingHandlerFunc = extern "x86-interrupt" fn(&mut InterruptStackFrame) -> !; +/// A handler function with an error code that must not return, e.g. for a double fault exception. +pub type DivergingHandlerFuncWithErrCode = + extern "x86-interrupt" fn(&mut InterruptStackFrame, error_code: u32) -> !; + +/// Represents the interrupt stack frame pushed by the CPU on interrupt or exception entry. +#[derive(Clone, Debug)] +#[repr(C)] +pub struct InterruptStackFrame { + pub eip: u32, + pub cs: u32, + pub eflags: u32, +} diff --git a/src/shared/src/structures/mod.rs b/src/shared/src/structures/mod.rs new file mode 100644 index 00000000..6aed3686 --- /dev/null +++ b/src/shared/src/structures/mod.rs @@ -0,0 +1,2 @@ +pub mod gdt; +pub mod idt; \ No newline at end of file diff --git a/src/sse.rs b/src/sse.rs deleted file mode 100644 index b4e0a9f2..00000000 --- a/src/sse.rs +++ /dev/null @@ -1,17 +0,0 @@ -/// Enables Streaming SIMD Extensions (SSE) support for loaded kernels. -pub fn enable_sse() { - use x86_64::registers::control::{Cr0, Cr0Flags, Cr4, Cr4Flags}; - let mut flags = Cr0::read(); - flags.remove(Cr0Flags::EMULATE_COPROCESSOR); - flags.insert(Cr0Flags::MONITOR_COPROCESSOR); - unsafe { - Cr0::write(flags); - } - - let mut flags = Cr4::read(); - flags.insert(Cr4Flags::OSFXSR); - flags.insert(Cr4Flags::OSXMMEXCPT_ENABLE); - unsafe { - Cr4::write(flags); - } -} diff --git a/src/stage_1.s b/src/stage_1.s deleted file mode 100644 index ebfa1a08..00000000 --- a/src/stage_1.s +++ /dev/null @@ -1,233 +0,0 @@ -.section .boot-first-stage, "awx" -.global _start -.intel_syntax noprefix -.code16 - -# This stage initializes the stack, enables the A20 line, loads the rest of -# the bootloader from disk, and jumps to stage_2. - -_start: - # zero segment registers - xor ax, ax - mov ds, ax - mov es, ax - mov ss, ax - mov fs, ax - mov gs, ax - - # clear the direction flag (e.g. go forward in memory when using - # instructions like lodsb) - cld - - # initialize stack - mov sp, 0x7c00 - - lea si, boot_start_str - call real_mode_println - -enable_a20: - # enable A20-Line via IO-Port 92, might not work on all motherboards - in al, 0x92 - test al, 2 - jnz enable_a20_after - or al, 2 - and al, 0xFE - out 0x92, al -enable_a20_after: - - -enter_protected_mode: - # clear interrupts - cli - push ds - push es - - lgdt [gdt32info] - - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - jmp protected_mode # tell 386/486 to not crash - -protected_mode: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - - and al, 0xfe # clear protected mode bit - mov cr0, eax - -unreal_mode: - pop es # get back old extra segment - pop ds # get back old data segment - sti - - # back to real mode, but internal data segment register is still loaded - # with gdt segment -> we can access the full 4GiB of memory - - mov bx, 0x0f01 # attrib/char of smiley - mov eax, 0xb8f00 # note 32 bit offset - mov word ptr ds:[eax], bx - -check_int13h_extensions: - mov ah, 0x41 - mov bx, 0x55aa - # dl contains drive number - int 0x13 - jc no_int13h_extensions - -load_rest_of_bootloader_from_disk: - lea eax, _rest_of_bootloader_start_addr - - # start of memory buffer - mov [dap_buffer_addr], ax - - # number of disk blocks to load - lea ebx, _rest_of_bootloader_end_addr - sub ebx, eax # end - start - shr ebx, 9 # divide by 512 (block size) - mov [dap_blocks], bx - - # number of start block - lea ebx, _start - sub eax, ebx - shr eax, 9 # divide by 512 (block size) - mov [dap_start_lba], eax - - lea si, dap - mov ah, 0x42 - int 0x13 - jc rest_of_bootloader_load_failed - -jump_to_second_stage: - lea eax, [stage_2] - jmp eax - -spin: - jmp spin - -# print a string and a newline -# IN -# si: points at zero-terminated String -# CLOBBER -# ax -real_mode_println: - call real_mode_print - mov al, 13 # \r - call real_mode_print_char - mov al, 10 # \n - jmp real_mode_print_char - -# print a string -# IN -# si: points at zero-terminated String -# CLOBBER -# ax -real_mode_print: - cld -real_mode_print_loop: - # note: if direction flag is set (via std) - # this will DECREMENT the ptr, effectively - # reading/printing in reverse. - lodsb al, BYTE PTR [si] - test al, al - jz real_mode_print_done - call real_mode_print_char - jmp real_mode_print_loop -real_mode_print_done: - ret - -# print a character -# IN -# al: character to print -# CLOBBER -# ah -real_mode_print_char: - mov ah, 0x0e - int 0x10 - ret - -# print a number in hex -# IN -# bx: the number -# CLOBBER -# al, cx -real_mode_print_hex: - mov cx, 4 -.lp: - mov al, bh - shr al, 4 - - cmp al, 0xA - jb .below_0xA - - add al, 'A' - 0xA - '0' -.below_0xA: - add al, '0' - - call real_mode_print_char - - shl bx, 4 - loop .lp - - ret - -real_mode_error: - call real_mode_println - jmp spin - -no_int13h_extensions: - lea si, no_int13h_extensions_str - jmp real_mode_error - -rest_of_bootloader_load_failed: - lea si, rest_of_bootloader_load_failed_str - jmp real_mode_error - -boot_start_str: .asciz "Booting (first stage)..." -error_str: .asciz "Error: " -no_int13h_extensions_str: .asciz "No support for int13h extensions" -rest_of_bootloader_load_failed_str: .asciz "Failed to load rest of bootloader" - -gdt32info: - .word gdt32_end - gdt32 - 1 # last byte in table - .word gdt32 # start of table - -gdt32: - # entry 0 is always unused - .quad 0 -codedesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x9a - .byte 0xcf - .byte 0 -datadesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x92 - .byte 0xcf - .byte 0 -gdt32_end: - -dap: # disk access packet - .byte 0x10 # size of dap - .byte 0 # unused -dap_blocks: - .word 0 # number of sectors -dap_buffer_addr: - .word 0 # offset to memory buffer -dap_buffer_seg: - .word 0 # segment of memory buffer -dap_start_lba: - .quad 0 # start logical block address - -.org 510 -.word 0xaa55 # magic number for bootable disk diff --git a/src/stage_2.s b/src/stage_2.s deleted file mode 100644 index 5486439a..00000000 --- a/src/stage_2.s +++ /dev/null @@ -1,105 +0,0 @@ -.section .boot, "awx" -.intel_syntax noprefix -.code16 - -# This stage sets the target operating mode, loads the kernel from disk, -# creates an e820 memory map, enters protected mode, and jumps to the -# third stage. - -second_stage_start_str: .asciz "Booting (second stage)..." -kernel_load_failed_str: .asciz "Failed to load kernel from disk" - -kernel_load_failed: - lea si, [kernel_load_failed_str] - call real_mode_println -kernel_load_failed_spin: - jmp kernel_load_failed_spin - -stage_2: - lea si, [second_stage_start_str] - call real_mode_println - -set_target_operating_mode: - # Some BIOSs assume the processor will only operate in Legacy Mode. We change the Target - # Operating Mode to "Long Mode Target Only", so the firmware expects each CPU to enter Long Mode - # once and then stay in it. This allows the firmware to enable mode-specifc optimizations. - # We save the flags, because CF is set if the callback is not supported (in which case, this is - # a NOP) - pushf - mov ax, 0xec00 - mov bl, 0x2 - int 0x15 - popf - -load_kernel_from_disk: - # start of memory buffer - lea eax, _kernel_buffer - mov [dap_buffer_addr], ax - - # number of disk blocks to load - mov word ptr [dap_blocks], 1 - - # number of start block - lea eax, _kernel_start_addr - lea ebx, _start - sub eax, ebx - shr eax, 9 # divide by 512 (block size) - mov [dap_start_lba], eax - - # destination address - mov edi, 0x400000 - - # block count - lea ecx, _kernel_size - add ecx, 511 # align up - shr ecx, 9 - -load_next_kernel_block_from_disk: - # load block from disk - lea si, dap - mov ah, 0x42 - int 0x13 - jc kernel_load_failed - - # copy block to 2MiB - push ecx - push esi - mov ecx, 512 / 4 - # move with zero extension - # because we are moving a word ptr - # to esi, a 32-bit register. - movzx esi, word ptr [dap_buffer_addr] - # move from esi to edi ecx times. - rep movsd [edi], [esi] - pop esi - pop ecx - - # next block - mov eax, [dap_start_lba] - add eax, 1 - mov [dap_start_lba], eax - - sub ecx, 1 - jnz load_next_kernel_block_from_disk - -create_memory_map: - lea di, es:[_memory_map] - call do_e820 - -video_mode_config: - call config_video_mode - -enter_protected_mode_again: - cli - lgdt [gdt32info] - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - push 0x8 - lea eax, [stage_3] - push eax - retf - -spin32: - jmp spin32 diff --git a/src/stage_3.s b/src/stage_3.s deleted file mode 100644 index 06d753c3..00000000 --- a/src/stage_3.s +++ /dev/null @@ -1,199 +0,0 @@ -.section .boot, "awx" -.intel_syntax noprefix -.code32 - -# This stage performs some checks on the CPU (cpuid, long mode), sets up an -# initial page table mapping (identity map the bootloader, map the P4 -# recursively, map the kernel blob to 4MB), enables paging, switches to long -# mode, and jumps to stage_4. - -stage_3: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - mov ss, bx # set stack segment - - lea si, boot_third_stage_str - call vga_println - -check_cpu: - call check_cpuid - call check_long_mode - - cli # disable interrupts - - lidt zero_idt # Load a zero length IDT so that any NMI causes a triple fault. - -# enter long mode - -set_up_page_tables: - # zero out buffer for page tables - lea edi, [__page_table_start] - lea ecx, [__page_table_end] - sub ecx, edi - shr ecx, 2 # one stosd zeros 4 bytes -> divide by 4 - xor eax, eax - rep stosd - - # p4 - lea eax, [_p3] - or eax, (1 | 2) - mov [_p4], eax - # p3 - lea eax, [_p2] - or eax, (1 | 2) - mov [_p3], eax - # p2 - lea eax, [_p1] - or eax, (1 | 2) - mov [_p2], eax - mov eax, (0x400000 | 1 | 2 | (1 << 7)) - mov ecx, 2 - lea edx, _kernel_size - add edx, 0x400000 # start address - add edx, 0x200000 - 1 # align up - shr edx, 12 + 9 # end huge page number - map_p2_table: - mov [_p2 + ecx * 8], eax - add eax, 0x200000 - add ecx, 1 - cmp ecx, edx - jb map_p2_table - # p1 - # start mapping from __page_table_start, as we need to be able to access - # the p4 table from rust. stop mapping at __bootloader_end - lea eax, __page_table_start - and eax, 0xfffff000 - or eax, (1 | 2) - lea ecx, __page_table_start - shr ecx, 12 # start page number - lea edx, __bootloader_end - add edx, 4096 - 1 # align up - shr edx, 12 # end page number - map_p1_table: - mov [_p1 + ecx * 8], eax - add eax, 4096 - add ecx, 1 - cmp ecx, edx - jb map_p1_table - map_framebuffer: - call vga_map_frame_buffer - -enable_paging: - # Write back cache and add a memory fence. I'm not sure if this is - # necessary, but better be on the safe side. - wbinvd - mfence - - # load P4 to cr3 register (cpu uses this to access the P4 table) - lea eax, [_p4] - mov cr3, eax - - # enable PAE-flag in cr4 (Physical Address Extension) - mov eax, cr4 - or eax, (1 << 5) - mov cr4, eax - - # set the long mode bit in the EFER MSR (model specific register) - mov ecx, 0xC0000080 - rdmsr - or eax, (1 << 8) - wrmsr - - # enable paging in the cr0 register - mov eax, cr0 - or eax, (1 << 31) - mov cr0, eax - -load_64bit_gdt: - lgdt gdt_64_pointer # Load GDT.Pointer defined below. - -jump_to_long_mode: - push 0x8 - lea eax, [stage_4] - push eax - retf # Load CS with 64 bit segment and flush the instruction cache - -spin_here: - jmp spin_here - -check_cpuid: - # Check if CPUID is supported by attempting to flip the ID bit (bit 21) - # in the FLAGS register. If we can flip it, CPUID is available. - - # Copy FLAGS in to EAX via stack - pushfd - pop eax - - # Copy to ECX as well for comparing later on - mov ecx, eax - - # Flip the ID bit - xor eax, (1 << 21) - - # Copy EAX to FLAGS via the stack - push eax - popfd - - # Copy FLAGS back to EAX (with the flipped bit if CPUID is supported) - pushfd - pop eax - - # Restore FLAGS from the old version stored in ECX (i.e. flipping the - # ID bit back if it was ever flipped). - push ecx - popfd - - # Compare EAX and ECX. If they are equal then that means the bit - # wasn't flipped, and CPUID isn't supported. - cmp eax, ecx - je no_cpuid - ret -no_cpuid: - lea esi, no_cpuid_str - call vga_println -no_cpuid_spin: - hlt - jmp no_cpuid_spin - -check_long_mode: - # test if extended processor info in available - mov eax, 0x80000000 # implicit argument for cpuid - cpuid # get highest supported argument - cmp eax, 0x80000001 # it needs to be at least 0x80000001 - jb no_long_mode # if it's less, the CPU is too old for long mode - - # use extended info to test if long mode is available - mov eax, 0x80000001 # argument for extended processor info - cpuid # returns various feature bits in ecx and edx - test edx, (1 << 29) # test if the LM-bit is set in the D-register - jz no_long_mode # If it's not set, there is no long mode - ret -no_long_mode: - lea esi, no_long_mode_str - call vga_println -no_long_mode_spin: - hlt - jmp no_long_mode_spin - - -.align 4 -zero_idt: - .word 0 - .byte 0 - -gdt_64: - .quad 0x0000000000000000 # Null Descriptor - should be present. - .quad 0x00209A0000000000 # 64-bit code descriptor (exec/read). - .quad 0x0000920000000000 # 64-bit data descriptor (read/write). - -.align 4 - .word 0 # Padding to make the "address of the GDT" field aligned on a 4-byte boundary - -gdt_64_pointer: - .word gdt_64_pointer - gdt_64 - 1 # 16-bit Size (Limit) of GDT. - .long gdt_64 # 32-bit Base Address of GDT. (CPU will zero extend to 64-bit) - -boot_third_stage_str: .asciz "Booting (third stage)..." -no_cpuid_str: .asciz "Error: CPU does not support CPUID" -no_long_mode_str: .asciz "Error: CPU does not support long mode" diff --git a/src/video_mode/vga_320x200.s b/src/video_mode/vga_320x200.s deleted file mode 100644 index 17d4a743..00000000 --- a/src/video_mode/vga_320x200.s +++ /dev/null @@ -1,90 +0,0 @@ -.section .boot, "awx" -.intel_syntax noprefix -.code16 - -config_video_mode: - mov ah, 0 - mov al, 0x13 # 320x200 256 color graphics - int 0x10 - ret - -.code32 - -vga_map_frame_buffer: - mov eax, 0xa0000 - or eax, (1 | 2) -vga_map_frame_buffer_loop: - mov ecx, eax - shr ecx, 12 - mov [_p1 + ecx * 8], eax - - add eax, 4096 - cmp eax, 0xc0000 - jl vga_map_frame_buffer_loop - - ret - -# print a string and a newline -# IN -# esi: points at zero-terminated String -vga_println: - push eax - push ebx - push ecx - push edx - - call vga_print - - # newline - mov edx, 0 - mov eax, vga_position - mov ecx, 80 * 2 - div ecx - add eax, 1 - mul ecx - mov vga_position, eax - - pop edx - pop ecx - pop ebx - pop eax - - ret - -# print a string -# IN -# esi: points at zero-terminated String -# CLOBBER -# ah, ebx -vga_print: - cld -vga_print_loop: - # note: if direction flag is set (via std) - # this will DECREMENT the ptr, effectively - # reading/printing in reverse. - lodsb al, BYTE PTR [esi] - test al, al - jz vga_print_done - call vga_print_char - jmp vga_print_loop -vga_print_done: - ret - - -# print a character -# IN -# al: character to print -# CLOBBER -# ah, ebx -vga_print_char: - mov ebx, vga_position - mov ah, 0x0f - mov [ebx + 0xa0000], ax - - add ebx, 2 - mov [vga_position], ebx - - ret - -vga_position: - .double 0 diff --git a/src/video_mode/vga_text_80x25.s b/src/video_mode/vga_text_80x25.s deleted file mode 100644 index 0ddb0d6f..00000000 --- a/src/video_mode/vga_text_80x25.s +++ /dev/null @@ -1,90 +0,0 @@ -.section .boot, "awx" -.intel_syntax noprefix -.code16 - -config_video_mode: - mov ah, 0 - mov al, 0x03 # 80x25 16 color text - int 0x10 - ret - -.code32 - -vga_map_frame_buffer: - mov eax, 0xa0000 - or eax, (1 | 2) -vga_map_frame_buffer_loop: - mov ecx, eax - shr ecx, 12 - mov [_p1 + ecx * 8], eax - - add eax, 4096 - cmp eax, 0xc0000 - jl vga_map_frame_buffer_loop - - ret - -# print a string and a newline -# IN -# esi: points at zero-terminated String -vga_println: - push eax - push ebx - push ecx - push edx - - call vga_print - - # newline - mov edx, 0 - mov eax, vga_position - mov ecx, 80 * 2 - div ecx - add eax, 1 - mul ecx - mov vga_position, eax - - pop edx - pop ecx - pop ebx - pop eax - - ret - -# print a string -# IN -# esi: points at zero-terminated String -# CLOBBER -# ah, ebx -vga_print: - cld -vga_print_loop: - # note: if direction flag is set (via std) - # this will DECREMENT the ptr, effectively - # reading/printing in reverse. - lodsb al, BYTE PTR [esi] - test al, al - jz vga_print_done - call vga_print_char - jmp vga_print_loop -vga_print_done: - ret - - -# print a character -# IN -# al: character to print -# CLOBBER -# ah, ebx -vga_print_char: - mov ebx, vga_position - mov ah, 0x0f - mov [ebx + 0xb8000], ax - - add ebx, 2 - mov [vga_position], ebx - - ret - -vga_position: - .double 0 diff --git a/test-kernel/src/main.rs b/test-kernel/src/main.rs index f0352e22..24699749 100644 --- a/test-kernel/src/main.rs +++ b/test-kernel/src/main.rs @@ -15,7 +15,9 @@ pub extern "C" fn _start() -> ! { // named `_start` by default // exit QEMU (see https://os.phil-opp.com/integration-tests/#shutting-down-qemu) - unsafe { exit_qemu(); } + unsafe { + exit_qemu(); + } loop {} } diff --git a/x86_64-bootloader.json b/x86_64-bootloader.json deleted file mode 100644 index ad13109d..00000000 --- a/x86_64-bootloader.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none-gnu", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "pre-link-args": { - "ld.lld": [ - "--script=linker.ld" - ] - }, - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "arch": "x86_64", - "os": "none", - "features": "-mmx,-sse,+soft-float", - "disable-redzone": true, - "panic": "abort", - "executables": true, - "relocation_model": "static" -}