Skip to content

Commit

Permalink
Support multiple kernel formats
Browse files Browse the repository at this point in the history
In the previous commit we added support for the simplest type of
external kernel, a raw image that can be directly copied into the
VM's memory.

This commit builds on that to add support for multiple kernel
formats. The ones currently implemented are:

 - ELF: A kernel binary in ELF format (vmlinux).
 - PeGz: A PE binary embedding a kernel image compressed with GZIP.
 - ImageBz2: An Image file embedding a kernel compressed with BZIP2.
 - ImageGz: An Image file embedding a kernel compressed with GZIP.
 - ImageZstd: An Image file embedding a kernel compressed with ZSTD.

Adding new kernel formats should be quite straightforward.

Please note this change doesn't implement support for loading an
external initramfs. The main reason is that we can't guarantee to
maintain the control of the VM boot when using an arbitrary
initramfs.

This means that the external kernel must be built with, at least,
the following driver built-in:

- virtio-mmio
- virtio-console
- virtio-fs

Depending on the use case, more drivers might be required.

Signed-off-by: Sergio Lopez <[email protected]>
  • Loading branch information
slp committed Jan 24, 2025
1 parent 95e72d6 commit d62666b
Show file tree
Hide file tree
Showing 9 changed files with 718 additions and 226 deletions.
73 changes: 73 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 12 additions & 3 deletions include/libkrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,18 +403,27 @@ int32_t krun_set_exec(uint32_t ctx_id,
const char *const argv[],
const char *const envp[]);

#define KRUN_KERNEL_FORMAT_RAW 0
#define KRUN_KERNEL_FORMAT_ELF 1
#define KRUN_KERNEL_FORMAT_PE_GZ 2
#define KRUN_KERNEL_FORMAT_IMAGE_BZ2 3
#define KRUN_KERNEL_FORMAT_IMAGE_GZ 4
#define KRUN_KERNEL_FORMAT_IMAGE_ZSTD 5
/**
* Sets the path to the kernel to be loaded in the microVM.
*
* Arguments:
* "ctx_id" - the configuration context ID.
* "kernel_path" - the path to the kernel, relative to the host's filesystem.
* "ctx_id" - the configuration context ID.
* "kernel_path" - the path to the kernel, relative to the host's filesystem.
* "kernel_format" - the kernel format
*
* Returns:
* Zero on success or a negative error number on failure.
*/
/* Supported disk image formats */
int32_t krun_set_kernel(uint32_t ctx_id,
const char *kernel_path);
const char *kernel_path,
uint32_t kernel_format);

