From cf68d9cebc8dd76c5584f1dd4aaf9973a006cd21 Mon Sep 17 00:00:00 2001
From: Wei Gao <gaowei66@huawei.com>
Date: Wed, 4 Nov 2020 01:40:05 -0500
Subject: [PATCH] boot_loader: add support for bzImage format linux kernel.

1. Add support for bzImage format linux kernel in x86_64.
2. Update comment in boot_loader.
3. Update docs for bzImage format kernel.

Signed-off-by: Wei Gao <gaowei66@huawei.com>
---
 README.ch.md                        |   2 +-
 README.md                           |   2 +-
 boot_loader/src/aarch64/mod.rs      |   5 +-
 boot_loader/src/lib.rs              |  58 +++++++++-------
 boot_loader/src/x86_64/bootparam.rs |  38 ++++++++---
 boot_loader/src/x86_64/mod.rs       | 102 ++++++++++++++++++++++++----
 docs/config_guidebook.md            |   2 +-
 docs/quickstart.md                  |  11 ++-
 8 files changed, 169 insertions(+), 51 deletions(-)

diff --git a/README.ch.md b/README.ch.md
index 0189ccc..bac84f9 100644
--- a/README.ch.md
+++ b/README.ch.md
@@ -21,7 +21,7 @@ $ cargo build --release
 
 ### 运行软件
 为了快速上手StratoVirt，需要准备
-* PE格式的Linux内核镜像
+* PE格式或bzImage格式(仅x86_64)的Linux内核镜像
 * EXT4格式的rootfs镜像
 
 ```shell
diff --git a/README.md b/README.md
index 2fc3311..5e45cd7 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Now you can find StratoVirt binary in `target/release/stratovirt`.
 
 ### Run a VM with StratoVirt
 To run StratoVirt quickly, requires
-* A PE format Linux kernel
+* A PE or bzImage (only x86_64) format Linux kernel
 * An EXT4-format rootfs image
 
 ```shell
