use std::io::Seek;
use anyhow::{Context, Result};
use kvm_bindings::{KVM_REG_ARM_CORE, KVM_REG_ARM64, KVM_REG_SIZE_U64};
use kvm_ioctls::VcpuFd;
use vm_memory::{Address, GuestAddress, GuestMemoryMmap};
use crate::vmm::kvm::{CMDLINE_MAX, KERNEL_LOAD_ADDR};
pub struct KernelLoadResult {
pub entry: u64,
pub kernel_end: u64,
}
const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];
pub fn load_kernel(
guest_mem: &GuestMemoryMmap,
kernel_path: &std::path::Path,
) -> Result<KernelLoadResult> {
use linux_loader::loader::{KernelLoader, pe::PE};
use std::fs::File;
use std::io::Read;
let mut kernel_file = File::open(kernel_path)
.with_context(|| format!("open kernel: {}", kernel_path.display()))?;
let mut magic = [0u8; 2];
kernel_file
.read_exact(&mut magic)
.context("read kernel magic")?;
kernel_file
.seek(std::io::SeekFrom::Start(0))
.context("seek kernel to start")?;
if magic == GZIP_MAGIC {
let mut decoder = flate2::read::GzDecoder::new(kernel_file);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.context("decompress gzip kernel")?;
let mut cursor = std::io::Cursor::new(decompressed);
let result = PE::load(
guest_mem,
Some(GuestAddress(KERNEL_LOAD_ADDR)),
&mut cursor,
None,
)
.context("load decompressed aarch64 Image")?;
Ok(KernelLoadResult {
entry: result.kernel_load.raw_value(),
kernel_end: result.kernel_end,
})
} else {
let result = PE::load(
guest_mem,
Some(GuestAddress(KERNEL_LOAD_ADDR)),
&mut kernel_file,
None,
)
.context("load aarch64 Image")?;
Ok(KernelLoadResult {
entry: result.kernel_load.raw_value(),
kernel_end: result.kernel_end,
})
}
}
pub fn validate_cmdline(cmdline: &str) -> Result<()> {
anyhow::ensure!(
cmdline.len() < CMDLINE_MAX,
"cmdline too long ({} > {})",
cmdline.len(),
CMDLINE_MAX
);
Ok(())
}
const REG_CORE_BASE: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE as u64;
const REG_X0: u64 = REG_CORE_BASE;
const REG_PC: u64 = REG_CORE_BASE | (256 / 4);
const REG_PSTATE: u64 = REG_CORE_BASE | (264 / 4);
const PSTATE_MODE_EL1H: u64 = 0x5;
const PSTATE_DAIF_MASK: u64 = 0x3C0;
pub fn setup_regs(vcpu: &VcpuFd, entry: u64, fdt_addr: u64) -> Result<()> {
vcpu.set_one_reg(REG_PC, &entry.to_le_bytes())
.context("set PC")?;
vcpu.set_one_reg(REG_X0, &fdt_addr.to_le_bytes())
.context("set x0 (FDT address)")?;
let pstate: u64 = PSTATE_MODE_EL1H | PSTATE_DAIF_MASK;
vcpu.set_one_reg(REG_PSTATE, &pstate.to_le_bytes())
.context("set pstate")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kernel_load_result_fields() {
let r = KernelLoadResult {
entry: 0x28_0000,
kernel_end: 0x40_0000,
};
assert_eq!(r.entry, 0x28_0000);
assert_eq!(r.kernel_end, 0x40_0000);
}
#[test]
fn write_cmdline_basic() {
validate_cmdline("console=ttyAMA0").unwrap();
}
#[test]
fn write_cmdline_too_long() {
let long = "x".repeat(CMDLINE_MAX + 1);
assert!(validate_cmdline(&long).is_err());
}
#[test]
fn reg_ids_follow_encoding() {
assert_eq!(REG_X0 & 0xFFFF, 0);
assert_eq!(REG_PC & 0xFFFF, 64);
assert_eq!(REG_PSTATE & 0xFFFF, 66);
}
#[test]
fn pstate_el1h_value() {
let pstate = PSTATE_MODE_EL1H | PSTATE_DAIF_MASK;
assert_eq!(pstate, 0x3C5);
}
#[test]
fn setup_regs_on_real_vcpu() {
use crate::vmm::topology::Topology;
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
};
let vm = crate::vmm::kvm::KtstrKvm::new(topo, 64, false).unwrap();
let result = setup_regs(&vm.vcpus[0], 0x28_0000, 0x4000_0000);
assert!(result.is_ok(), "setup_regs failed: {:?}", result.err());
let mut pc_buf = [0u8; 8];
vm.vcpus[0].get_one_reg(REG_PC, &mut pc_buf).unwrap();
assert_eq!(u64::from_le_bytes(pc_buf), 0x28_0000);
let mut x0_buf = [0u8; 8];
vm.vcpus[0].get_one_reg(REG_X0, &mut x0_buf).unwrap();
assert_eq!(u64::from_le_bytes(x0_buf), 0x4000_0000);
let mut pstate_buf = [0u8; 8];
vm.vcpus[0]
.get_one_reg(REG_PSTATE, &mut pstate_buf)
.unwrap();
assert_eq!(u64::from_le_bytes(pstate_buf), 0x3C5);
}
}