summary refs log tree commit diff
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2018-12-11 16:29:26 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-02-07 03:02:08 -0800
commite403f5ccd0581ec62fbfb86de00b8c01958ffa67 (patch)
tree8bc44cbdeba1e57780a27d8092918b00a870bb54
parente54b33834c6adba8921947330583afa19fbd100a (diff)
downloadcrosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.tar
crosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.tar.gz
crosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.tar.bz2
crosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.tar.lz
crosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.tar.xz
crosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.tar.zst
crosvm-e403f5ccd0581ec62fbfb86de00b8c01958ffa67.zip
linux: add support for loading an initrd
Based on Linux boot protocol references:
- x86: Documentation/x86/boot.txt
- arm: Documentation/devicetree/bindings/chosen.txt

BUG=None
TEST=Boot Alpine Linux netboot initrd on x86_64 and aarch64

Change-Id: If4730765638f0a0b8bb8f63203c98e4765a354ee
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1407221
Tested-by: kokoro <noreply+kokoro@google.com>
-rw-r--r--aarch64/src/fdt.rs16
-rw-r--r--aarch64/src/lib.rs26
-rw-r--r--arch/src/lib.rs1
-rw-r--r--src/linux.rs12
-rw-r--r--src/main.rs6
-rw-r--r--x86_64/src/lib.rs34
6 files changed, 89 insertions, 6 deletions
diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs
index 0780a55..e8ab335 100644
--- a/aarch64/src/fdt.rs
+++ b/aarch64/src/fdt.rs
@@ -165,11 +165,21 @@ fn create_psci_node(fdt: &mut Vec<u8>) -> Result<(), Box<Error>> {
     Ok(())
 }
 