diff --git a/boot_loader/src/aarch64/mod.rs b/boot_loader/src/aarch64/mod.rs
index 1c6a94e..52dded6 100644
--- a/boot_loader/src/aarch64/mod.rs
+++ b/boot_loader/src/aarch64/mod.rs
@@ -58,8 +58,10 @@ pub struct AArch64BootLoaderConfig {
 
 /// The start address for `kernel image`, `initrd image` and `dtb` in guest memory.
 pub struct AArch64BootLoader {
-    /// Start address for `kernel image` in guest memory.
+    /// Start address for `kernel` execute binary in guest memory.
     pub kernel_start: u64,
+    /// Start address for `vmlinux` in guest memory.
+    pub vmlinux_start: u64,
     /// Start address for `initrd image` in guest memory.
     pub initrd_start: u64,
     /// Start address for `dtb` in guest memory.
@@ -110,6 +112,7 @@ pub fn linux_bootloader(
 
     Ok(AArch64BootLoader {
         kernel_start: DRAM_MEM_START + AARCH64_KERNEL_OFFSET,
+        vmlinux_start: DRAM_MEM_START + AARCH64_KERNEL_OFFSET,
         initrd_start: initrd_addr,
         dtb_start: dtb_addr,
     })
diff --git a/boot_loader/src/lib.rs b/boot_loader/src/lib.rs
index 3207586..bd947a8 100644
--- a/boot_loader/src/lib.rs
+++ b/boot_loader/src/lib.rs
@@ -17,7 +17,7 @@
 //! ## Design
 //!
 //! This crate offers support for:
-//! 1. Loading PE (vmlinux.bin) kernel images.
+//! 1. Loading PE (vmlinux.bin) kernel images and bzImage kernel images (only in x86_64).
 //! 2. Loading initrd image.
 //! 3. Initialization for architecture related information.
 //!
@@ -79,8 +79,8 @@ mod aarch64;
 #[cfg(target_arch = "x86_64")]
 mod x86_64;
 
-use std::fs;
-use std::path::PathBuf;
+use std::fs::File;
+use std::io::{Seek, SeekFrom};
 use std::sync::Arc;
 
 use address_space::{AddressSpace, GuestAddress};
@@ -112,44 +112,44 @@ pub mod errors {
         }
         errors {
             BootLoaderOpenKernel {
-                description("Boot loader open kernel error")
-                display("Failed to open kernel image or initrd")
+                display("Failed to open kernel image")
+            }
+            BootLoaderOpenInitrd {
+                display("Failed to open initrd image")
             }
         }
     }
 }
 
-use self::errors::{ErrorKind, Result};
+use self::errors::{ErrorKind, Result, ResultExt};
 
-/// Load PE(vmlinux.bin) linux kernel to Guest Memory.
+/// Load linux kernel or initrd image file to Guest Memory.
 ///
 /// # Arguments
-/// * `kernel_file` - host path for kernel.
-/// * `kernel_start` - kernel start address in guest memory.
+/// * `image` - image file for kernel or initrd.
+/// * `start_addr` - image start address in guest memory.
 /// * `sys_mem` - guest memory.
 ///
 /// # Errors
-/// * `BootLoaderOpenKernel`: Open PE linux kernel failed.
-/// * `AddressSpace`: Write PE linux kernel to guest memory failed.
-fn load_image(kernel_file: &PathBuf, kernel_start: u64, sys_mem: &Arc<AddressSpace>) -> Result<()> {
-    debug!("Loading image {:?}", kernel_file);
-    let len = std::fs::metadata(kernel_file).unwrap().len();
-    let mut kernel_image = match fs::File::open(kernel_file) {
-        Ok(file) => file,
-        _ => return Err(ErrorKind::BootLoaderOpenKernel.into()),
-    };
+/// * `BootLoaderOpenKernel`: Open image failed.
+/// * `AddressSpace`: Write image to guest memory failed.
+fn load_image(image: &mut File, start_addr: u64, sys_mem: &Arc<AddressSpace>) -> Result<()> {
+    let curr_loc = image.seek(SeekFrom::Current(0)).unwrap();
+    let len = image.seek(SeekFrom::End(0)).unwrap();
+    image.seek(SeekFrom::Start(curr_loc)).unwrap();
 
-    sys_mem.write(&mut kernel_image, GuestAddress(kernel_start), len)?;
+    sys_mem.write(image, GuestAddress(start_addr), len - curr_loc)?;
 
     Ok(())
 }
 
-/// Load PE(vmlinux.bin) linux kernel and other boot source to Guest Memory.
+/// Load PE(vmlinux.bin) linux kernel / bzImage linux kernel (only x86_64) and
+/// other boot source to Guest Memory.
 ///
 /// # Steps
 ///
 /// 1. Prepare for linux kernel boot env, return guest memory layout.
-/// 2. According guest memory layout, load PE linux kernel to guest memory.
+/// 2. According guest memory layout, load linux kernel to guest memory.
 /// 3. According guest memory layout, load initrd image to guest memory.
 /// 4. For `x86_64` arch, inject cmdline to guest memory.
 ///
@@ -164,12 +164,24 @@ fn load_image(kernel_file: &PathBuf, kernel_start: u64, sys_mem: &Arc<AddressSpa
 /// Load kernel, initrd or kernel cmdline to guest memory failed. Boot source
 /// is broken or guest memory is unnormal.
 pub fn load_kernel(config: &BootLoaderConfig, sys_mem: &Arc<AddressSpace>) -> Result<BootLoader> {
+    let mut kernel_image =
+        File::open(&config.kernel).chain_err(|| ErrorKind::BootLoaderOpenKernel)?;
+
+    #[cfg(target_arch = "x86_64")]
+    let boot_loader = {
+        let boot_hdr = x86_64::load_bzimage(&mut kernel_image).ok();
+        linux_bootloader(config, sys_mem, boot_hdr)?
+    };
+    #[cfg(target_arch = "aarch64")]
     let boot_loader = linux_bootloader(config, sys_mem)?;
 
-    load_image(&config.kernel, boot_loader.kernel_start, &sys_mem)?;
+    load_image(&mut kernel_image, boot_loader.vmlinux_start, &sys_mem)?;
+
     match &config.initrd {
         Some(initrd) => {
-            load_image(&initrd, boot_loader.initrd_start, &sys_mem)?;
+            let mut initrd_image =
+                File::open(initrd).chain_err(|| ErrorKind::BootLoaderOpenInitrd)?;
+            load_image(&mut initrd_image, boot_loader.initrd_start, &sys_mem)?;
         }
         None => {}
     };
diff --git a/boot_loader/src/x86_64/bootparam.rs b/boot_loader/src/x86_64/bootparam.rs
index 16914e9..8023823 100644
--- a/boot_loader/src/x86_64/bootparam.rs
+++ b/boot_loader/src/x86_64/bootparam.rs
@@ -14,6 +14,10 @@ use util::byte_code::ByteCode;
 
 pub const E820_RAM: u32 = 1;
 pub const E820_RESERVED: u32 = 2;
+pub const BOOT_VERSION: u16 = 0x0200;
+pub const BOOT_FLAG: u16 = 0xAA55;
+pub const HDRS: u32 = 0x5372_6448;
+pub const UNDEFINED_ID: u8 = 0xFF;
 
 // Structures below sourced from:
 // https://www.kernel.org/doc/html/latest/x86/boot.html
@@ -21,7 +25,7 @@ pub const E820_RESERVED: u32 = 2;
 #[repr(C, packed)]
 #[derive(Debug, Default, Copy, Clone)]
 pub struct RealModeKernelHeader {
-    setup_sects: u8,
+    pub setup_sects: u8,
     root_flags: u16,
     syssize: u32,
     ram_size: u16,
@@ -29,15 +33,15 @@ pub struct RealModeKernelHeader {
     root_dev: u16,
     boot_flag: u16,
     jump: u16,
-    header: u32,
-    version: u16,
+    pub header: u32,
+    pub version: u16,
     realmode_swtch: u32,
     start_sys_seg: u16,
     kernel_version: u16,
     type_of_loader: u8,
-    loadflags: u8,
+    pub loadflags: u8,
     setup_move_size: u16,
-    code32_start: u32,
+    pub code32_start: u32,
     ramdisk_image: u32,
     ramdisk_size: u32,
     bootsect_kludge: u32,
@@ -62,12 +66,14 @@ pub struct RealModeKernelHeader {
     kernel_info_offset: u32,
 }
 
+impl ByteCode for RealModeKernelHeader {}
+
 impl RealModeKernelHeader {
     pub fn new(cmdline_ptr: u32, cmdline_size: u32, ramdisk_image: u32, ramdisk_size: u32) -> Self {
         RealModeKernelHeader {
-            boot_flag: 0xaa55,
-            header: 0x5372_6448,  // "HdrS"
-            type_of_loader: 0xff, // undefined identifier and version
+            boot_flag: BOOT_FLAG,
+            header: HDRS,
+            type_of_loader: UNDEFINED_ID,
             cmdline_ptr,
             cmdline_size,
             ramdisk_image,
@@ -75,6 +81,20 @@ impl RealModeKernelHeader {
             ..Default::default()
         }
     }
+
+    pub fn setup(
+        &mut self,
+        cmdline_ptr: u32,
+        cmdline_size: u32,
+        ramdisk_image: u32,
+        ramdisk_size: u32,
+    ) {
+        self.type_of_loader = UNDEFINED_ID;
+        self.cmdline_ptr = cmdline_ptr;
+        self.cmdline_size = cmdline_size;
+        self.ramdisk_image = ramdisk_image;
+        self.ramdisk_size = ramdisk_size;
+    }
 }
 
 #[repr(C, packed)]
@@ -171,7 +191,7 @@ mod test {
             kernel_cmdline: String::from("this_is_a_piece_of_test_string"),
             cpu_count: 2,
         };
-        let (_, initrd_addr_tmp) = setup_boot_params(&config, &space).unwrap();
+        let (_, initrd_addr_tmp) = setup_boot_params(&config, &space, None).unwrap();
         assert_eq!(initrd_addr_tmp, 0xfff_0000);
         let test_zero_page = space
             .read_object::<BootParams>(GuestAddress(0x0000_7000))
diff --git a/boot_loader/src/x86_64/mod.rs b/boot_loader/src/x86_64/mod.rs
index a4bcb3f..0dcbfa7 100644
--- a/boot_loader/src/x86_64/mod.rs
+++ b/boot_loader/src/x86_64/mod.rs
@@ -10,7 +10,7 @@
 // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 // See the Mulan PSL v2 for more details.
 
-//! Boot Loader load PE linux kernel image to guest memory according
+//! Boot Loader load PE and bzImage linux kernel image to guest memory according
 //! [`x86 boot protocol`](https://www.kernel.org/doc/Documentation/x86/boot.txt).
 //!
 //! Below is x86_64 bootloader memory layout:
@@ -60,6 +60,8 @@ mod bootparam;
 mod gdt;
 mod mptable;
 
+use std::fs::File;
+use std::io::{Read, Seek, SeekFrom};
 use std::path::PathBuf;
 use std::string::String;
 use std::sync::Arc;
@@ -68,13 +70,14 @@ use kvm_bindings::kvm_segment;
 
 use self::errors::{ErrorKind, Result, ResultExt};
 use address_space::{AddressSpace, GuestAddress};
-use bootparam::{BootParams, RealModeKernelHeader, E820_RAM, E820_RESERVED};
+use bootparam::{BootParams, RealModeKernelHeader, BOOT_VERSION, E820_RAM, E820_RESERVED, HDRS};
 use gdt::GdtEntry;
 use mptable::{
     BusEntry, ConfigTableHeader, FloatingPointer, IOApicEntry, IOInterruptEntry,
     LocalInterruptEntry, ProcessEntry, DEST_ALL_LAPIC_MASK, INTERRUPT_TYPE_EXTINT,
     INTERRUPT_TYPE_INT, INTERRUPT_TYPE_NMI, IOAPIC_BASE_ADDR, LAPIC_BASE_ADDR,
 };
+use util::byte_code::ByteCode;
 use util::checksum::obj_checksum;
 
 pub mod errors {
@@ -82,10 +85,16 @@ pub mod errors {
         links {
             AddressSpace(address_space::errors::Error, address_space::errors::ErrorKind);
         }
+        foreign_links {
+            Io(std::io::Error);
+        }
         errors {
             MaxCpus(cpus: u8) {
                 display("Configure cpu number({}) above supported max cpu numbers(254)", cpus)
             }
+            InvalidBzImage {
+                display("Invalid bzImage kernel file")
+            }
         }
     }
 }
@@ -95,11 +104,13 @@ const PML4_START: u64 = 0x0000_9000;
 const PDPTE_START: u64 = 0x0000_a000;
 const PDE_START: u64 = 0x0000_b000;
 const CMDLINE_START: u64 = 0x0002_0000;
+const BOOT_HDR_START: u64 = 0x0000_01F1;
+const BZIMAGE_BOOT_OFFSET: u64 = 0x0200;
 
 const EBDA_START: u64 = 0x0009_fc00;
 const VGA_RAM_BEGIN: u64 = 0x000a_0000;
 const MB_BIOS_BEGIN: u64 = 0x000f_0000;
-const VMLINUX_RAM_START: u64 = 0x0010_0000;
+pub const VMLINUX_RAM_START: u64 = 0x0010_0000;
 const KVM_32BIT_GAP_SIZE: u64 = 0x0300 << 20; /* 3GB */
 const KVM_32BIT_MAX_MEM_SIZE: u64 = 1 << 32; /* 4GB */
 const KVM_32BIT_GAP_START: u64 = KVM_32BIT_MAX_MEM_SIZE - KVM_32BIT_GAP_SIZE;
@@ -115,6 +126,50 @@ const BOOT_IDT_OFFSET: u64 = 0x520;
 
 const BOOT_GDT_MAX: usize = 4;
 
+/// Load bzImage linux kernel to Guest Memory.
+///
+/// # Notes
+/// According to `Documentation/x86/boot.txt`, bzImage includes two parts:
+/// * the setup
+/// * the compressed kernel
+/// The setup `RealModeKernelHeader` can be load at offset `0x01f1` in bzImage kernel image.
+/// The compressed kernel will be loaded into guest memory at `code32_start` in
+/// `RealModeKernelHeader`.
+/// The start address of compressed kernel is the loader address + 0x200. It will be
+/// set in `kernel_start` in `BootLoader` structure set.
+///
+/// # Arguments
+/// * `kernel_file` - host path for kernel.
+/// * `sys_mem` - guest memory.
+///
+/// # Errors
+/// * `InvalidBzImage`: BzImage header or version is invalid.
+/// * `AddressSpace`: Write bzImage linux kernel to guest memory failed.
+pub fn load_bzimage(kernel_image: &mut File) -> Result<bootparam::RealModeKernelHeader> {
+    kernel_image.seek(SeekFrom::Start(BOOT_HDR_START))?;
+    let mut boot_hdr_buf = [0_u8; std::mem::size_of::<bootparam::RealModeKernelHeader>()];
+    kernel_image.read_exact(&mut boot_hdr_buf)?;
+    let boot_hdr = bootparam::RealModeKernelHeader::from_bytes(&boot_hdr_buf).unwrap();
+
+    if boot_hdr.header != HDRS {
+        return Err(ErrorKind::InvalidBzImage.into());
+    }
+
+    if (boot_hdr.version < BOOT_VERSION) || ((boot_hdr.loadflags & 0x1) == 0x0) {
+        return Err(ErrorKind::InvalidBzImage.into());
+    }
+
+    let mut setup_size = boot_hdr.setup_sects as u64;
+    if setup_size == 0 {
+        setup_size = 4;
+    }
+    setup_size = (setup_size + 1) << 9;
+
+    kernel_image.seek(SeekFrom::Start(setup_size as u64))?;
+
+    Ok(*boot_hdr)
+}
+
 /// Boot loader config used for x86_64.
 pub struct X86BootLoaderConfig {
     /// Path of the kernel image.
@@ -131,6 +186,7 @@ pub struct X86BootLoaderConfig {
 
 /// The start address for some boot source in guest memory for `x86_64`.
 pub struct X86BootLoader {
+    pub vmlinux_start: u64,
     pub kernel_start: u64,
     pub kernel_sp: u64,
     pub initrd_start: u64,
@@ -266,6 +322,7 @@ fn setup_isa_mptable(sys_mem: &Arc<AddressSpace>, start_addr: u64, num_cpus: u8)
 fn setup_boot_params(
     config: &X86BootLoaderConfig,
     sys_mem: &Arc<AddressSpace>,
+    boot_hdr: Option<RealModeKernelHeader>,
 ) -> Result<(u64, u64)> {
     let (ramdisk_size, ramdisk_image, initrd_addr) = if config.initrd_size > 0 {
         let mut initrd_addr_max = INITRD_ADDR_MAX as u32;
@@ -280,12 +337,22 @@ fn setup_boot_params(
         (0u32, 0u32, 0u64)
     };
 
-    let mut boot_params = BootParams::new(RealModeKernelHeader::new(
-        CMDLINE_START as u32,
-        config.kernel_cmdline.len() as u32,
-        ramdisk_image,
-        ramdisk_size,
-    ));
+    let mut boot_params = if let Some(mut boot_hdr) = boot_hdr {
+        boot_hdr.setup(
+            CMDLINE_START as u32,
+            config.kernel_cmdline.len() as u32,
+            ramdisk_image,
+            ramdisk_size,
+        );
+        BootParams::new(boot_hdr)
+    } else {
+        BootParams::new(RealModeKernelHeader::new(
+            CMDLINE_START as u32,
+            config.kernel_cmdline.len() as u32,
+            ramdisk_image,
+            ramdisk_size,
+        ))
+    };
 
     boot_params.add_e820_entry(
         REAL_MODE_IVT_BEGIN,
@@ -376,17 +443,28 @@ pub fn setup_gdt(guest_mem: &Arc<AddressSpace>) -> Result<BootGdtSegment> {
 pub fn linux_bootloader(
     config: &X86BootLoaderConfig,
     sys_mem: &Arc<AddressSpace>,
+    boot_hdr: Option<RealModeKernelHeader>,
 ) -> Result<X86BootLoader> {
+    let (kernel_start, vmlinux_start) = if let Some(boot_hdr) = boot_hdr {
+        (
+            boot_hdr.code32_start as u64 + BZIMAGE_BOOT_OFFSET,
+            boot_hdr.code32_start as u64,
+        )
+    } else {
+        (VMLINUX_STARTUP, VMLINUX_STARTUP)
+    };
+
     let boot_pml4 = setup_page_table(sys_mem)?;
 
     setup_isa_mptable(sys_mem, EBDA_START, config.cpu_count)?;
 
-    let (zero_page, initrd_addr) = setup_boot_params(&config, sys_mem)?;
+    let (zero_page, initrd_addr) = setup_boot_params(&config, sys_mem, boot_hdr)?;
 
     let gdt_seg = setup_gdt(sys_mem)?;
 
     Ok(X86BootLoader {
-        kernel_start: VMLINUX_STARTUP,
+        kernel_start,
+        vmlinux_start,
         kernel_sp: BOOT_LOADER_SP,
         initrd_start: initrd_addr,
         boot_pml4_addr: boot_pml4,
@@ -450,7 +528,7 @@ mod test {
             kernel_cmdline: String::from("this_is_a_piece_of_test_string"),
             cpu_count: 2,
         };
-        let (_, initrd_addr_tmp) = setup_boot_params(&config, &space).unwrap();
+        let (_, initrd_addr_tmp) = setup_boot_params(&config, &space, None).unwrap();
         assert_eq!(initrd_addr_tmp, 0xfff_0000);
 
         //test setup_gdt function
diff --git a/docs/config_guidebook.md b/docs/config_guidebook.md
index 35a8e7e..65e1a35 100644
--- a/docs/config_guidebook.md
+++ b/docs/config_guidebook.md
@@ -56,7 +56,7 @@ But unfortunately, in json configuration file, only `byte` is supported as unit.
 
 ### 1.3 Kernel and Kernel Parameters
 
-StratoVirt supports to launch PE-format linux kernel 4.19 and can also set kernel
+StratoVirt supports to launch PE or bzImage (only x86_64) format linux kernel 4.19 and can also set kernel
  parameters for VM.
 
 This allows you to give a path to linux kernel, the path can be either absolute path or relative path.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 376cb2c..e74e395 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -66,8 +66,8 @@ Now you can find StratoVirt binary file in `target/${arch}-unknown-linux-gnu/rel
 
 ### 3.1 Build kernel
 
-The StratoVirt in current version supports only PE-format kernel images on both x86_64 and aarch64
-platforms, which can be built with:
+The StratoVirt in current version supports PE or bzImage (only x86_64) format kernel images on
+both x86_64 and aarch64 platforms, which can be built with:
 
 1. Firstly, get the openEuler kernel source code:
 
@@ -95,6 +95,11 @@ copy it to `kernel` path as `.config`. You can also modify config options by:
    $ make -j vmlinux && objcopy -O binary vmlinux vmlinux.bin
    ```
 
+5. If you want compile bzImage format kernel in x86_64.
+   ```shell
+   $ make -j bzImage
+   ```
+
 ### 3.2 Make rootfs
 
 Rootfs image is a file system image.  An EXT4-format image with `/sbin/init` can be mounted at
@@ -159,7 +164,7 @@ With kernel and rootfs image, we can boot a guest linux machine by StratoVirt.
 
 The minimum configuration for StratoVirt is:
 
-* A PE format linux kernel
+* A PE or bzImage (only x86_64) format linux kernel
 * A rootfs image as virtio-blk device, which has to be added to kernel parameters
 * Api-channel to control StratoVirt
 * If you want to login with ttyS0, you may need a serial and add ttyS0 to kernel parameters
-- 
2.27.0