/**
* Sets environment variables to be configured in the context of the executable.
Expand Down
88 changes: 55 additions & 33 deletions src/arch/src/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,48 +67,69 @@ pub const MMIO_MEM_START: u64 = FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE;
#[cfg(not(feature = "tee"))]
pub fn arch_memory_regions(
size: usize,
kernel_load_addr: u64,
kernel_load_addr: Option<u64>,
kernel_size: usize,
) -> (ArchMemoryInfo, Vec<(GuestAddress, usize)>) {
let page_size: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };

let size = round_up(size, page_size);
if size < (kernel_load_addr + kernel_size as u64) as usize {
panic!("Kernel doesn't fit in RAM");
}

// It's safe to cast MMIO_MEM_START to usize because it fits in a u32 variable
// (It points to an address in the 32 bit space).
let (ram_last_addr, shm_start_addr, regions) = match size.checked_sub(MMIO_MEM_START as usize) {
// case1: guest memory fits before the gap
None | Some(0) => {
let ram_last_addr = kernel_load_addr + kernel_size as u64 + size as u64;
let shm_start_addr = FIRST_ADDR_PAST_32BITS;
(
ram_last_addr,
shm_start_addr,
vec![
(GuestAddress(0), kernel_load_addr as usize),
(GuestAddress(kernel_load_addr + kernel_size as u64), size),
],
)
if let Some(kernel_load_addr) = kernel_load_addr {
if size < (kernel_load_addr + kernel_size as u64) as usize {
panic!("Kernel doesn't fit in RAM");
}

let ram_last_addr = kernel_load_addr + kernel_size as u64 + size as u64;
let shm_start_addr = FIRST_ADDR_PAST_32BITS;
(
ram_last_addr,
shm_start_addr,
vec![
(GuestAddress(0), kernel_load_addr as usize),
(GuestAddress(kernel_load_addr + kernel_size as u64), size),
],
)
} else {
let ram_last_addr = size as u64;
let shm_start_addr = FIRST_ADDR_PAST_32BITS;
(ram_last_addr, shm_start_addr, vec![(GuestAddress(0), size)])
}
}

// case2: guest memory extends beyond the gap
Some(remaining) => {
let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64;
let shm_start_addr = ((ram_last_addr / 0x4000_0000) + 1) * 0x4000_0000;
(
ram_last_addr,
shm_start_addr,
vec![
(GuestAddress(0), kernel_load_addr as usize),
(
GuestAddress(kernel_load_addr + kernel_size as u64),
(MMIO_MEM_START - (kernel_load_addr + kernel_size as u64)) as usize,
),
(GuestAddress(FIRST_ADDR_PAST_32BITS), remaining),
],
)
if let Some(kernel_load_addr) = kernel_load_addr {
let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64;
let shm_start_addr = ((ram_last_addr / 0x4000_0000) + 1) * 0x4000_0000;
(
ram_last_addr,
shm_start_addr,
vec![
(GuestAddress(0), kernel_load_addr as usize),
(
GuestAddress(kernel_load_addr + kernel_size as u64),
(MMIO_MEM_START - (kernel_load_addr + kernel_size as u64)) as usize,
),
(GuestAddress(FIRST_ADDR_PAST_32BITS), remaining),
],
)
} else {
let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64;
let shm_start_addr = ((ram_last_addr / 0x4000_0000) + 1) * 0x4000_0000;
(
ram_last_addr,
shm_start_addr,
vec![
(GuestAddress(0), MMIO_MEM_START as usize),
(GuestAddress(FIRST_ADDR_PAST_32BITS), remaining),
],
)
}
}
};
let info = ArchMemoryInfo {
Expand Down Expand Up @@ -319,7 +340,8 @@ mod tests {

#[test]
fn regions_lt_4gb() {
let (_info, regions) = arch_memory_regions(1usize << 29, KERNEL_LOAD_ADDR, KERNEL_SIZE);
let (_info, regions) =
arch_memory_regions(1usize << 29, Some(KERNEL_LOAD_ADDR), KERNEL_SIZE);
assert_eq!(2, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(KERNEL_LOAD_ADDR as usize, regions[0].1);
Expand All @@ -333,7 +355,7 @@ mod tests {
#[test]
fn regions_gt_4gb() {
let (_info, regions) =
arch_memory_regions((1usize << 32) + 0x8000, KERNEL_LOAD_ADDR, KERNEL_SIZE);
arch_memory_regions((1usize << 32) + 0x8000, Some(KERNEL_LOAD_ADDR), KERNEL_SIZE);
assert_eq!(3, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(KERNEL_LOAD_ADDR as usize, regions[0].1);
Expand All @@ -360,21 +382,21 @@ mod tests {
// Now assigning some memory that falls before the 32bit memory hole.
let mem_size = 128 << 20;
let (arch_mem_info, arch_mem_regions) =
arch_memory_regions(mem_size, KERNEL_LOAD_ADDR, KERNEL_SIZE);
arch_memory_regions(mem_size, Some(KERNEL_LOAD_ADDR), KERNEL_SIZE);
let gm = GuestMemoryMmap::from_ranges(&arch_mem_regions).unwrap();
configure_system(&gm, &arch_mem_info, GuestAddress(0), 0, &None, no_vcpus).unwrap();

// Now assigning some memory that is equal to the start of the 32bit memory hole.
let mem_size = 3328 << 20;
let (arch_mem_info, arch_mem_regions) =
arch_memory_regions(mem_size, KERNEL_LOAD_ADDR, KERNEL_SIZE);
arch_memory_regions(mem_size, Some(KERNEL_LOAD_ADDR), KERNEL_SIZE);
let gm = GuestMemoryMmap::from_ranges(&arch_mem_regions).unwrap();
configure_system(&gm, &arch_mem_info, GuestAddress(0), 0, &None, no_vcpus).unwrap();

// Now assigning some memory that falls after the 32bit memory hole.
let mem_size = 3330 << 20;
let (arch_mem_info, arch_mem_regions) =
arch_memory_regions(mem_size, KERNEL_LOAD_ADDR, KERNEL_SIZE);
arch_memory_regions(mem_size, Some(KERNEL_LOAD_ADDR), KERNEL_SIZE);
let gm = GuestMemoryMmap::from_ranges(&arch_mem_regions).unwrap();
configure_system(&gm, &arch_mem_info, GuestAddress(0), 0, &None, no_vcpus).unwrap();
}
Expand Down
Loading

0 comments on commit d62666b

Please sign in to comment.