use std::fs::File;
use std::io::Read;
use std::path::Path;
use crate::error::{Result, VmmError};
use arcbox_hypervisor::GuestAddress;
pub mod linux {
pub const BZIMAGE_MAGIC: u32 = 0x53726448;
pub const MIN_BOOT_PROTOCOL: u16 = 0x0200;
pub const BOOT_PROTOCOL_64BIT: u16 = 0x0206;
pub const SETUP_HEADER_OFFSET: u64 = 0x1F1;
pub const BOOT_FLAG: u16 = 0xAA55;
pub const KERNEL_LOAD_ADDR: u64 = 0x100000;
pub const INITRD_LOAD_ADDR: u64 = 0x1000000;
pub const CMDLINE_ADDR: u64 = 0x20000;
pub const CMDLINE_MAX_SIZE: usize = 2048;
pub const BOOT_PARAMS_ADDR: u64 = 0x7000;
pub mod e820 {
pub const RAM: u32 = 1;
pub const RESERVED: u32 = 2;
pub const ACPI: u32 = 3;
pub const NVS: u32 = 4;
pub const UNUSABLE: u32 = 5;
}
}
pub mod arm64 {
pub const KERNEL_LOAD_ADDR: u64 = 0x80000;
pub const FDT_LOAD_ADDR: u64 = 0x40000000;
pub const INITRD_LOAD_ADDR: u64 = 0x48000000;
pub const FDT_MAX_SIZE: usize = 2 * 1024 * 1024; }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KernelType {
LinuxBzImage,
LinuxArm64,
RawBinary,
Unknown,
}
#[derive(Debug, Clone)]
pub struct BootParams {
pub kernel_path: String,
pub kernel_type: KernelType,
pub kernel_addr: GuestAddress,
pub kernel_size: u64,
pub entry_point: GuestAddress,
pub cmdline: String,
pub cmdline_addr: GuestAddress,
pub initrd_path: Option<String>,
pub initrd_addr: Option<GuestAddress>,
pub initrd_size: Option<u64>,
pub fdt_addr: Option<GuestAddress>,
pub fdt_size: Option<u64>,
}
impl BootParams {
#[must_use]
pub fn new(kernel_path: impl Into<String>, cmdline: impl Into<String>) -> Self {
Self {
kernel_path: kernel_path.into(),
kernel_type: KernelType::Unknown,
kernel_addr: GuestAddress::new(0),
kernel_size: 0,
entry_point: GuestAddress::new(0),
cmdline: cmdline.into(),
cmdline_addr: GuestAddress::new(0),
initrd_path: None,
initrd_addr: None,
initrd_size: None,
fdt_addr: None,
fdt_size: None,
}
}
#[must_use]
pub fn with_initrd(mut self, path: impl Into<String>) -> Self {
self.initrd_path = Some(path.into());
self
}
}
pub struct KernelLoader;
impl KernelLoader {
pub fn detect_kernel_type(path: &Path) -> Result<KernelType> {
let mut file =
File::open(path).map_err(|e| VmmError::config(format!("Cannot open kernel: {e}")))?;
let mut header = [0u8; 0x210];
let bytes_read = file
.read(&mut header)
.map_err(|e| VmmError::config(format!("Cannot read kernel header: {e}")))?;
if bytes_read >= 0x206 {
let magic =
u32::from_le_bytes([header[0x202], header[0x203], header[0x204], header[0x205]]);
if magic == linux::BZIMAGE_MAGIC {
return Ok(KernelType::LinuxBzImage);
}
}
if bytes_read >= 0x40 && &header[0x38..0x3C] == b"ARM\x64" {
return Ok(KernelType::LinuxArm64);
}
if bytes_read >= 4 && &header[0..4] == b"\x7FELF" {
return Ok(KernelType::RawBinary);
}
if bytes_read >= 2 && &header[0..2] == b"MZ" {
return Ok(KernelType::RawBinary);
}
Ok(KernelType::Unknown)
}
pub fn load(path: &Path, cmdline: &str) -> Result<BootParams> {
let kernel_type = Self::detect_kernel_type(path)?;
tracing::info!(
"Loading kernel: path={}, type={:?}",
path.display(),
kernel_type
);
let file_size = std::fs::metadata(path)
.map_err(|e| VmmError::config(format!("Cannot stat kernel: {e}")))?
.len();
let mut params = BootParams::new(path.to_string_lossy(), cmdline);
params.kernel_type = kernel_type;
params.kernel_size = file_size;
match kernel_type {
KernelType::LinuxBzImage => {
params.kernel_addr = GuestAddress::new(linux::KERNEL_LOAD_ADDR);
params.entry_point = GuestAddress::new(linux::KERNEL_LOAD_ADDR);
params.cmdline_addr = GuestAddress::new(linux::CMDLINE_ADDR);
}
KernelType::LinuxArm64 => {
params.kernel_addr = GuestAddress::new(arm64::KERNEL_LOAD_ADDR);
params.entry_point = GuestAddress::new(arm64::KERNEL_LOAD_ADDR);
params.fdt_addr = Some(GuestAddress::new(arm64::FDT_LOAD_ADDR));
}
KernelType::RawBinary | KernelType::Unknown => {
#[cfg(target_arch = "aarch64")]
{
params.kernel_addr = GuestAddress::new(arm64::KERNEL_LOAD_ADDR);
params.entry_point = GuestAddress::new(arm64::KERNEL_LOAD_ADDR);
}
#[cfg(target_arch = "x86_64")]
{
params.kernel_addr = GuestAddress::new(linux::KERNEL_LOAD_ADDR);
params.entry_point = GuestAddress::new(linux::KERNEL_LOAD_ADDR);
}
}
}
Ok(params)
}
pub fn load_initrd(path: &Path, params: &mut BootParams) -> Result<()> {
let file_size = std::fs::metadata(path)
.map_err(|e| VmmError::config(format!("Cannot stat initrd: {e}")))?
.len();
let load_addr = match params.kernel_type {
KernelType::LinuxArm64 => arm64::INITRD_LOAD_ADDR,
_ => linux::INITRD_LOAD_ADDR,
};
params.initrd_path = Some(path.to_string_lossy().to_string());
params.initrd_addr = Some(GuestAddress::new(load_addr));
params.initrd_size = Some(file_size);
tracing::info!(
"Loaded initrd: path={}, size={}, addr={:#x}",
path.display(),
file_size,
load_addr
);
Ok(())
}
pub fn read_kernel(path: &Path) -> Result<Vec<u8>> {
std::fs::read(path).map_err(|e| VmmError::config(format!("Cannot read kernel: {e}")))
}
pub fn read_initrd(path: &Path) -> Result<Vec<u8>> {
std::fs::read(path).map_err(|e| VmmError::config(format!("Cannot read initrd: {e}")))
}
}
#[cfg(target_arch = "x86_64")]
pub mod x86_64 {
use super::*;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct E820Entry {
pub addr: u64,
pub size: u64,
pub type_: u32,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct BootParamsStruct {
pub e820_entries: u8,
pub e820_table: [E820Entry; 128],
pub cmd_line_ptr: u32,
pub ramdisk_image: u32,
pub ramdisk_size: u32,
}
impl Default for BootParamsStruct {
fn default() -> Self {
Self {
e820_entries: 0,
e820_table: [E820Entry::default(); 128],
cmd_line_ptr: 0,
ramdisk_image: 0,
ramdisk_size: 0,
}
}
}
pub fn create_boot_params(params: &BootParams, memory_size: u64) -> BootParamsStruct {
let mut boot_params = BootParamsStruct::default();
boot_params.e820_table[0] = E820Entry {
addr: 0,
size: 0x9FC00, type_: linux::e820::RAM,
};
boot_params.e820_table[1] = E820Entry {
addr: 0x9FC00,
size: 0x100000 - 0x9FC00,
type_: linux::e820::RESERVED,
};
boot_params.e820_table[2] = E820Entry {
addr: 0x100000,
size: memory_size - 0x100000,
type_: linux::e820::RAM,
};
boot_params.e820_entries = 3;
boot_params.cmd_line_ptr = params.cmdline_addr.raw() as u32;
if let (Some(addr), Some(size)) = (params.initrd_addr, params.initrd_size) {
boot_params.ramdisk_image = addr.raw() as u32;
boot_params.ramdisk_size = size as u32;
}
boot_params
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_type_detection_bzimage() {
let temp = tempfile::NamedTempFile::new().unwrap();
let path = temp.path();
let mut data = vec![0u8; 0x210];
data[0x202] = 0x48; data[0x203] = 0x64; data[0x204] = 0x72; data[0x205] = 0x53;
std::fs::write(path, &data).unwrap();
let kernel_type = KernelLoader::detect_kernel_type(path).unwrap();
assert_eq!(kernel_type, KernelType::LinuxBzImage);
}
#[test]
fn test_kernel_type_detection_arm64() {
let temp = tempfile::NamedTempFile::new().unwrap();
let path = temp.path();
let mut data = vec![0u8; 0x100];
data[0x38..0x3C].copy_from_slice(b"ARM\x64");
std::fs::write(path, &data).unwrap();
let kernel_type = KernelLoader::detect_kernel_type(path).unwrap();
assert_eq!(kernel_type, KernelType::LinuxArm64);
}
#[test]
fn test_kernel_type_detection_elf() {
let temp = tempfile::NamedTempFile::new().unwrap();
let path = temp.path();
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(b"\x7FELF");
std::fs::write(path, &data).unwrap();
let kernel_type = KernelLoader::detect_kernel_type(path).unwrap();
assert_eq!(kernel_type, KernelType::RawBinary);
}
#[test]
fn test_kernel_type_detection_pe() {
let temp = tempfile::NamedTempFile::new().unwrap();
let path = temp.path();
let mut data = vec![0u8; 64];
data[0..2].copy_from_slice(b"MZ");
std::fs::write(path, &data).unwrap();
let kernel_type = KernelLoader::detect_kernel_type(path).unwrap();
assert_eq!(kernel_type, KernelType::RawBinary);
}
#[test]
fn test_kernel_type_detection_unknown() {
let temp = tempfile::NamedTempFile::new().unwrap();
let path = temp.path();
let data = vec![0x42u8; 64];
std::fs::write(path, &data).unwrap();
let kernel_type = KernelLoader::detect_kernel_type(path).unwrap();
assert_eq!(kernel_type, KernelType::Unknown);
}
#[test]
fn test_boot_params_creation() {
let params =
BootParams::new("/path/to/kernel", "console=ttyS0").with_initrd("/path/to/initrd");
assert_eq!(params.kernel_path, "/path/to/kernel");
assert_eq!(params.cmdline, "console=ttyS0");
assert_eq!(params.initrd_path, Some("/path/to/initrd".to_string()));
}
#[test]
fn test_linux_constants() {
assert_eq!(linux::KERNEL_LOAD_ADDR, 0x100000);
assert_eq!(linux::INITRD_LOAD_ADDR, 0x1000000);
assert_eq!(linux::CMDLINE_ADDR, 0x20000);
}
#[test]
fn test_arm64_constants() {
assert_eq!(arm64::KERNEL_LOAD_ADDR, 0x80000);
assert_eq!(arm64::FDT_LOAD_ADDR, 0x40000000);
}
}