Browse Source

feat(dadk-config): 添加boot config (#69)

* feat(dadk-config): 添加boot config


Signed-off-by: longjin <longjin@DragonOS.org>
LoGin 5 months ago
parent
commit
285d70f183

+ 4 - 0
dadk-config/Cargo.toml

@@ -11,8 +11,12 @@ authors = [
 [dependencies]
 anyhow = { version = "1.0.90", features = ["std", "backtrace"] }
 cfg-if = "1.0.0"
+env_logger = "0.11.5"
+indexmap = "2.6.0"
+log = "0.4.22"
 serde = { version = "1.0.160", features = ["serde_derive"] }
 serde_json = "1.0.96"
+shlex = "1.3.0"
 toml = "0.8.12"
 
 # 只有在test的情况下才会引入下列库

+ 8 - 0
dadk-config/src/boot/dragonstub.rs

@@ -0,0 +1,8 @@
+use serde::Deserialize;
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct DragonStubConfig {
+    /// The path to the source code of the DragonStub project.
+    #[serde(rename = "src-path")]
+    pub src_path: String,
+}

+ 94 - 0
dadk-config/src/boot/grub.rs

@@ -0,0 +1,94 @@
+use serde::Deserialize;
+
+/// Default time for GRUB to wait for user selection
+const GRUB_DEFAULT_TIMEOUT: u32 = 10;
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct GrubConfig {
+    /// Time to wait for user selection before booting
+    #[serde(default = "default_timeout")]
+    pub timeout: u32,
+
+    #[serde(rename = "i386-legacy")]
+    pub i386_legacy: Option<ArchConfig>,
+    #[serde(rename = "i386-efi")]
+    pub i386_efi: Option<ArchConfig>,
+    #[serde(rename = "x86_64-efi")]
+    pub x86_64_efi: Option<ArchConfig>,
+}
+
+const fn default_timeout() -> u32 {
+    GRUB_DEFAULT_TIMEOUT
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct ArchConfig {
+    /// 指向grub-file的路径
+    #[serde(rename = "grub-file")]
+    pub grub_file: String,
+    /// 指向grub-install的路径
+    #[serde(rename = "grub-install")]
+    pub grub_install: String,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// Test if the GRUB configuration parsing is correct for all architectures
+    #[test]
+    fn test_all_architectures() {
+        let toml = r#"
+        timeout = 15
+        [i386-legacy]
+        grub-file = "/opt/dragonos-grub/arch/i386/legacy/grub/bin/grub-file"
+        grub-install = "/opt/dragonos-grub/arch/i386/legacy/grub/sbin/grub-install"
+        [i386-efi]
+        grub-file = "/opt/dragonos-grub/arch/i386/efi/grub/bin/grub-file"
+        grub-install = "/opt/dragonos-grub/arch/i386/efi/grub/sbin/grub-install"
+        [x86_64-efi]
+        grub-file = "/opt/dragonos-grub/arch/x86_64/efi/grub/bin/grub-file"
+        grub-install = "/opt/dragonos-grub/arch/x86_64/efi/grub/sbin/grub-install"
+        "#;
+        let config: GrubConfig = toml::from_str(toml).unwrap();
+        assert_eq!(config.timeout, 15);
+        assert!(config.i386_legacy.is_some());
+        assert!(config.i386_efi.is_some());
+        assert!(config.x86_64_efi.is_some());
+    }
+
+    #[test]
+    fn test_default_timeout() {
+        let toml = r#"
+        [i386-legacy]
+        grub-file = "grub Legacy"
+        grub-install = "/boot/grub/i386-legacy"
+        "#;
+        let config: GrubConfig = toml::from_str(toml).unwrap();
+        assert_eq!(config.timeout, GRUB_DEFAULT_TIMEOUT);
+    }
+
+    #[test]
+    fn test_custom_timeout() {
+        let toml = r#"
+        timeout = 5
+        [i386-efi]
+        grub-file = "grub EFI"
+        grub-install = "/boot/grub/i386-efi"
+        "#;
+        let config: GrubConfig = toml::from_str(toml).unwrap();
+        assert_eq!(config.timeout, 5);
+    }
+
+    #[test]
+    fn test_no_architectures() {
+        let toml = r#"
+        timeout = 20
+        "#;
+        let config: GrubConfig = toml::from_str(toml).unwrap();
+        assert_eq!(config.timeout, 20);
+        assert!(config.i386_legacy.is_none());
+        assert!(config.i386_efi.is_none());
+        assert!(config.x86_64_efi.is_none());
+    }
+}

+ 0 - 0
dadk-config/src/hypervisor/hyp_type.rs → dadk-config/src/boot/hypervisor/hyp_type.rs


+ 2 - 0
dadk-config/src/hypervisor/mod.rs → dadk-config/src/boot/hypervisor/mod.rs

@@ -1 +1,3 @@
 pub mod hyp_type;
+
+pub mod qemu;

+ 296 - 0
dadk-config/src/boot/hypervisor/qemu.rs

@@ -0,0 +1,296 @@
+//! This file contains the configuration for qemu.
+//!
+//! The file is partitially taken from asterinas osdk, and is Licensed under both GPL-2.0 and MPL-2.0.
+//! (GPL-2.0 is compatible with MPL-2.0)
+//! https://www.gnu.org/licenses/license-list.zh-cn.html#MPL-2.0
+
+use anyhow::{anyhow, Result};
+use serde::Deserialize;
+
+use crate::{
+    common::target_arch::TargetArch,
+    utils::{apply_kv_array, get_key, split_to_kv_array},
+};
+
+#[derive(Debug, Clone, Deserialize, Default)]
+pub struct QemuConfig {
+    /// Path prefix for qemu binary.
+    ///
+    /// If not set, the default path will be used.
+    ///
+    /// Example:
+    /// Fill in `/usr/bin/qemu-system-`,
+    /// then for the `x86_64` architecture, `/usr/bin/qemu-system-x86_64` will be used.
+    #[serde(rename = "path-prefix")]
+    path_prefix: Option<String>,
+    /// Arguments to pass to qemu.
+    args: String,
+
+    /// Parameters to apply when no-graphic is enabled
+    #[serde(rename = "no-graphic-args")]
+    pub no_graphic_args: String,
+
+    /// Hardware acceleration
+    accelerate: Option<QemuAccel>,
+}
+
+impl QemuConfig {
+    /// Get the path to the qemu binary
+    pub fn path(&self, arch: TargetArch) -> String {
+        let arch_name: &str = arch.into();
+        if let Some(prefix) = &self.path_prefix {
+            format!("{}{}", prefix, arch_name)
+        } else {
+            format!("qemu-system-{}", arch_name)
+        }
+    }
+
+    /// Apply the arguments to the qemu configuration
+    pub fn apply_qemu_args(&mut self, args: &Vec<String>) -> Result<()> {
+        let mut joined =
+            split_to_kv_array(&self.args).map_err(|e| anyhow!("apply_qemu_args: {:?}", e))?;
+
+        // Check the soundness of qemu arguments
+        for arg in joined.iter() {
+            check_qemu_arg(arg).map_err(|e| anyhow!("apply_qemu_args: {:?}", e))?;
+        }
+        log::warn!("apply_qemu_args: joined: {:?}", joined);
+
+        apply_kv_array(&mut joined, args, " ", MULTI_VALUE_KEYS, SINGLE_VALUE_KEYS)?;
+
+        self.args = joined.join(" ");
+        Ok(())
+    }
+
+    /// Get the arguments to pass to qemu
+    pub fn args(&self) -> String {
+        self.args.clone()
+    }
+
+    /// Get the hardware acceleration configuration
+    pub fn accelerate(&self) -> QemuAccel {
+        self.accelerate.clone().unwrap_or(QemuAccel::None)
+    }
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
+pub enum QemuAccel {
+    #[serde(rename = "none")]
+    None,
+    #[serde(rename = "kvm")]
+    Kvm,
+    #[serde(rename = "hvf")]
+    Hvf,
+    #[serde(rename = "tcg")]
+    Tcg,
+}
+
+// Below are checked keys in qemu arguments. The key list is non-exhaustive.
+
+/// Keys with multiple values
+const MULTI_VALUE_KEYS: &[&str] = &[
+    "-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
+];
+/// Keys with only single value
+const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
+/// Keys with no value
+const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
+/// Keys are not allowed to set in configuration files and command line
+const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-append", "-initrd"];
+
+fn check_qemu_arg(arg: &str) -> Result<()> {
+    let key = if let Some(key) = get_key(arg, " ") {
+        key
+    } else {
+        arg.to_string()
+    };
+
+    if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) {
+        return Err(anyhow!("`{}` is not allowed to set", arg));
+    }
+
+    if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg {
+        return Err(anyhow!("`{}` cannot have value", arg));
+    }
+
+    if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str()))
+        && key.as_str() == arg
+    {
+        return Err(anyhow!("`{}` must have value", arg));
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_qemu_config_path() {
+        let config = QemuConfig {
+            path_prefix: Some("/usr/bin/qemu-system-".to_string()),
+            args: "".to_string(),
+            ..Default::default()
+        };
+
+        assert_eq!(
+            config.path(TargetArch::X86_64),
+            "/usr/bin/qemu-system-x86_64"
+        );
+        assert_eq!(
+            config.path(TargetArch::RiscV64),
+            "/usr/bin/qemu-system-riscv64"
+        );
+    }
+
+    #[test]
+    fn test_qemu_config_path_default() {
+        let config = QemuConfig {
+            path_prefix: None,
+            args: "".to_string(),
+            ..Default::default()
+        };
+
+        assert_eq!(config.path(TargetArch::X86_64), "qemu-system-x86_64");
+        assert_eq!(config.path(TargetArch::RiscV64), "qemu-system-riscv64");
+    }
+
+    #[test]
+    fn test_apply_qemu_args() -> Result<()> {
+        let mut config = QemuConfig {
+            path_prefix: None,
+            args: "-m 1G -nographic".to_string(),
+            ..Default::default()
+        };
+
+        let args = vec!["-m 2G".to_string(), "-enable-kvm".to_string()];
+        config.apply_qemu_args(&args)?;
+
+        assert_eq!(config.args, "-m 2G -nographic -enable-kvm");
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_qemu_args_invalid() {
+        // 不允许直接设置 -kernel
+        let mut config = QemuConfig {
+            path_prefix: None,
+            args: "-kernel path/to/kernel".to_string(),
+            ..Default::default()
+        };
+
+        let args = vec!["".to_string()];
+        let result = config.apply_qemu_args(&args);
+
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn test_check_qemu_arg_valid() -> Result<()> {
+        assert!(check_qemu_arg("-m 1G").is_ok());
+        assert!(check_qemu_arg("-nographic").is_ok());
+        Ok(())
+    }
+
+    #[test]
+    fn test_check_qemu_arg_invalid() {
+        assert!(check_qemu_arg("-kernel path/to/kernel").is_err());
+        assert!(check_qemu_arg("-m").is_err());
+        assert!(check_qemu_arg("-nographic value").is_err());
+    }
+
+    #[test]
+    fn test_apply_qemu_args_multi_value_keys() -> Result<()> {
+        let mut config = QemuConfig {
+            path_prefix: None,
+            args: "-device virtio-net-pci,netdev=net0 -netdev user,id=net0".to_string(),
+            ..Default::default()
+        };
+
+        let args = vec![
+            "-device virtio-net-pci,netdev=net1".to_string(),
+            "-netdev user,id=net1".to_string(),
+        ];
+        config.apply_qemu_args(&args)?;
+
+        assert_eq!(
+            config.args,
+            "-device virtio-net-pci,netdev=net0 -device virtio-net-pci,netdev=net1 -netdev user,id=net0 -netdev user,id=net1"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_qemu_args_multi_value_keys_invalid() {
+        let mut config = QemuConfig {
+            path_prefix: None,
+            args: "-device virtio-net-pci,netdev=net0".to_string(),
+            ..Default::default()
+        };
+
+        let args = vec!["-device".to_string()];
+        let result = config.apply_qemu_args(&args);
+
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn test_check_qemu_arg_multi_value_keys_valid() -> Result<()> {
+        assert!(check_qemu_arg("-device virtio-net-pci,netdev=net0").is_ok());
+        assert!(check_qemu_arg("-chardev socket,id=chr0,path=/tmp/qemu.sock").is_ok());
+        Ok(())
+    }
+
+    #[test]
+    fn test_check_qemu_arg_multi_value_keys_invalid() {
+        assert!(check_qemu_arg("-device").is_err());
+        assert!(check_qemu_arg("-chardev").is_err());
+    }
+
+    #[test]
+    fn test_qemu_config_args() {
+        let mut config = QemuConfig {
+            path_prefix: None,
+            args: "-m 1G -nographic".to_string(),
+            ..Default::default()
+        };
+
+        config.apply_qemu_args(&vec![]).unwrap();
+        assert_eq!(config.args(), "-m 1G -nographic");
+    }
+
+    #[test]
+    fn test_qemu_accelerate_args() {
+        let s = r#""kvm""#;
+        let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
+        match result {
+            Ok(QemuAccel::Kvm) => assert!(true),
+            _ => assert!(false, "Expected Ok(QemuAccel::Kvm) but got {:?}", result),
+        }
+
+        let s = r#""tcg""#;
+        let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
+        match result {
+            Ok(QemuAccel::Tcg) => assert!(true),
+            _ => assert!(false, "Expected Ok(QemuAccel::Tcg) but got {:?}", result),
+        }
+
+        let s = r#""none""#;
+        let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
+        match result {
+            Ok(QemuAccel::None) => assert!(true),
+            _ => assert!(false, "Expected Ok(QemuAccel::None) but got {:?}", result),
+        }
+
+        let s = r#""hvf""#;
+        let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
+        match result {
+            Ok(QemuAccel::Hvf) => assert!(true),
+            _ => assert!(false, "Expected Ok(QemuAccel::Hvf) but got {:?}", result),
+        }
+
+        let s = r#""invalid""#;
+        let result: Result<QemuAccel, serde_json::Error> = serde_json::from_str(s);
+        assert!(result.is_err(), "Expected Err but got {:?}", result);
+    }
+}

+ 186 - 0
dadk-config/src/boot/metadata.rs

@@ -0,0 +1,186 @@
+use serde::Deserialize;
+
+use super::hypervisor::hyp_type::HypervisorType;
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct BootMetadata {
+    /// The boot protocol used during startup
+    #[serde(rename = "boot-protocol")]
+    pub boot_protocol: BootProtocol,
+    /// The mode of booting
+    #[serde(rename = "boot-mode")]
+    pub boot_mode: BootMode,
+    /// The hypervisor used during startup
+    pub hypervisor: HypervisorType,
+
+    /// Kernel command-line arguments
+    #[serde(rename = "kcmd-args", default = "default_empty_vec")]
+    pub kcmd_args: Vec<String>,
+    /// Arguments passed to the init process
+    #[serde(rename = "init-args", default = "default_empty_vec")]
+    pub init_args: Vec<String>,
+}
+
+fn default_empty_vec() -> Vec<String> {
+    vec![]
+}
+
+#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
+pub enum BootProtocol {
+    /// BIOS Bootloader
+    #[serde(rename = "grub-legacy")]
+    GrubLegacy,
+    /// UEFI Bootloader (Grub)
+    #[serde(rename = "grub-efi")]
+    GrubEFI,
+    /// Direct Linux Boot (with `-kernel` options)
+    #[serde(rename = "direct")]
+    Direct,
+    /// Dragon Stub Bootloader (riscv only)
+    #[serde(rename = "dragon-stub")]
+    DragonStub,
+}
+
+/// The mode of booting
+#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
+pub enum BootMode {
+    /// Graphic mode
+    #[serde(rename = "graphic")]
+    Graphic,
+    /// Graphic mode with VNC
+    #[serde(rename = "graphic-vnc")]
+    GraphicVnc,
+    /// No graphic mode
+    #[serde(rename = "no-graphic")]
+    NoGraphic,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // Helper function to parse TOML string into BootMetadata
+    fn parse_boot_metadata(toml_str: &str) -> Result<BootMetadata, toml::de::Error> {
+        toml::from_str(toml_str)
+    }
+
+    fn assert_missing_field(err_str: &str, field: &str) {
+        assert!(err_str.contains(&format!("missing field `{field}`")));
+    }
+
+    fn assert_unknown_variant(err_str: &str, variant: &str) {
+        assert!(err_str.contains(&format!("unknown variant `{variant}`")));
+    }
+
+    #[test]
+    fn test_parse_grub_legacy_graphic() {
+        let toml_str = r#"
+        boot-protocol = "grub-legacy"
+        boot-mode = "graphic"
+        hypervisor = "qemu"
+        "#;
+
+        let result = parse_boot_metadata(toml_str).unwrap();
+        assert_eq!(result.boot_protocol, BootProtocol::GrubLegacy);
+        assert_eq!(result.boot_mode, BootMode::Graphic);
+    }
+
+    #[test]
+    fn test_parse_grub_efi_graphic_vnc() {
+        let toml_str = r#"
+        boot-protocol = "grub-efi"
+        boot-mode = "graphic-vnc"
+        hypervisor = "qemu"
+        "#;
+
+        let result = parse_boot_metadata(toml_str).unwrap();
+        assert_eq!(result.boot_protocol, BootProtocol::GrubEFI);
+        assert_eq!(result.boot_mode, BootMode::GraphicVnc);
+    }
+
+    #[test]
+    fn test_parse_direct_no_graphic() {
+        let toml_str = r#"
+        boot-protocol = "direct"
+        boot-mode = "no-graphic"
+        hypervisor = "qemu"
+        "#;
+
+        let result = parse_boot_metadata(toml_str).unwrap();
+        assert_eq!(result.boot_protocol, BootProtocol::Direct);
+        assert_eq!(result.boot_mode, BootMode::NoGraphic);
+    }
+
+    #[test]
+    fn test_parse_dragon_stub_graphic() {
+        let toml_str = r#"
+        boot-protocol = "dragon-stub"
+        boot-mode = "graphic"
+        hypervisor = "qemu"
+        "#;
+
+        let result = parse_boot_metadata(toml_str).unwrap();
+        assert_eq!(result.boot_protocol, BootProtocol::DragonStub);
+        assert_eq!(result.boot_mode, BootMode::Graphic);
+    }
+
+    #[test]
+    fn test_parse_missing_boot_protocol() {
+        let toml_str = r#"
+        boot-mode = "graphic"
+        "#;
+
+        let r = parse_boot_metadata(toml_str);
+        assert!(r.is_err());
+        let r = r.unwrap_err();
+        assert_missing_field(&r.to_string(), "boot-protocol");
+    }
+
+    #[test]
+    fn test_parse_missing_boot_mode() {
+        let toml_str = r#"
+        boot-protocol = "grub-legacy"
+        hypervisor = "qemu"
+        "#;
+
+        let r = parse_boot_metadata(toml_str);
+        assert!(r.is_err());
+        let r = r.unwrap_err();
+        assert_missing_field(&r.to_string(), "boot-mode");
+    }
+
+    #[test]
+    fn test_parse_invalid_boot_protocol() {
+        let toml_str = r#"
+        boot-protocol = "invalid-protocol"
+        boot-mode = "graphic"
+        "#;
+        let r = parse_boot_metadata(toml_str);
+        assert!(r.is_err());
+        let r = r.unwrap_err();
+        assert_unknown_variant(&r.to_string(), "invalid-protocol");
+    }
+
+    #[test]
+    fn test_parse_invalid_boot_mode() {
+        let toml_str = r#"
+        boot-protocol = "grub-legacy"
+        boot-mode = "invalid-mode"
+        "#;
+
+        let r = parse_boot_metadata(toml_str);
+        assert!(r.is_err());
+        let r = r.unwrap_err();
+        assert_unknown_variant(&r.to_string(), "invalid-mode");
+    }
+
+    #[test]
+    fn test_parse_empty_fields() {
+        let toml_str = r#"
+        boot-protocol = ""
+        boot-mode = ""
+        "#;
+
+        assert!(parse_boot_metadata(toml_str).is_err());
+    }
+}

+ 47 - 0
dadk-config/src/boot/mod.rs

@@ -0,0 +1,47 @@
+use std::{fs, path::PathBuf};
+
+use anyhow::Result;
+use dragonstub::DragonStubConfig;
+use grub::GrubConfig;
+use hypervisor::qemu::QemuConfig;
+use metadata::BootMetadata;
+use serde::Deserialize;
+use uboot::UbootConfig;
+
+pub mod dragonstub;
+pub mod grub;
+pub mod hypervisor;
+pub mod metadata;
+pub mod uboot;
+
+/// Boot configuration file
+#[derive(Debug, Clone, Deserialize)]
+pub struct BootConfigFile {
+    /// Boot metadata
+    pub metadata: BootMetadata,
+
+    /// GRUB configuration
+    pub grub: Option<GrubConfig>,
+    /// DragonStub configuration
+    pub dragonstub: Option<DragonStubConfig>,
+
+    /// U-Boot configuration
+    pub uboot: Option<UbootConfig>,
+
+    /// QEMU configuration
+    pub qemu: Option<QemuConfig>,
+}
+
+impl BootConfigFile {
+    pub fn load(path: &PathBuf) -> Result<Self> {
+        // 读取文件内容
+        let content = fs::read_to_string(path)?;
+        Self::load_from_str(&content)
+    }
+
+    pub fn load_from_str(content: &str) -> Result<Self> {
+        let config: BootConfigFile = toml::from_str(content)?;
+
+        Ok(config)
+    }
+}

+ 99 - 0
dadk-config/src/boot/uboot.rs

@@ -0,0 +1,99 @@
+use serde::Deserialize;
+
+use crate::common::target_arch::TargetArch;
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct UbootConfig {
+    /// URL to download U-Boot binary file
+    ///
+    /// If the URL is `https://mirrors.dragonos.org.cn/pub/third_party/u-boot`,
+    /// then the final download URL will be `https://mirrors.dragonos.org.cn/pub/third_party/u-boot/u-boot-{version}-{arch}.tar.xz`
+    #[serde(rename = "download-url", default = "default_download_url")]
+    pub download_url: String,
+
+    /// Version of U-Boot
+    #[serde(rename = "version", default = "default_version")]
+    pub version: String,
+
+    /// Prefix directory for U-Boot binary file
+    ///
+    /// Example:
+    /// If the current architecture is `riscv64` and the version is `v2023.10`,
+    /// `path_prefix` is `bin/uboot/`,
+    /// then the path to locate the U-Boot binary file would be: `bin/uboot/riscv64/v2023.10/uboot.bin`
+    #[serde(rename = "path-prefix", default = "default_path_prefix")]
+    pub path_prefix: String,
+}
+
+impl Default for UbootConfig {
+    fn default() -> Self {
+        Self {
+            download_url: Self::DEFAULT_DOWNLOAD_URL.to_string(),
+            version: Self::DEFAULT_VERSION.to_string(),
+            path_prefix: Self::DEFAULT_PATH_PREFIX.to_string(),
+        }
+    }
+}
+
+impl UbootConfig {
+    const DEFAULT_DOWNLOAD_URL: &'static str =
+        "https://mirrors.dragonos.org.cn/pub/third_party/u-boot";
+
+    const DEFAULT_VERSION: &'static str = "v2023.10";
+
+    const DEFAULT_PATH_PREFIX: &'static str = "bin/uboot/";
+    /// Get the full download URL for the U-Boot binary file archive
+    pub fn full_download_url(&self, target_arch: TargetArch) -> String {
+        let arch_str: &str = target_arch.into();
+        format!(
+            "{}/u-boot-{}-{}.tar.xz",
+            self.download_url, self.version, arch_str
+        )
+    }
+}
+
+fn default_download_url() -> String {
+    UbootConfig::DEFAULT_DOWNLOAD_URL.to_string()
+}
+
+fn default_version() -> String {
+    UbootConfig::DEFAULT_VERSION.to_string()
+}
+
+fn default_path_prefix() -> String {
+    UbootConfig::DEFAULT_PATH_PREFIX.to_string()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_default_uboot_config() {
+        let config = UbootConfig::default();
+        assert_eq!(config.download_url, UbootConfig::DEFAULT_DOWNLOAD_URL);
+        assert_eq!(config.version, "v2023.10");
+        assert_eq!(config.path_prefix, "bin/uboot/");
+    }
+
+    #[test]
+    fn test_full_download_url_riscv64() {
+        let config = UbootConfig::default();
+        let url = config.full_download_url(TargetArch::RiscV64);
+        assert_eq!(
+            url,
+            "https://mirrors.dragonos.org.cn/pub/third_party/u-boot/u-boot-v2023.10-riscv64.tar.xz"
+        );
+    }
+
+    #[test]
+    fn test_empty_toml_deserialization() {
+        let toml_content = "";
+        let config: UbootConfig = toml::from_str(toml_content).unwrap();
+
+        // Verify that the default values are set
+        assert_eq!(config.download_url, UbootConfig::DEFAULT_DOWNLOAD_URL);
+        assert_eq!(config.version, "v2023.10");
+        assert_eq!(config.path_prefix, "bin/uboot/");
+    }
+}

+ 3 - 2
dadk-config/src/lib.rs

@@ -1,8 +1,9 @@
-#[deny(clippy::all)]
+#![deny(clippy::all)]
+pub mod boot;
 pub mod common;
-pub mod hypervisor;
 pub mod manifest;
 pub mod rootfs;
 pub mod user;
+mod utils;
 
 extern crate anyhow;

+ 12 - 0
dadk-config/src/manifest.rs

@@ -69,6 +69,10 @@ pub struct Metadata {
     /// Boot configuration file path
     #[serde(default = "default_boot_config_path")]
     pub boot_config: PathBuf,
+
+    /// Sysroot directory path
+    #[serde(default = "default_sysroot_dir")]
+    pub sysroot_dir: PathBuf,
 }
 
 /// Returns the default path for the rootfs configuration file.
@@ -89,6 +93,12 @@ fn default_boot_config_path() -> PathBuf {
     "config/boot.toml".into()
 }
 
+/// Returns the default path for the sysroot directory.
+fn default_sysroot_dir() -> PathBuf {
+    set_used_default();
+    "bin/sysroot".into()
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -104,6 +114,7 @@ mod tests {
             rootfs_config = "config/rootfs-x86_64.toml"
             hypervisor_config = "config/hypervisor-x86_64.toml"
             boot_config = "config/boot-x86_64.toml"
+            sysroot_dir = "bin/sysroot"
         "#;
 
         let mut temp_file = NamedTempFile::new()?;
@@ -125,6 +136,7 @@ mod tests {
             manifest.metadata.boot_config,
             PathBuf::from("config/boot-x86_64.toml")
         );
+        assert_eq!(manifest.metadata.sysroot_dir, PathBuf::from("bin/sysroot"));
         assert!(!manifest.used_default);
 
         Ok(())

+ 1 - 0
dadk-config/src/rootfs/fstype.rs

@@ -1,5 +1,6 @@
 use serde::{Deserialize, Deserializer};
 
+/// Possible filesystem types for rootfs
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum FsType {
     Fat32,

+ 226 - 0
dadk-config/src/utils.rs

@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: MPL-2.0
+// Note: This file is derived from the asterinas osdk,
+// and we extend our heartfelt thanks to the developers of Asterinas!
+
+//! This module contains utilities for manipulating common Unix command-line arguments.
+
+use anyhow::{anyhow, Result};
+use indexmap::{IndexMap, IndexSet};
+
+/// Split a string of Unix arguments into an array of key-value strings or switches.
+/// Positional arguments are not supported.
+pub fn split_to_kv_array(args: &str) -> Result<Vec<String>> {
+    let target = shlex::split(args).ok_or(anyhow!("Failed to split unix arguments"))?;
+
+    // Join the key value arguments as a single element
+    let mut joined = Vec::<String>::new();
+    let mut last_has_value = false;
+    for elem in target {
+        if !elem.starts_with('-') && !last_has_value {
+            if let Some(last) = joined.last_mut() {
+                last.push(' ');
+                last.push_str(&elem);
+                last_has_value = true;
+                continue;
+            }
+        }
+        joined.push(elem);
+        last_has_value = false;
+    }
+
+    Ok(joined)
+}
+
+/// Apply key-value pairs to an array of strings.
+///
+/// The provided arguments will be appended to the array if the key is not already present or if the key is a multi-value key.
+/// Otherwise, the value will be updated.
+pub fn apply_kv_array(
+    array: &mut Vec<String>,
+    args: &Vec<String>,
+    separator: &str,
+    multi_value_keys: &[&str],
+    single_value_keys: &[&str],
+) -> Result<()> {
+    let multi_value_keys = {
+        let mut inferred_keys = infer_multi_value_keys(array, separator);
+        for key in multi_value_keys {
+            inferred_keys.insert(key.to_string());
+        }
+        inferred_keys
+    };
+
+    log::debug!("apply_kv_array: multi value keys: {:?}", multi_value_keys);
+
+    // We use IndexMap to keep key orders
+    let mut key_strings = IndexMap::new();
+    let mut multi_value_key_strings: IndexMap<String, Vec<String>> = IndexMap::new();
+    for item in array.drain(..) {
+        // Each key-value string has two patterns:
+        // 1. Separated by separator: key value / key=value
+        if let Some(key) = get_key(&item, separator) {
+            if multi_value_keys.contains(&key) {
+                if let Some(v) = multi_value_key_strings.get_mut(&key) {
+                    v.push(item);
+                } else {
+                    let v = vec![item];
+                    multi_value_key_strings.insert(key, v);
+                }
+                continue;
+            }
+
+            key_strings.insert(key, item);
+            continue;
+        }
+        // 2. Only key, no value
+        key_strings.insert(item.clone(), item);
+    }
+
+    for arg in args {
+        if let Some(key) = get_key(arg, separator) {
+            if multi_value_keys.contains(&key) {
+                if let Some(v) = multi_value_key_strings.get_mut(&key) {
+                    v.push(arg.to_owned());
+                } else {
+                    let v = vec![arg.to_owned()];
+                    multi_value_key_strings.insert(key, v);
+                }
+                continue;
+            }
+
+            key_strings.insert(key, arg.to_owned());
+            continue;
+        } else {
+            if single_value_keys.contains(&arg.as_str()) || multi_value_keys.contains(arg) {
+                return Err(anyhow!("Invalid argument: {}", arg));
+            } else {
+                key_strings.insert(arg.to_owned(), arg.to_owned());
+            }
+        }
+    }
+
+    *array = key_strings.into_iter().map(|(_, value)| value).collect();
+
+    for (_, mut values) in multi_value_key_strings {
+        array.append(&mut values);
+    }
+    Ok(())
+}
+
+fn infer_multi_value_keys(array: &Vec<String>, separator: &str) -> IndexSet<String> {
+    let mut multi_val_keys = IndexSet::new();
+
+    let mut occurred_keys = IndexSet::new();
+    for item in array {
+        let Some(key) = get_key(item, separator) else {
+            continue;
+        };
+
+        if occurred_keys.contains(&key) {
+            multi_val_keys.insert(key);
+        } else {
+            occurred_keys.insert(key);
+        }
+    }
+
+    multi_val_keys
+}
+
+pub fn get_key(item: &str, separator: &str) -> Option<String> {
+    let split = item.split(separator).collect::<Vec<_>>();
+    let len = split.len();
+    if len > 2 || len == 0 {
+        log::error!("{} is an invalid argument.", item);
+        return None;
+    }
+
+    if len == 1 {
+        return None;
+    }
+
+    let key = split.first().unwrap();
+
+    Some(key.to_string())
+}
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+
+    #[test]
+    fn test_get_key() {
+        let string1 = "init=/bin/init";
+        let key = get_key(string1, "=").unwrap();
+        assert_eq!(key.as_str(), "init");
+
+        let string2 = "-m 8G";
+        let key = get_key(string2, " ").unwrap();
+        assert_eq!(key.as_str(), "-m");
+
+        let string3 = "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off";
+        let key = get_key(string3, " ").unwrap();
+        assert_eq!(key.as_str(), "-device");
+
+        let string4 = "-device";
+        assert!(get_key(string4, " ").is_none());
+
+        let string5 = "-m 8G a";
+        assert!(get_key(string5, " ").is_none());
+        let string6 = "-m=8G";
+        assert!(get_key(string6, " ").is_none());
+    }
+
+    #[test]
+    fn test_apply_kv_array() {
+        let qemu_args = &[
+            "-enable-kvm",
+            "-m 8G",
+            "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
+            "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
+        ];
+
+        let args = &["-m 100G", "-device ioh3420,id=pcie.0,chassis=1"];
+
+        let expected = &[
+            "-enable-kvm",
+            "-m 100G",
+            "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
+            "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
+            "-device ioh3420,id=pcie.0,chassis=1",
+        ];
+
+        let mut array = qemu_args.iter().map(ToString::to_string).collect();
+        let args = args.iter().map(ToString::to_string).collect();
+        assert!(apply_kv_array(&mut array, &args, " ", &["-device"], &[]).is_ok());
+
+        let expected: Vec<_> = expected.iter().map(ToString::to_string).collect();
+        assert_eq!(expected, array);
+    }
+
+    #[test]
+    fn test_apply_kv_array_insert_multi_value_key() {
+        let mut array = vec!["key1=value1".to_string(), "key2=value2".to_string()];
+        let args = vec!["key3=value3".to_string()];
+        let separator = "=";
+        let multi_value_keys = vec!["key3"];
+        let single_value_keys: Vec<&str> = vec![];
+
+        let result = apply_kv_array(
+            &mut array,
+            &args,
+            separator,
+            &multi_value_keys,
+            &single_value_keys,
+        );
+
+        assert!(result.is_ok());
+        assert_eq!(
+            array,
+            vec![
+                "key1=value1".to_string(),
+                "key2=value2".to_string(),
+                "key3=value3".to_string(),
+            ]
+        );
+    }
+}

+ 90 - 0
dadk-config/templates/config/boot.toml

@@ -0,0 +1,90 @@
+[metadata]
+
+# Optional values: "grub-legacy", "grub-efi", "direct", "dragon-stub"
+# Note: "dragon-stub" can only be used with the riscv64 architecture
+boot-protocol = "grub-legacy"
+
+# Optional values: "graphic", "graphic-vnc", "no-graphic"
+boot-mode = "graphic"
+
+# Optional values: "qemu"
+hypervisor = "qemu"
+
+# Kernel command line arguments
+kcmd-args = [
+    "console=ttyS0",
+    "root=/dev/vda"
+]
+
+# Arguments passed to the init process
+init-args = []
+
+
+# (Optional) Grub2 configuration 
+[grub]
+# Time in seconds before the default entry is booted
+timeout = 10
+
+# (Optional) Grub2 i386-legacy configuration 
+[grub.i386-legacy]
+grub-file = "/opt/dragonos-grub/arch/i386/legacy/grub/bin/grub-file"
+grub-install = "/opt/dragonos-grub/arch/i386/legacy/grub/sbin/grub-install"
+
+# (Optional) Grub2 i386-efi configuration
+[grub.i386-efi]
+grub-file = "/opt/dragonos-grub/arch/i386/efi/grub/bin/grub-file"
+grub-install = "/opt/dragonos-grub/arch/i386/efi/grub/sbin/grub-install"
+
+# (Optional) Grub2 x86_64-efi configuration
+[grub.x86_64-efi]
+grub-file = "/opt/dragonos-grub/arch/x86_64/efi/grub/bin/grub-file"
+grub-install = "/opt/dragonos-grub/arch/x86_64/efi/grub/sbin/grub-install"
+
+
+# (Optional) DragonStub Bootloader configuration (Only for riscv64)
+[dragonstub]
+# The path to the source code of the DragonStub project.
+src-path = "kernel/submodules/DragonStub"
+
+
+[uboot]
+# URL to download U-Boot binary file
+#
+# If the URL is `https://mirrors.dragonos.org.cn/pub/third_party/u-boot`,
+# then the final download URL will be `https://mirrors.dragonos.org.cn/pub/third_party/u-boot/u-boot-{version}-{arch}.tar.xz`
+download-url = "https://mirrors.dragonos.org.cn/pub/third_party/u-boot"
+
+# U-Boot version
+version = "v2023.10"
+
+# Prefix directory for U-Boot binary file
+#
+# Example:
+# If the current architecture is `riscv64` and the version is `v2023.10`,
+# `path_prefix` is `bin/uboot/`,
+# then the path to locate the U-Boot binary file would be: `bin/uboot/riscv64/v2023.10/uboot.bin`
+path-prefix = "bin/uboot"
+
+
+[qemu]
+
+# (Optional) Path prefix for qemu binary.
+#
+# If not set, the default path will be used.
+#
+# Example:
+# Fill in `/usr/bin/qemu-system-`,
+# then for the `x86_64` architecture, `/usr/bin/qemu-system-x86_64` will be used.
+path-prefix = "qemu-system-"
+
+# Arguments to pass to qemu.
+args = """\
+    -machine virt \
+"""
+
+# Parameters to apply when no-graphic is enabled
+no-graphic-args = ""
+
+# (Optional) Hardware acceleration
+# 可选值:"kvm", "tcg", "hvf", "none". 不填写时,默认为none
+accelerate = "kvm"

+ 3 - 0
dadk-config/templates/dadk-manifest.toml

@@ -13,3 +13,6 @@ rootfs_config = "config/rootfs.toml"
 
 # Boot config path
 boot_config = "config/boot.toml"
+
+# System root directory folder (DADK will copy the files in this directory to the root directory of the disk image)
+sysroot_dir = "bin/sysroot"

+ 18 - 0
dadk-config/tests/test_boot_config.rs

@@ -0,0 +1,18 @@
+use dadk_config::{self, boot::BootConfigFile};
+use test_base::{
+    dadk_config::DadkConfigTestContext,
+    test_context::{self as test_context, test_context},
+};
+
+const BOOT_CONFIG_FILE_NAME: &str = "config/boot.toml";
+
+/// 测试加载模板目录中的 boot.toml 文件,验证它能被加载成功.
+#[test_context(DadkConfigTestContext)]
+#[test]
+fn test_load_boot_config_template(ctx: &DadkConfigTestContext) {
+    let boot_config_path = ctx.templates_dir().join(BOOT_CONFIG_FILE_NAME);
+    assert_eq!(boot_config_path.exists(), true);
+    assert_eq!(boot_config_path.is_file(), true);
+    let _manifest = BootConfigFile::load(&boot_config_path).expect("Failed to load boot config");
+    // TODO 校验 manifest 中的字段是否齐全
+}

+ 1 - 0
dadk-config/tests/test_dadk_manifest.rs

@@ -14,5 +14,6 @@ fn test_load_dadk_manifest_template(ctx: &DadkConfigTestContext) {
     assert_eq!(manifest_path.exists(), true);
     assert_eq!(manifest_path.is_file(), true);
     let manifest = DadkManifestFile::load(&manifest_path).expect("Failed to load manifest");
+    // 验证 dadk-manifest.toml 已经包含了所有字段
     assert_eq!(manifest.used_default, false);
 }

+ 2 - 2
dadk-config/tests/test_rootfs_config.rs

@@ -4,13 +4,13 @@ use test_base::{
     test_context::{self as test_context, test_context},
 };
 
-const ROOTFS_MANIFEST_FILE_NAME: &str = "config/rootfs.toml";
+const ROOTFS_CONFIG_FILE_NAME: &str = "config/rootfs.toml";
 
 /// 测试加载模板目录中的 rootfs.toml 文件,验证它能被加载成功,并且已经包含了所有字段
 #[test_context(DadkConfigTestContext)]
 #[test]
 fn test_load_rootfs_manifest_template(ctx: &DadkConfigTestContext) {
-    let rootfs_manifest_path = ctx.templates_dir().join(ROOTFS_MANIFEST_FILE_NAME);
+    let rootfs_manifest_path = ctx.templates_dir().join(ROOTFS_CONFIG_FILE_NAME);
     assert_eq!(rootfs_manifest_path.exists(), true);
     assert_eq!(rootfs_manifest_path.is_file(), true);
     let _manifest =