diff --git a/Cargo.lock b/Cargo.lock index ec5f0fe..0b0afa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,17 +245,6 @@ dependencies = [ "port", ] -[[package]] -name = "rustup-configurator" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ea4d26348e7916570c4cc67014aad9e017f148cd5bb8a4ee0f5b95f2db788d" -dependencies = [ - "strip-ansi-escapes", - "target-lexicon", - "thiserror", -] - [[package]] name = "serde" version = "1.0.186" @@ -285,15 +274,6 @@ dependencies = [ "serde", ] -[[package]] -name = "strip-ansi-escapes" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" -dependencies = [ - "vte", -] - [[package]] name = "strsim" version = "0.10.0" @@ -327,29 +307,6 @@ name = "target-lexicon" version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" -dependencies = [ - "serde", -] - -[[package]] -name = "thiserror" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.29", -] [[package]] name = "toml" @@ -397,26 +354,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "vte" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" -dependencies = [ - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -517,7 +454,7 @@ name = "xtask" version = "0.1.0" dependencies = [ "clap", - "rustup-configurator", "serde", + "target-lexicon", "toml", ] diff --git a/README.md b/README.md index a59eb49..797811d 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,7 @@ Right now, r9 is not self-hosting. ## Runtime Dependencies -`cargo xtask dist`, which `cargo xtask qemu` and -`cargo xtask qemukvm` depend on, requires `llvm-objcopy`. +`cargo xtask dist`, which `cargo xtask qemu` depends on, requires `llvm-objcopy`. This is expected to live in the rust toolchain path. You can install by running: ``` rustup component add llvm-tools @@ -47,6 +46,7 @@ R9 can be run using qemu for the various supported architectures: |----|-----------| |aarch64|cargo xtask qemu --arch aarch64 --verbose| |x86-64|cargo xtask qemu --arch x86-64 --verbose| +|x86-64 (with kvm)|cargo xtask qemu --arch x86-64 --kvm --verbose| |riscv|cargo xtask qemu --arch riscv64 --verbose| ## Running on Real Hardware™️ diff --git a/aarch64/src/kmem.rs b/aarch64/src/kmem.rs index 4576a32..b941234 100644 --- a/aarch64/src/kmem.rs +++ b/aarch64/src/kmem.rs @@ -50,7 +50,7 @@ pub fn eearly_pagetables_addr() -> usize { unsafe { eearly_pagetables.as_ptr().addr() } } -#[derive(Clone, Copy, PartialEq, PartialOrd)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[repr(transparent)] pub struct PhysAddr(u64); diff --git a/aarch64/src/vm.rs b/aarch64/src/vm.rs index 204c2c6..ec0d0a8 100644 --- a/aarch64/src/vm.rs +++ b/aarch64/src/vm.rs @@ -470,35 +470,40 @@ pub unsafe fn init(kpage_table: &mut PageTable, dtb_phys: PhysAddr, edtb_phys: P write_volatile(&mut kpage_table.entries[511], entry); } - let text_phys = PhysAddr::from_virt(text_addr()); - let etext_phys = PhysAddr::from_virt(etext_addr()); - let erodata_phys = PhysAddr::from_virt(erodata_addr()); - let ebss_phys = PhysAddr::from_virt(ebss_addr()); - let heap_phys = PhysAddr::from_virt(heap_addr()); - let eheap_phys = PhysAddr::from_virt(eheap_addr()); - - let mmio = rpi_mmio().expect("mmio base detect failed"); - let mmio_end = PhysAddr::from(mmio + (2 * PAGE_SIZE_2M as u64)); - - let custom_map = [ - // TODO We don't actualy unmap the first page... We should to achieve: - // Note that the first page is left unmapped to try and - // catch null pointer dereferences in unsafe code: defense - // in depth! - ("DTB", dtb_phys, edtb_phys, Entry::ro_kernel_data(), PageSize::Page4K), - ("Kernel Text", text_phys, etext_phys, Entry::ro_kernel_text(), PageSize::Page2M), - ("Kernel Data", etext_phys, erodata_phys, Entry::ro_kernel_data(), PageSize::Page2M), - ("Kernel BSS", erodata_phys, ebss_phys, Entry::rw_kernel_data(), PageSize::Page2M), - ("Kernel Heap", heap_phys, eheap_phys, Entry::rw_kernel_data(), PageSize::Page2M), - ("MMIO", mmio, mmio_end, Entry::ro_kernel_device(), PageSize::Page2M), - ]; + // TODO We don't actualy unmap the first page... We should to achieve: + // Note that the first page is left unmapped to try and + // catch null pointer dereferences in unsafe code: defense + // in depth! + let custom_map = { + let text_phys = PhysAddr::from_virt(text_addr()); + let etext_phys = PhysAddr::from_virt(etext_addr()); + let erodata_phys = PhysAddr::from_virt(erodata_addr()); + let ebss_phys = PhysAddr::from_virt(ebss_addr()); + let heap_phys = PhysAddr::from_virt(heap_addr()); + let eheap_phys = PhysAddr::from_virt(eheap_addr()); + + let mmio = rpi_mmio().expect("mmio base detect failed"); + let mmio_end = PhysAddr::from(mmio + (2 * PAGE_SIZE_2M as u64)); + + let mut map = [ + ("DTB", dtb_phys, edtb_phys, Entry::ro_kernel_data(), PageSize::Page4K), + ("Kernel Text", text_phys, etext_phys, Entry::ro_kernel_text(), PageSize::Page2M), + ("Kernel Data", etext_phys, erodata_phys, Entry::ro_kernel_data(), PageSize::Page2M), + ("Kernel BSS", erodata_phys, ebss_phys, Entry::rw_kernel_data(), PageSize::Page2M), + ("Kernel Heap", heap_phys, eheap_phys, Entry::rw_kernel_data(), PageSize::Page2M), + ("MMIO", mmio, mmio_end, Entry::ro_kernel_device(), PageSize::Page2M), + ]; + map.sort_by_key(|a| a.1); + map + }; + println!("Memory map:"); for (name, start, end, flags, page_size) in custom_map.iter() { let mapped_range = kpage_table .map_phys_range(*start, *end, *flags, *page_size) .expect("init mapping failed"); println!( - "Mapped {:16} {:#018x}-{:#018x} to {:#018x}-{:#018x} flags: {:?} page_size: {:?}", + " {:14}{:#018x}-{:#018x} to {:#018x}-{:#018x} flags: {:?} page_size: {:?}", name, start.addr(), end.addr(), diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 9038372..df2278f 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] clap = { version = "4.2.4", features = ["derive"] } -rustup-configurator = "0.1.1" serde = { version = "1.0.160", features = ["derive"] } +target-lexicon = { version = "0.12" } toml = "0.8.0" diff --git a/xtask/src/config.rs b/xtask/src/config.rs index a6543b7..5d99726 100644 --- a/xtask/src/config.rs +++ b/xtask/src/config.rs @@ -75,23 +75,25 @@ pub struct Configuration { pub link: Option>, } -pub fn read_config(filename: String) -> Configuration { - let contents = match fs::read_to_string(filename.clone()) { - Ok(c) => c, - Err(_) => { - eprintln!("Could not read file `{filename}`"); - exit(1); - } - }; - let config: Configuration = match toml::from_str(&contents) { - Ok(d) => d, - Err(e) => { - eprintln!("TOML: Unable to load data from `{}`", filename); - eprintln!("{e}"); - exit(1); - } - }; - config +impl Configuration { + pub fn load(filename: String) -> Self { + let contents = match fs::read_to_string(filename.clone()) { + Ok(c) => c, + Err(_) => { + eprintln!("Could not read file `{filename}`"); + exit(1); + } + }; + let config: Configuration = match toml::from_str(&contents) { + Ok(d) => d, + Err(e) => { + eprintln!("TOML: Unable to load data from `{}`", filename); + eprintln!("{e}"); + exit(1); + } + }; + config + } } /// task could be 'build', 'clippy' diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 3d0447d..ae55200 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,22 +1,40 @@ -use crate::config::{generate_args, read_config, Configuration}; -use rustup_configurator::Triple; +use crate::config::{generate_args, Configuration}; use std::{ env, fmt, path::{Path, PathBuf}, process::{self, Command}, + str::FromStr, }; +use target_lexicon::Triple; mod config; type DynError = Box; type Result = std::result::Result; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Profile { Debug, Release, } +impl Profile { + fn from(matches: &clap::ArgMatches) -> Self { + if matches.get_flag("release") { + Profile::Release + } else { + Profile::Debug + } + } + + fn dir(&self) -> &'static str { + match self { + Profile::Debug => "debug", + Profile::Release => "release", + } + } +} + impl fmt::Display for Profile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:?}") @@ -30,84 +48,30 @@ enum Arch { X86_64, } -impl fmt::Display for Arch { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{self:?}") - } -} - -// TODO This is becoming a bag of random fields - maybe turn into an enum -struct BuildParams { - arch: Arch, - profile: Profile, - verbose: bool, - wait_for_gdb: bool, - config: Configuration, - dump_dtb: String, - json_output: bool, -} - -impl BuildParams { - fn new(matches: &clap::ArgMatches) -> Self { - let profile = if matches.try_contains_id("release").unwrap_or(false) { - Profile::Release - } else { - Profile::Debug - }; - let verbose = matches.get_flag("verbose"); - let arch = matches.try_get_one("arch").ok().flatten().unwrap_or(&Arch::X86_64); - let wait_for_gdb = - matches.try_contains_id("gdb").unwrap_or(false) && matches.get_flag("gdb"); - - let dump_dtb: String = matches - .try_get_one::("dump_dtb") - .ok() - .flatten() - .unwrap_or(&"".to_string()) - .clone(); - let default = "default".to_string(); - let config_file = matches.try_get_one("config").ok().flatten().unwrap_or(&default); - let config = read_config(format!( - "{}/{}/lib/config_{}.toml", - workspace().display(), - arch.to_string().to_lowercase(), - config_file - )); - - // This is a very awkward way to check a boolean which may not exist... - let json_output = matches.try_contains_id("json").unwrap_or(false) - && matches.value_source("json") != Some(clap::parser::ValueSource::DefaultValue); - - Self { arch: *arch, profile, verbose, wait_for_gdb, dump_dtb, config, json_output } - } - - fn dir(&self) -> &'static str { - match self.profile { - Profile::Debug => "debug", - Profile::Release => "release", - } - } - - fn add_build_arg(&self, cmd: &mut Command) { - if let Profile::Release = self.profile { - cmd.arg("--release"); - } +impl Arch { + fn from(matches: &clap::ArgMatches) -> Self { + *matches.get_one::("arch").unwrap_or(&Arch::X86_64) } fn qemu_system(&self) -> String { - let defaultqemu = match self.arch { - Arch::Aarch64 => "qemu-system-aarch64", - Arch::Riscv64 => "qemu-system-riscv64", - Arch::X86_64 => "qemu-system-x86_64", - }; - env_or("QEMU", defaultqemu) + env_or( + "QEMU", + match self { + Arch::Aarch64 => "qemu-system-aarch64", + Arch::Riscv64 => "qemu-system-riscv64", + Arch::X86_64 => "qemu-system-x86_64", + }, + ) } fn target(&self) -> String { - env_or( - "TARGET", - format!("{}-unknown-none-elf", self.arch.to_string().to_lowercase()).as_str(), - ) + env_or("TARGET", format!("{}-unknown-none-elf", self.to_string().to_lowercase()).as_str()) + } +} + +impl fmt::Display for Arch { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{self:?}") } } @@ -121,11 +85,25 @@ impl RustupState { /// Also caches the current toolchain. fn new() -> Self { Self { - installed_targets: rustup_configurator::installed().unwrap(), + installed_targets: Self::installed_rustup_targets().unwrap(), curr_toolchain: env::var("RUSTUP_TOOLCHAIN").unwrap(), } } + /// Call `rustup target list --installed` to get all installed target triples + fn installed_rustup_targets() -> Result> { + let output = + Command::new("rustup").arg("target").arg("list").arg("--installed").output()?; + if !output.status.success() { + return Err(String::from_utf8(output.stdout.clone())?.into()); + } + + Ok(String::from_utf8(output.stdout.clone())? + .lines() + .flat_map(|line| Triple::from_str(line)) + .collect()) + } + /// For the given arch, return a compatible toolchain triple that is /// installed and can be used by cargo check. It will prefer the default /// toolchain if it's a match, otherwise it will look for the @@ -152,6 +130,7 @@ fn main() { .version("0.1.0") .author("The r9 Authors") .about("Build support for the r9 operating system") + .arg_required_else_help(true) .subcommand( clap::Command::new("build").about("Builds r9").args(&[ clap::arg!(--release "Build release version").conflicts_with("debug"), @@ -197,6 +176,7 @@ fn main() { .subcommand(clap::Command::new("test").about("Runs unit tests").args(&[ clap::arg!(--release "Build a release version").conflicts_with("debug"), clap::arg!(--debug "Build a debug version").conflicts_with("release"), + clap::arg!(--json "Output messages as json"), clap::arg!(--verbose "Print commands"), ])) .subcommand( @@ -222,6 +202,7 @@ fn main() { clap::arg!(--arch "Target architecture") .value_parser(clap::builder::EnumValueParser::::new()), clap::arg!(--gdb "Wait for gdb connection on start"), + clap::arg!(--kvm "Run with KVM"), clap::arg!(--config "Configuration") .value_parser(clap::builder::NonEmptyStringValueParser::new()) .default_value("default"), @@ -230,32 +211,28 @@ fn main() { .value_parser(clap::value_parser!(String)), ]), ) - .subcommand( - clap::Command::new("qemukvm").about("Run r9 under QEMU with KVM").args(&[ - clap::arg!(--release "Build a release version").conflicts_with("debug"), - clap::arg!(--debug "Build a debug version").conflicts_with("release"), - clap::arg!(--arch "Target architecture") - .value_parser(clap::builder::EnumValueParser::::new()), - clap::arg!(--gdb "Wait for gdb connection on start"), - clap::arg!(--verbose "Print commands"), - clap::arg!(--dump_dtb "Dump the DTB from QEMU to a file") - .value_parser(clap::value_parser!(String)), - ]), - ) .subcommand(clap::Command::new("clean").about("Cargo clean")) .get_matches(); if let Err(e) = match matches.subcommand() { - Some(("build", m)) => build(&BuildParams::new(m)), - Some(("expand", m)) => expand(&BuildParams::new(m)), - Some(("kasm", m)) => kasm(&BuildParams::new(m)), - Some(("dist", m)) => dist(&BuildParams::new(m)), - Some(("test", m)) => test(&BuildParams::new(m)), - Some(("clippy", m)) => clippy(&BuildParams::new(m)), - Some(("check", m)) => check(&BuildParams::new(m)), - Some(("qemu", m)) => run(&BuildParams::new(m)), - Some(("qemukvm", m)) => accelrun(&BuildParams::new(m)), - Some(("clean", _)) => clean(), + Some(("build", m)) => BuildStep::new(m).run(), + Some(("expand", m)) => ExpandStep::new(m).run(), + Some(("kasm", m)) => KasmStep::new(m).run(), + Some(("dist", m)) => { + let s1 = BuildStep::new(m); + let s2 = DistStep::new(m); + s1.run().and_then(|_| s2.run()) + } + Some(("test", m)) => TestStep::new(m).run(), + Some(("clippy", m)) => ClippyStep::new(m).run(), + Some(("check", m)) => CheckStep::new(m).run(), + Some(("qemu", m)) => { + let s1 = BuildStep::new(m); + let s2 = DistStep::new(m); + let s3 = QemuStep::new(m); + s1.run().and_then(|_| s2.run()).and_then(|_| s3.run()) + } + Some(("clean", _)) => CleanStep::new().run(), _ => Err("bad subcommand".into()), } { eprintln!("{e}"); @@ -299,459 +276,603 @@ fn objcopy() -> String { env_or("OBJCOPY", &llvm_objcopy) } -fn build(build_params: &BuildParams) -> Result<()> { - let mut cmd = generate_args( - "build", - &build_params.config, - &build_params.target(), - &build_params.profile, - workspace().to_str().unwrap(), - ); - cmd.current_dir(workspace()); - cmd.arg("--workspace"); - cmd.arg("--exclude").arg("xtask"); - exclude_other_arches(build_params.arch, &mut cmd); - build_params.add_build_arg(&mut cmd); - if build_params.verbose { - println!("Executing {cmd:?}"); - } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("build kernel failed".into()); - } - Ok(()) -} - -fn expand(build_params: &BuildParams) -> Result<()> { - let mut cmd = Command::new(cargo()); - cmd.current_dir(workspace()); - cmd.arg("rustc"); - cmd.arg("-Z").arg("build-std=core,alloc"); - cmd.arg("-p").arg(build_params.arch.to_string().to_lowercase()); - cmd.arg("--target").arg(format!("lib/{}.json", build_params.target())); - cmd.arg("--"); - cmd.arg("-Z").arg("unpretty=expanded"); - build_params.add_build_arg(&mut cmd); - if build_params.verbose { - println!("Executing {cmd:?}"); - } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("build kernel failed".into()); - } - Ok(()) -} - -fn kasm(build_params: &BuildParams) -> Result<()> { - let mut cmd = Command::new(cargo()); - cmd.current_dir(workspace()); - cmd.arg("rustc"); - cmd.arg("-Z").arg("build-std=core,alloc"); - cmd.arg("-p").arg(build_params.arch.to_string().to_lowercase()); - cmd.arg("--target").arg(format!("lib/{}.json", build_params.target())); - cmd.arg("--").arg("--emit").arg("asm"); - build_params.add_build_arg(&mut cmd); - if build_params.verbose { - println!("Executing {cmd:?}"); +fn load_config(arch: Arch, matches: &clap::ArgMatches) -> Configuration { + let default = "default".to_string(); + let config_file = matches.try_get_one("config").ok().flatten().unwrap_or(&default); + Configuration::load(format!( + "{}/{}/lib/config_{}.toml", + workspace().display(), + arch.to_string().to_lowercase(), + config_file + )) +} + +fn verbose(matches: &clap::ArgMatches) -> bool { + matches.get_flag("verbose") +} + +struct BuildStep { + arch: Arch, + config: Configuration, + profile: Profile, + verbose: bool, +} + +impl BuildStep { + fn new(matches: &clap::ArgMatches) -> Self { + let arch = *matches.get_one::("arch").unwrap(); + let config = load_config(arch, matches); + let profile = Profile::from(matches); + let verbose = verbose(matches); + + Self { arch, config, profile, verbose } } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("build kernel failed".into()); + + fn run(self) -> Result<()> { + let mut cmd = generate_args( + "build", + &self.config, + &self.arch.target(), + &self.profile, + workspace().to_str().unwrap(), + ); + cmd.current_dir(workspace()); + cmd.arg("--workspace"); + cmd.arg("--exclude").arg("xtask"); + exclude_other_arches(self.arch, &mut cmd); + if self.profile == Profile::Release { + cmd.arg("--release"); + } + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("build kernel failed".into()); + } + Ok(()) } - Ok(()) } -fn dist(build_params: &BuildParams) -> Result<()> { - build(build_params)?; +struct DistStep { + arch: Arch, + profile: Profile, + verbose: bool, +} - match build_params.arch { - Arch::Aarch64 => { - // Qemu needs a flat binary in order to handle device tree files correctly - let mut cmd = Command::new(objcopy()); - cmd.arg("-O"); - cmd.arg("binary"); - cmd.arg(format!("target/{}/{}/aarch64", build_params.target(), build_params.dir())); - cmd.arg(format!( - "target/{}/{}/aarch64-qemu", - build_params.target(), - build_params.dir() - )); - cmd.current_dir(workspace()); - if build_params.verbose { - println!("Executing {cmd:?}"); - } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("objcopy failed".into()); - } +impl DistStep { + fn new(matches: &clap::ArgMatches) -> Self { + let arch = Arch::from(matches); + let profile = Profile::from(matches); + let verbose = verbose(matches); + Self { arch, profile, verbose } + } - // Compress the binary. We do this because they're much faster when used - // for netbooting and qemu also accepts them. - let mut cmd = Command::new("gzip"); - cmd.arg("-k"); - cmd.arg("-f"); - cmd.arg(format!( - "target/{}/{}/aarch64-qemu", - build_params.target(), - build_params.dir() - )); - cmd.current_dir(workspace()); - if build_params.verbose { - println!("Executing {cmd:?}"); + fn run(self) -> Result<()> { + match self.arch { + Arch::Aarch64 => { + // Qemu needs a flat binary in order to handle device tree files correctly + let mut cmd = Command::new(objcopy()); + cmd.arg("-O"); + cmd.arg("binary"); + cmd.arg(format!("target/{}/{}/aarch64", self.arch.target(), self.profile.dir())); + cmd.arg(format!( + "target/{}/{}/aarch64-qemu", + self.arch.target(), + self.profile.dir() + )); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("objcopy failed".into()); + } + + // Compress the binary. We do this because they're much faster when used + // for netbooting and qemu also accepts them. + let mut cmd = Command::new("gzip"); + cmd.arg("-k"); + cmd.arg("-f"); + cmd.arg(format!( + "target/{}/{}/aarch64-qemu", + self.arch.target(), + self.profile.dir() + )); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("gzip failed".into()); + } } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("gzip failed".into()); + Arch::X86_64 => { + let mut cmd = Command::new(objcopy()); + cmd.arg("--input-target=elf64-x86-64"); + cmd.arg("--output-target=elf32-i386"); + cmd.arg(format!("target/{}/{}/x86_64", self.arch.target(), self.profile.dir())); + cmd.arg(format!("target/{}/{}/r9.elf32", self.arch.target(), self.profile.dir())); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("objcopy failed".into()); + } } - } - Arch::X86_64 => { - let mut cmd = Command::new(objcopy()); - cmd.arg("--input-target=elf64-x86-64"); - cmd.arg("--output-target=elf32-i386"); - cmd.arg(format!("target/{}/{}/x86_64", build_params.target(), build_params.dir())); - cmd.arg(format!("target/{}/{}/r9.elf32", build_params.target(), build_params.dir())); - cmd.current_dir(workspace()); - if build_params.verbose { - println!("Executing {cmd:?}"); - } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("objcopy failed".into()); + Arch::Riscv64 => { + // Qemu needs a flat binary in order to handle device tree files correctly + let mut cmd = Command::new(objcopy()); + cmd.arg("-O"); + cmd.arg("binary"); + cmd.arg(format!("target/{}/{}/riscv64", self.arch.target(), self.profile.dir())); + cmd.arg(format!( + "target/{}/{}/riscv64-qemu", + self.arch.target(), + self.profile.dir() + )); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("objcopy failed".into()); + } } + }; + + Ok(()) + } +} + +struct QemuStep { + arch: Arch, + profile: Profile, + wait_for_gdb: bool, + kvm: bool, + dump_dtb: String, + verbose: bool, +} + +impl QemuStep { + fn new(matches: &clap::ArgMatches) -> Self { + let arch = Arch::from(matches); + let profile = Profile::from(matches); + let wait_for_gdb = matches.get_flag("gdb"); + let kvm = matches.get_flag("kvm"); + let dump_dtb: String = matches + .try_get_one::("dump_dtb") + .ok() + .flatten() + .unwrap_or(&"".to_string()) + .clone(); + let verbose = verbose(matches); + + Self { arch, profile, wait_for_gdb, kvm, dump_dtb, verbose } + } + + fn run(self) -> Result<()> { + let target = self.arch.target(); + let dir = self.profile.dir(); + let qemu_system = self.arch.qemu_system(); + + if self.kvm && self.arch != Arch::X86_64 { + return Err("KVM only supported under x86-64".into()); } - Arch::Riscv64 => { - // Qemu needs a flat binary in order to handle device tree files correctly - let mut cmd = Command::new(objcopy()); - cmd.arg("-O"); - cmd.arg("binary"); - cmd.arg(format!("target/{}/{}/riscv64", build_params.target(), build_params.dir())); - cmd.arg(format!( - "target/{}/{}/riscv64-qemu", - build_params.target(), - build_params.dir() - )); - cmd.current_dir(workspace()); - if build_params.verbose { - println!("Executing {cmd:?}"); + + match self.arch { + Arch::Aarch64 => { + let mut cmd = Command::new(qemu_system); + + // TODO Choose UART at cmdline + // If using UART0 (PL011), this enables serial + cmd.arg("-nographic"); + + // If using UART1 (MiniUART), this enables serial + cmd.arg("-serial"); + cmd.arg("null"); + cmd.arg("-serial"); + cmd.arg("mon:stdio"); + + cmd.arg("-M"); + cmd.arg("raspi3b"); + if self.wait_for_gdb { + cmd.arg("-s").arg("-S"); + } + cmd.arg("-dtb"); + cmd.arg("aarch64/lib/bcm2710-rpi-3-b.dtb"); + // Show exception level change events in stdout + cmd.arg("-d"); + cmd.arg("int"); + cmd.arg("-kernel"); + cmd.arg(format!("target/{}/{}/aarch64-qemu.gz", target, dir)); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("qemu failed".into()); + } } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("objcopy failed".into()); + Arch::Riscv64 => { + let mut cmd = Command::new(qemu_system); + cmd.arg("-nographic"); + //cmd.arg("-curses"); + // cmd.arg("-bios").arg("none"); + let dump_dtb = &self.dump_dtb; + if dump_dtb != "" { + cmd.arg("-machine").arg(format!("virt,dumpdtb={dump_dtb}")); + } else { + cmd.arg("-machine").arg("virt"); + } + cmd.arg("-cpu").arg("rv64"); + // FIXME: This is not needed as of now, and will only work once the + // FIXME: // disk.bin is also taken care of. Doesn't exist by default. + if false { + cmd.arg("-drive").arg("file=disk.bin,format=raw,id=hd0"); + cmd.arg("-device").arg("virtio-blk-device,drive=hd0"); + } + cmd.arg("-netdev").arg("type=user,id=net0"); + cmd.arg("-device").arg("virtio-net-device,netdev=net0"); + cmd.arg("-smp").arg("4"); + cmd.arg("-m").arg("1024M"); + cmd.arg("-serial").arg("mon:stdio"); + if self.wait_for_gdb { + cmd.arg("-s").arg("-S"); + } + cmd.arg("-d").arg("guest_errors,unimp"); + cmd.arg("-kernel"); + cmd.arg(format!("target/{}/{}/riscv64", target, dir)); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("qemu failed".into()); + } } - } - }; + Arch::X86_64 => { + let mut cmd = Command::new(qemu_system); + cmd.arg("-nographic"); + //cmd.arg("-curses"); + if self.kvm { + cmd.arg("-accel").arg("kvm"); + cmd.arg("-cpu").arg("host,pdpe1gb,xsaveopt,fsgsbase,apic,msr"); + } else { + cmd.arg("-M").arg("q35"); + cmd.arg("-cpu").arg("qemu64,pdpe1gb,xsaveopt,fsgsbase,apic,msr"); + } + cmd.arg("-smp"); + cmd.arg("8"); + cmd.arg("-s"); + cmd.arg("-m"); + cmd.arg("8192"); + if self.wait_for_gdb { + cmd.arg("-s").arg("-S"); + } + //cmd.arg("-device"); + //cmd.arg("ahci,id=ahci0"); + //cmd.arg("-drive"); + //cmd.arg("id=sdahci0,file=sdahci0.img,if=none"); + //cmd.arg("-device"); + //cmd.arg("ide-hd,drive=sdahci0,bus=ahci0.0"); + cmd.arg("-kernel"); + cmd.arg(format!("target/{}/{}/r9.elf32", target, dir)); + cmd.current_dir(workspace()); + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("qemu failed".into()); + } + } + }; - Ok(()) + Ok(()) + } } -/// Run tests for the current host toolchain. -fn test(build_params: &BuildParams) -> Result<()> { - let mut all_cmd_args = Vec::new(); - - all_cmd_args.push(vec![ - "test".to_string(), - "--package".to_string(), - "port".to_string(), - "--lib".to_string(), - ]); +struct ExpandStep { + arch: Arch, + profile: Profile, + verbose: bool, +} - let rustup_state = RustupState::new(); +impl ExpandStep { + fn new(matches: &clap::ArgMatches) -> Self { + let arch = Arch::from(matches); + let profile = Profile::from(matches); + let verbose = verbose(matches); - let arch = std::env::consts::ARCH; - if let Some(target) = rustup_state.std_supported_target(arch) { - all_cmd_args.push(vec![ - "test".to_string(), - "--package".to_string(), - arch.to_string(), - "--bins".to_string(), - "--target".to_string(), - target.to_string(), - ]); + Self { arch, profile, verbose } } - for cmd_args in all_cmd_args { + fn run(self) -> Result<()> { let mut cmd = Command::new(cargo()); cmd.current_dir(workspace()); - - cmd.args(cmd_args); - if build_params.json_output { - cmd.arg("--message-format=json").arg("--quiet"); + cmd.arg("rustc"); + cmd.arg("-Z").arg("build-std=core,alloc"); + cmd.arg("-p").arg(self.arch.to_string().to_lowercase()); + cmd.arg("--target").arg(format!("lib/{}.json", self.arch.target())); + cmd.arg("--"); + cmd.arg("-Z").arg("unpretty=expanded"); + if self.profile == Profile::Release { + cmd.arg("--release"); } - - if build_params.verbose { + if self.verbose { println!("Executing {cmd:?}"); } let status = annotated_status(&mut cmd)?; if !status.success() { - return Err("check failed".into()); + return Err("build kernel failed".into()); } + Ok(()) } - Ok(()) -} - -fn clippy(build_params: &BuildParams) -> Result<()> { - let mut cmd = generate_args( - "clippy", - &build_params.config, - &build_params.target(), - &build_params.profile, - workspace().to_str().unwrap(), - ); - cmd.current_dir(workspace()); - cmd.arg("--workspace"); - exclude_other_arches(build_params.arch, &mut cmd); - build_params.add_build_arg(&mut cmd); - if build_params.verbose { - println!("Executing {cmd:?}"); - } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("clippy failed".into()); - } - Ok(()) } -/// Run check for all packages for all relevant toolchains. -/// This assumes that the -unknown-linux-gnu toolchain has been installed -/// for any arch we care about. -fn check(build_params: &BuildParams) -> Result<()> { - // To run check for bins and lib we use the default toolchain, which has - // been set to the OS-independent arch toolchain in each Cargo.toml file. - // The same applies to tests and benches for non-arch-specific lib packages. - let bins_lib_package_cmd_args = vec![ - vec![ - "check".to_string(), - "--package".to_string(), - "aarch64".to_string(), - "--bins".to_string(), - ], - vec![ - "check".to_string(), - "--package".to_string(), - "riscv64".to_string(), - "--bins".to_string(), - ], - vec![ - "check".to_string(), - "--package".to_string(), - "x86_64".to_string(), - "--bins".to_string(), - ], - vec![ - "check".to_string(), - "--package".to_string(), - "port".to_string(), - "--lib".to_string(), - "--tests".to_string(), - "--benches".to_string(), - ], - ]; - - let rustup_state = RustupState::new(); - - // However, running check for tests and benches in arch packages requires - // that we use a toolchain with `std`, so we need an OS-specific toolchain. - // If the arch matches that of the current toolchain, then that will be used - // for check. Otherwise we'll always default to -unknown-linux-gnu. - let mut benches_tests_package_cmd_args = Vec::new(); - - for arch in ["aarch64", "riscv64", "x86_64"] { - let Some(target) = rustup_state.std_supported_target(arch) else { - continue; - }; +struct KasmStep { + arch: Arch, + profile: Profile, + verbose: bool, +} - benches_tests_package_cmd_args.push(vec![ - "check".to_string(), - "--package".to_string(), - arch.to_string(), - "--tests".to_string(), - "--benches".to_string(), - "--target".to_string(), - target.to_string(), - ]); +impl KasmStep { + fn new(matches: &clap::ArgMatches) -> Self { + let arch = Arch::from(matches); + let profile = Profile::from(matches); + let verbose = verbose(matches); + + Self { arch, profile, verbose } } - for cmd_args in [bins_lib_package_cmd_args, benches_tests_package_cmd_args].concat() { + fn run(self) -> Result<()> { let mut cmd = Command::new(cargo()); - cmd.args(cmd_args); - if build_params.json_output { - cmd.arg("--message-format=json").arg("--quiet"); - } cmd.current_dir(workspace()); - - if build_params.verbose { + cmd.arg("rustc"); + cmd.arg("-Z").arg("build-std=core,alloc"); + cmd.arg("-p").arg(self.arch.to_string().to_lowercase()); + cmd.arg("--target").arg(format!("lib/{}.json", self.arch.target())); + cmd.arg("--").arg("--emit").arg("asm"); + if self.profile == Profile::Release { + cmd.arg("--release"); + } + if self.verbose { println!("Executing {cmd:?}"); } let status = annotated_status(&mut cmd)?; if !status.success() { - return Err("check failed".into()); + return Err("build kernel failed".into()); } + Ok(()) } - Ok(()) } -fn run(build_params: &BuildParams) -> Result<()> { - dist(build_params)?; +/// Run tests for the current host toolchain. +struct TestStep { + json_output: bool, + verbose: bool, +} - match build_params.arch { - Arch::Aarch64 => { - let mut cmd = Command::new(build_params.qemu_system()); - - // TODO Choose UART at cmdline - // If using UART0 (PL011), this enables serial - cmd.arg("-nographic"); - - // If using UART1 (MiniUART), this enables serial - cmd.arg("-serial"); - cmd.arg("null"); - cmd.arg("-serial"); - cmd.arg("mon:stdio"); - - cmd.arg("-M"); - cmd.arg("raspi3b"); - if build_params.wait_for_gdb { - cmd.arg("-s").arg("-S"); - } - cmd.arg("-dtb"); - cmd.arg("aarch64/lib/bcm2710-rpi-3-b.dtb"); - // Show exception level change events in stdout - cmd.arg("-d"); - cmd.arg("int"); - cmd.arg("-kernel"); - cmd.arg(format!( - "target/{}/{}/aarch64-qemu.gz", - build_params.target(), - build_params.dir() - )); - cmd.current_dir(workspace()); - if build_params.verbose { - println!("Executing {cmd:?}"); - } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("qemu failed".into()); - } +impl TestStep { + fn new(matches: &clap::ArgMatches) -> Self { + let json_output = matches.get_flag("json"); + let verbose = verbose(matches); + + Self { json_output, verbose } + } + + fn run(self) -> Result<()> { + let mut all_cmd_args = Vec::new(); + + all_cmd_args.push(vec![ + "test".to_string(), + "--package".to_string(), + "port".to_string(), + "--lib".to_string(), + ]); + + let rustup_state = RustupState::new(); + + let arch = std::env::consts::ARCH; + if let Some(target) = rustup_state.std_supported_target(arch) { + all_cmd_args.push(vec![ + "test".to_string(), + "--package".to_string(), + arch.to_string(), + "--bins".to_string(), + "--target".to_string(), + target.to_string(), + ]); } - Arch::Riscv64 => { - let mut cmd = Command::new(build_params.qemu_system()); - cmd.arg("-nographic"); - //cmd.arg("-curses"); - // cmd.arg("-bios").arg("none"); - let dump_dtb = &build_params.dump_dtb; - if dump_dtb != "" { - cmd.arg("-machine").arg(format!("virt,dumpdtb={dump_dtb}")); - } else { - cmd.arg("-machine").arg("virt"); - } - cmd.arg("-cpu").arg("rv64"); - // FIXME: This is not needed as of now, and will only work once the - // FIXME: // disk.bin is also taken care of. Doesn't exist by default. - if false { - cmd.arg("-drive").arg("file=disk.bin,format=raw,id=hd0"); - cmd.arg("-device").arg("virtio-blk-device,drive=hd0"); - } - cmd.arg("-netdev").arg("type=user,id=net0"); - cmd.arg("-device").arg("virtio-net-device,netdev=net0"); - cmd.arg("-smp").arg("4"); - cmd.arg("-m").arg("1024M"); - cmd.arg("-serial").arg("mon:stdio"); - if build_params.wait_for_gdb { - cmd.arg("-s").arg("-S"); - } - cmd.arg("-d").arg("guest_errors,unimp"); - cmd.arg("-kernel"); - cmd.arg(format!("target/{}/{}/riscv64", build_params.target(), build_params.dir())); + + for cmd_args in all_cmd_args { + let mut cmd = Command::new(cargo()); cmd.current_dir(workspace()); - if build_params.verbose { + + cmd.args(cmd_args); + if self.json_output { + cmd.arg("--message-format=json").arg("--quiet"); + } + + if self.verbose { println!("Executing {cmd:?}"); } let status = annotated_status(&mut cmd)?; if !status.success() { - return Err("qemu failed".into()); + return Err("test failed".into()); } } - Arch::X86_64 => { - let mut cmd = Command::new(build_params.qemu_system()); - cmd.arg("-nographic"); - //cmd.arg("-curses"); - cmd.arg("-M"); - cmd.arg("q35"); - cmd.arg("-cpu"); - cmd.arg("qemu64,pdpe1gb,xsaveopt,fsgsbase,apic,msr"); - cmd.arg("-smp"); - cmd.arg("8"); - cmd.arg("-s"); - cmd.arg("-m"); - cmd.arg("8192"); - if build_params.wait_for_gdb { - cmd.arg("-s").arg("-S"); + Ok(()) + } +} + +struct ClippyStep { + arch: Arch, + config: Configuration, + profile: Profile, + verbose: bool, +} + +impl ClippyStep { + fn new(matches: &clap::ArgMatches) -> Self { + let arch = Arch::from(matches); + let config = load_config(arch, matches); + let profile = Profile::from(matches); + let verbose = verbose(matches); + + Self { arch, config, profile, verbose } + } + + fn run(self) -> Result<()> { + let mut cmd = generate_args( + "clippy", + &self.config, + &self.arch.target(), + &self.profile, + workspace().to_str().unwrap(), + ); + cmd.current_dir(workspace()); + cmd.arg("--workspace"); + exclude_other_arches(self.arch, &mut cmd); + if self.profile == Profile::Release { + cmd.arg("--release"); + } + if self.verbose { + println!("Executing {cmd:?}"); + } + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("clippy failed".into()); + } + Ok(()) + } +} + +/// Run check for all packages for all relevant toolchains. +/// This assumes that the -unknown-linux-gnu toolchain has been installed +/// for any arch we care about. +struct CheckStep { + json_output: bool, + verbose: bool, +} + +impl CheckStep { + fn new(matches: &clap::ArgMatches) -> Self { + let json_output = matches.get_flag("json"); + let verbose = verbose(matches); + + Self { json_output, verbose } + } + + fn run(self) -> Result<()> { + // To run check for bins and lib we use the default toolchain, which has + // been set to the OS-independent arch toolchain in each Cargo.toml file. + // The same applies to tests and benches for non-arch-specific lib packages. + let bins_lib_package_cmd_args = vec![ + vec![ + "check".to_string(), + "--package".to_string(), + "aarch64".to_string(), + "--bins".to_string(), + ], + vec![ + "check".to_string(), + "--package".to_string(), + "riscv64".to_string(), + "--bins".to_string(), + ], + vec![ + "check".to_string(), + "--package".to_string(), + "x86_64".to_string(), + "--bins".to_string(), + ], + vec![ + "check".to_string(), + "--package".to_string(), + "port".to_string(), + "--lib".to_string(), + "--tests".to_string(), + "--benches".to_string(), + ], + ]; + + let rustup_state = RustupState::new(); + + // However, running check for tests and benches in arch packages requires + // that we use a toolchain with `std`, so we need an OS-specific toolchain. + // If the arch matches that of the current toolchain, then that will be used + // for check. Otherwise we'll always default to -unknown-linux-gnu. + let mut benches_tests_package_cmd_args = Vec::new(); + + for arch in ["aarch64", "riscv64", "x86_64"] { + let Some(target) = rustup_state.std_supported_target(arch) else { + continue; + }; + + benches_tests_package_cmd_args.push(vec![ + "check".to_string(), + "--package".to_string(), + arch.to_string(), + "--tests".to_string(), + "--benches".to_string(), + "--target".to_string(), + target.to_string(), + ]); + } + + for cmd_args in [bins_lib_package_cmd_args, benches_tests_package_cmd_args].concat() { + let mut cmd = Command::new(cargo()); + cmd.args(cmd_args); + if self.json_output { + cmd.arg("--message-format=json").arg("--quiet"); } - //cmd.arg("-device"); - //cmd.arg("ahci,id=ahci0"); - //cmd.arg("-drive"); - //cmd.arg("id=sdahci0,file=sdahci0.img,if=none"); - //cmd.arg("-device"); - //cmd.arg("ide-hd,drive=sdahci0,bus=ahci0.0"); - cmd.arg("-kernel"); - cmd.arg(format!("target/{}/{}/r9.elf32", build_params.target(), build_params.dir())); cmd.current_dir(workspace()); - if build_params.verbose { + + if self.verbose { println!("Executing {cmd:?}"); } let status = annotated_status(&mut cmd)?; if !status.success() { - return Err("qemu failed".into()); + return Err("check failed".into()); } } - }; - - Ok(()) -} - -fn accelrun(build_params: &BuildParams) -> Result<()> { - dist(build_params)?; - let mut cmd = Command::new(build_params.qemu_system()); - cmd.arg("-nographic"); - cmd.arg("-accel"); - cmd.arg("kvm"); - cmd.arg("-cpu"); - cmd.arg("host,pdpe1gb,xsaveopt,fsgsbase,apic,msr"); - cmd.arg("-smp"); - cmd.arg("8"); - cmd.arg("-m"); - cmd.arg("8192"); - if build_params.wait_for_gdb { - cmd.arg("-s").arg("-S"); - } - cmd.arg("-kernel"); - cmd.arg(format!("target/{}/{}/r9.elf32", build_params.target(), build_params.dir())); - cmd.current_dir(workspace()); - if build_params.verbose { - println!("Executing {cmd:?}"); + Ok(()) } - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("qemu failed".into()); - } - Ok(()) } -fn clean() -> Result<()> { - let mut cmd = Command::new(cargo()); - cmd.current_dir(workspace()); - cmd.arg("clean"); - let status = annotated_status(&mut cmd)?; - if !status.success() { - return Err("clean failed".into()); +struct CleanStep {} + +impl CleanStep { + fn new() -> Self { + Self {} + } + + fn run(self) -> Result<()> { + let mut cmd = Command::new(cargo()); + cmd.current_dir(workspace()); + cmd.arg("clean"); + let status = annotated_status(&mut cmd)?; + if !status.success() { + return Err("clean failed".into()); + } + Ok(()) } - Ok(()) } fn workspace() -> PathBuf { Path::new(&env!("CARGO_MANIFEST_DIR")).ancestors().nth(1).unwrap().to_path_buf() } -// Exclude architectures other than the one being built +/// Exclude architectures other than the one being built fn exclude_other_arches(arch: Arch, cmd: &mut Command) { match arch { Arch::Aarch64 => { @@ -769,7 +890,7 @@ fn exclude_other_arches(arch: Arch, cmd: &mut Command) { } } -// Annotates the error result with the calling binary's name. +/// Annotates the error result with the calling binary's name. fn annotated_status(cmd: &mut Command) -> Result { Ok(cmd.status().map_err(|e| format!("{}: {}", cmd.get_program().to_string_lossy(), e))?) }