-fn create_chosen_node(fdt: &mut Vec<u8>, cmdline: &CStr) -> Result<(), Box<Error>> {
+fn create_chosen_node(
+    fdt: &mut Vec<u8>,
+    cmdline: &CStr,
+    initrd: Option<(GuestAddress, usize)>,
+) -> Result<(), Box<Error>> {
     begin_node(fdt, "chosen")?;
     property_u32(fdt, "linux,pci-probe-only", 1)?;
     property_cstring(fdt, "bootargs", cmdline)?;
     property_u64(fdt, "kaslr", 0)?;
+    if let Some((initrd_addr, initrd_size)) = initrd {
+        let initrd_start = initrd_addr.offset() as u32;
+        let initrd_end = initrd_start + initrd_size as u32;
+        property_u32(fdt, "linux,initrd-start", initrd_start)?;
+        property_u32(fdt, "linux,initrd-end", initrd_end)?;
+    }
     end_node(fdt)?;
 
     Ok(())
@@ -286,6 +296,7 @@ fn create_rtc_node(fdt: &mut Vec<u8>) -> Result<(), Box<Error>> {
 /// * `num_cpus` - Number of virtual CPUs the guest will have
 /// * `fdt_load_offset` - The offset into physical memory for the device tree
 /// * `cmdline` - The kernel commandline
+/// * `initrd` - An optional tuple of initrd guest physical address and size
 pub fn create_fdt(
     fdt_max_size: usize,
     guest_mem: &GuestMemory,
@@ -293,6 +304,7 @@ pub fn create_fdt(
     num_cpus: u32,
     fdt_load_offset: u64,
     cmdline: &CStr,
+    initrd: Option<(GuestAddress, usize)>,
 ) -> Result<(), Box<Error>> {
     let mut fdt = vec![0; fdt_max_size];
     start_fdt(&mut fdt, fdt_max_size)?;
@@ -304,7 +316,7 @@ pub fn create_fdt(
     property_u32(&mut fdt, "#address-cells", 0x2)?;
     property_u32(&mut fdt, "#size-cells", 0x2)?;
 
-    create_chosen_node(&mut fdt, cmdline)?;
+    create_chosen_node(&mut fdt, cmdline, initrd)?;
     create_memory_node(&mut fdt, guest_mem)?;
     create_cpu_nodes(&mut fdt, num_cpus)?;
     create_gic_node(&mut fdt)?;
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index 6bbc775..b03c3a4 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -38,6 +38,7 @@ mod fdt;
 // We place the kernel at offset 8MB
 const AARCH64_KERNEL_OFFSET: u64 = 0x80000;
 const AARCH64_FDT_MAX_SIZE: u64 = 0x200000;
+const AARCH64_INITRD_ALIGN: u64 = 0x1000000;
 
 // These constants indicate the address space used by the ARM vGIC.
 const AARCH64_GIC_DIST_SIZE: u64 = 0x10000;
@@ -135,6 +136,8 @@ pub enum Error {
     FDTCreateFailure(Box<error::Error>),
     /// Kernel could not be loaded
     KernelLoadFailure(arch::LoadImageError),
+    /// Initrd could not be loaded
+    InitrdLoadFailure(arch::LoadImageError),
     /// Failure to Create GIC
     CreateGICFailure(sys_util::Error),
     /// Couldn't register PCI bus.
@@ -159,6 +162,7 @@ impl error::Error for Error {
             &Error::CreateVcpu(_) => "failed to create VCPU",
             &Error::FDTCreateFailure(_) => "FDT could not be created",
             &Error::KernelLoadFailure(_) => "Kernel cound not be loaded",
+            &Error::InitrdLoadFailure(_) => "initrd cound not be loaded",
             &Error::CreateGICFailure(_) => "Failure to create GIC",
             &Error::RegisterPci(_) => "error registering PCI bus",
             &Error::RegisterVsock(_) => "error registering virtual socket device",
@@ -255,19 +259,22 @@ impl arch::LinuxArch for AArch64 {
 
         // separate out kernel loading from other setup to get a specific error for
         // kernel loading
-        arch::load_image(
+        let kernel_size = arch::load_image(
             &mem,
             &mut components.kernel_image,
             get_kernel_addr(),
             u64::max_value(),
         )
         .map_err(Error::KernelLoadFailure)?;
+        let kernel_end = get_kernel_addr().offset() + kernel_size as u64;
         Self::setup_system_memory(
             &mem,
             components.memory_mb,
             vcpu_count,
             &CString::new(cmdline).unwrap(),
+            components.initrd_image,
             pci_irqs,
+            kernel_end,
         )?;
 
         Ok(RunnableLinuxVm {
@@ -291,8 +298,24 @@ impl AArch64 {
         mem_size: u64,
         vcpu_count: u32,
         cmdline: &CStr,
+        initrd_file: Option<File>,
         pci_irqs: Vec<(u32, PciInterruptPin)>,
+        kernel_end: u64,
     ) -> Result<()> {
+        let initrd = match initrd_file {
+            Some(initrd_file) => {
+                let mut initrd_file = initrd_file;
+                let initrd_addr =
+                    (kernel_end + (AARCH64_INITRD_ALIGN - 1)) & !(AARCH64_INITRD_ALIGN - 1);
+                let initrd_max_size = mem_size - (initrd_addr - AARCH64_PHYS_MEM_START);
+                let initrd_addr = GuestAddress(initrd_addr);
+                let initrd_size =
+                    arch::load_image(mem, &mut initrd_file, initrd_addr, initrd_max_size)
+                        .map_err(Error::InitrdLoadFailure)?;
+                Some((initrd_addr, initrd_size))
+            }
+            None => None,
+        };
         fdt::create_fdt(
             AARCH64_FDT_MAX_SIZE as usize,
             mem,
@@ -300,6 +323,7 @@ impl AArch64 {
             vcpu_count,
             fdt_offset(mem_size),
             cmdline,
+            initrd,
         )?;
         Ok(())
     }
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index e6814bc..8a9c2d8 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -42,6 +42,7 @@ pub struct VmComponents {
     pub vcpu_count: u32,
     pub kernel_image: File,
     pub android_fstab: Option<File>,
+    pub initrd_image: Option<File>,
     pub extra_kernel_params: Vec<String>,
     pub wayland_dmabuf: bool,
 }
diff --git a/src/linux.rs b/src/linux.rs
index beb1021..b81bc71 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -69,6 +69,7 @@ pub enum Error {
     NetDeviceNew(devices::virtio::NetError),
     NoVarEmpty,
     OpenAndroidFstab(PathBuf, io::Error),
+    OpenInitrd(PathBuf, io::Error),
     OpenKernel(PathBuf, io::Error),
     P9DeviceNew(devices::virtio::P9Error),
     PollContextAdd(sys_util::Error),
@@ -126,6 +127,7 @@ impl fmt::Display for Error {
             }
             Error::NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {:?}", e),
             Error::NoVarEmpty => write!(f, "/var/empty doesn't exist, can't jail devices."),
+            Error::OpenInitrd(p, e) => write!(f, "failed to open initrd {:?}: {}", p, e),
             Error::OpenKernel(p, e) => write!(f, "failed to open kernel image {:?}: {}", p, e),
             Error::OpenAndroidFstab(ref p, ref e) => {
                 write!(f, "failed to open android fstab file {:?}: {}", p, e)
@@ -966,6 +968,15 @@ pub fn run_config(cfg: Config) -> Result<()> {
     // quickly.
     let sigchld_fd = SignalFd::new(libc::SIGCHLD).map_err(Error::CreateSignalFd)?;
 
+    let initrd_image = if let Some(ref initrd_path) = cfg.initrd_path {
+        Some(
+            File::open(initrd_path.as_path())
+                .map_err(|e| Error::OpenInitrd(initrd_path.clone(), e))?,
+        )
+    } else {
+        None
+    };
+
     let components = VmComponents {
         memory_mb: (cfg.memory.unwrap_or(256) << 20) as u64,
         vcpu_count: cfg.vcpu_count.unwrap_or(1),
@@ -978,6 +989,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
                 File::open(x.as_path()).map_err(|e| Error::OpenAndroidFstab(x.to_path_buf(), e))
             })
             .map_or(Ok(None), |v| v.map(Some))?,
+        initrd_image,
         extra_kernel_params: cfg.params.clone(),
         wayland_dmabuf: cfg.wayland_dmabuf,
     };
diff --git a/src/main.rs b/src/main.rs
index 31019f9..8a4fce0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -96,6 +96,7 @@ pub struct Config {
     memory: Option<usize>,
     kernel_path: PathBuf,
     android_fstab: Option<PathBuf>,
+    initrd_path: Option<PathBuf>,
     params: Vec<String>,
     socket_path: Option<PathBuf>,
     plugin: Option<PathBuf>,
@@ -130,6 +131,7 @@ impl Default for Config {
             memory: None,
             kernel_path: PathBuf::default(),
             android_fstab: None,
+            initrd_path: None,
             params: Vec::new(),
             socket_path: None,
             plugin: None,
@@ -569,6 +571,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
         "split-irqchip" => {
             cfg.split_irqchip = true;
         }
+        "initrd" => {
+            cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned()));
+        }
         "help" => return Err(argument::Error::PrintHelp),
         _ => unreachable!(),
     }
@@ -579,6 +584,7 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
     let arguments =
         &[Argument::positional("KERNEL", "bzImage of kernel to run"),
           Argument::value("android-fstab", "PATH", "Path to Android fstab"),
+          Argument::short_value('i', "initrd", "PATH", "Initial ramdisk to load."),
           Argument::short_value('p',
                                 "params",
                                 "PARAMS",
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index a56accf..69dcf76 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -113,6 +113,7 @@ pub enum Error {
     RegisterVsock(arch::DeviceRegistrationError),
     LoadCmdline(kernel_loader::Error),
     LoadKernel(kernel_loader::Error),
+    LoadInitrd(arch::LoadImageError),
     /// Error writing the zero page of guest memory.
     ZeroPageSetup,
     /// The zero page extends past the end of guest_mem.
@@ -138,6 +139,7 @@ impl error::Error for Error {
             Error::RegisterVsock(_) => "error registering virtual socket device",
             Error::LoadCmdline(_) => "Error Loading command line",
             Error::LoadKernel(_) => "Error Loading Kernel",
+            Error::LoadInitrd(_) => "Error loading initrd",
             Error::ZeroPageSetup => "Error writing the zero page of guest memory",
             Error::ZeroPagePastRamEnd => "The zero page extends past the end of guest_mem",
             Error::E820Configuration => "Invalid e820 setup params",
@@ -174,6 +176,7 @@ fn configure_system(
     num_cpus: u8,
     pci_irqs: Vec<(u32, PciInterruptPin)>,
     setup_data: Option<GuestAddress>,
+    initrd: Option<(GuestAddress, usize)>,
 ) -> Result<()> {
     const EBDA_START: u64 = 0x0009fc00;
     const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
@@ -197,6 +200,10 @@ fn configure_system(
     if let Some(setup_data) = setup_data {
         params.hdr.setup_data = setup_data.offset();
     }
+    if let Some((initrd_addr, initrd_size)) = initrd {
+        params.hdr.ramdisk_image = initrd_addr.offset() as u32;
+        params.hdr.ramdisk_size = initrd_size as u32;
+    }
 
     add_e820_entry(&mut params, 0, EBDA_START, E820_RAM)?;
 
@@ -341,6 +348,7 @@ impl arch::LinuxArch for X8664arch {
             components.memory_mb,
             vcpu_count,
             &CString::new(cmdline).unwrap(),
+            components.initrd_image,
             pci_irqs,
             components.android_fstab,
             kernel_end,
@@ -384,11 +392,13 @@ impl X8664arch {
     /// * `mem` - The memory to be used by the guest.
     /// * `vcpu_count` - Number of virtual CPUs the guest will have.
     /// * `cmdline` - the kernel commandline
+    /// * `initrd_file` - an initial ramdisk image
     fn setup_system_memory(
         mem: &GuestMemory,
         mem_size: u64,
         vcpu_count: u32,
         cmdline: &CStr,
+        initrd_file: Option<File>,
         pci_irqs: Vec<(u32, PciInterruptPin)>,
         android_fstab: Option<File>,
         kernel_end: u64,
@@ -396,24 +406,41 @@ impl X8664arch {
         kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline)?;
 
         // Track the first free address after the kernel - this is where extra
-        // data like the device tree blob will be loaded.
-        let free_addr = kernel_end;
+        // data like the device tree blob and initrd will be loaded.
+        let mut free_addr = kernel_end;
 
         let setup_data = if let Some(fstab) = android_fstab {
             let mut fstab = fstab;
             let free_addr_aligned = (((free_addr + 64 - 1) / 64) * 64) + 64;
             let dtb_start = GuestAddress(free_addr_aligned);
-            let _dtb_size = fdt::create_fdt(
+            let dtb_size = fdt::create_fdt(
                 X86_64_FDT_MAX_SIZE as usize,
                 mem,
                 dtb_start.offset(),
                 &mut fstab,
             )?;
+            free_addr = dtb_start.offset() + dtb_size as u64;
             Some(dtb_start)
         } else {
             None
         };
 
+        let initrd = match initrd_file {
+            Some(mut initrd_file) => {
+                let initrd_start = free_addr;
+                let initrd_max_size = mem_size - initrd_start;
+                let initrd_size = arch::load_image(
+                    mem,
+                    &mut initrd_file,
+                    GuestAddress(initrd_start),
+                    initrd_max_size,
+                )
+                .map_err(Error::LoadInitrd)?;
+                Some((GuestAddress(initrd_start), initrd_size))
+            }
+            None => None,
+        };
+
         configure_system(
             mem,
             mem_size,
@@ -423,6 +450,7 @@ impl X8664arch {
             vcpu_count as u8,
             pci_irqs,
             setup_data,
+            initrd,
         )?;
         Ok(())
     }