diff --git a/.cargo/config b/.cargo/config index ba63e46b..d23b917a 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,3 +1,5 @@ [target.aarch64-unknown-linux-musl] rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc" ] +[target.riscv64gc-unknown-linux-gnu] +linker = "riscv64-unknown-linux-gnu-gcc" diff --git a/README.md b/README.md index ed8097b0..bb864957 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ [![docs.rs](https://img.shields.io/docsrs/linux-loader)](https://docs.rs/linux-loader/) The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) and -compressed big zImage (`bzImage`) format kernel images on `x86_64` and PE -(`Image`) kernel images on `aarch64`. ELF support includes the +compressed big zImage (`bzImage`) format kernel images on `x86_64`, +and PE (`Image`) kernel images on `aarch64` and `riscv64`. +ELF support includes the [Linux](https://www.kernel.org/doc/Documentation/x86/boot.txt) and [PVH](https://xenbits.xen.org/docs/unstable/misc/pvh.html) boot protocols. @@ -17,8 +18,9 @@ much of the boot process remains the VMM's responsibility. See [Usage] for detai - Parsing and loading kernel images into guest memory. - `x86_64`: `vmlinux` (raw ELF image), `bzImage` - `aarch64`: `Image` + - `riscv64`: `Image` - Parsing and building the kernel command line. -- Loading device tree blobs (`aarch64`). +- Loading device tree blobs (`aarch64` and `riscv64`). - Configuring boot parameters using the exported primitives. - `x86_64` Linux boot: - [`setup_header`](https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/bootparam.h#L65) diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 3570283e..cc9ea7aa 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -1,3 +1,4 @@ +// Copyright (c) 2023 StarFive Technology Co., Ltd. All rights reserved. // Copyright © 2020, Oracle and/or its affiliates. // // Copyright (c) 2019 Intel Corporation. All rights reserved. @@ -41,6 +42,11 @@ mod aarch64; #[cfg(target_arch = "aarch64")] pub use aarch64::*; +#[cfg(target_arch = "riscv64")] +mod riscv; +#[cfg(target_arch = "riscv64")] +pub use riscv::*; + #[derive(Debug, PartialEq, Eq)] /// Kernel loader errors. pub enum Error { @@ -53,7 +59,7 @@ pub enum Error { Elf(elf::Error), /// Failed to load PE image. - #[cfg(all(feature = "pe", target_arch = "aarch64"))] + #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Pe(pe::Error), /// Invalid command line. @@ -80,7 +86,7 @@ impl fmt::Display for Error { Error::Bzimage(ref _e) => "failed to load bzImage kernel image", #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] Error::Elf(ref _e) => "failed to load ELF kernel image", - #[cfg(all(feature = "pe", target_arch = "aarch64"))] + #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Error::Pe(ref _e) => "failed to load PE kernel image", Error::InvalidCommandLine => "invalid command line provided", @@ -101,7 +107,7 @@ impl std::error::Error for Error { Error::Bzimage(ref e) => Some(e), #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] Error::Elf(ref e) => Some(e), - #[cfg(all(feature = "pe", target_arch = "aarch64"))] + #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Error::Pe(ref e) => Some(e), Error::InvalidCommandLine => None, @@ -127,7 +133,7 @@ impl From for Error { } } -#[cfg(all(feature = "pe", target_arch = "aarch64"))] +#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] impl From for Error { fn from(err: pe::Error) -> Self { Error::Pe(err) diff --git a/src/loader/riscv/mod.rs b/src/loader/riscv/mod.rs new file mode 100644 index 00000000..a81bc7cf --- /dev/null +++ b/src/loader/riscv/mod.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2023 StarFive Technology Co., Ltd. All rights reserved. +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for loading `riscv` kernels into guest memory. + +#![cfg(target_arch = "riscv64")] + +#[cfg(feature = "pe")] +pub mod pe; diff --git a/src/loader/riscv/pe/mod.rs b/src/loader/riscv/pe/mod.rs new file mode 100644 index 00000000..68c427c0 --- /dev/null +++ b/src/loader/riscv/pe/mod.rs @@ -0,0 +1,199 @@ +// Copyright (c) 2023 StarFive Technology Co., Ltd. All rights reserved. +// Copyright © 2020, Oracle and/or its affiliates. +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for loading pe image kernels into guest memory. + +#![cfg(feature = "pe")] + +use std::fmt; +use std::io::{Read, Seek, SeekFrom}; + +use vm_memory::{Address, ByteValued, GuestAddress, GuestMemory, GuestUsize, ReadVolatile}; + +use super::super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; + +/// RISC-V Image (PE) format support +pub struct PE; + +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for riscv_image_header {} + +#[derive(Debug, PartialEq, Eq)] +/// PE kernel loader errors. +pub enum Error { + /// Unable to seek to Image end. + SeekImageEnd, + /// Unable to seek to Image header. + SeekImageHeader, + /// Unable to read kernel image. + ReadKernelImage, + /// Unable to read Image header. + ReadImageHeader, + /// Invalid Image binary. + InvalidImage, + /// Invalid Image magic2 number. + InvalidImageMagicNumber, + /// Invalid base address alignment + InvalidBaseAddrAlignment, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let desc = match self { + Error::SeekImageEnd => "unable to seek Image end", + Error::SeekImageHeader => "unable to seek Image header", + Error::ReadImageHeader => "unable to read Image header", + Error::InvalidImage => "invalid Image", + Error::InvalidImageMagicNumber => "invalid Image magic2 number", + Error::ReadKernelImage => "unable to read kernel image", + Error::InvalidBaseAddrAlignment => "base address not aligned to 2MiB (for riscv64)", + }; + + write!(f, "PE Kernel Loader: {}", desc) + } +} + +impl std::error::Error for Error {} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +// See kernel doc Documentation/riscv/boot-image-header.rst +// All these fields should be little endian. +struct riscv_image_header { + code0: u32, + code1: u32, + text_offset: u64, + image_size: u64, + flags: u64, + version: u32, + res1: u32, + res2: u64, + magic: u64, + magic2: u32, + res3: u32, +} + +impl KernelLoader for PE { + /// Loads a PE Image into guest memory. + /// + /// # Arguments + /// + /// * `guest_mem` - The guest memory where the kernel image is loaded. + /// * `kernel_offset` - 2MiB-aligned (for riscv64) base address in guest memory at which to load the kernel. + /// * `kernel_image` - Input Image format kernel image. + /// * `highmem_start_address` - ignored on RISC-V. + /// + /// # Returns + /// * KernelLoaderResult + fn load( + guest_mem: &M, + kernel_offset: Option, + kernel_image: &mut F, + _highmem_start_address: Option, + ) -> Result + where + F: ReadVolatile + Read + Seek, + { + let kernel_size = kernel_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekImageEnd)? as usize; + let mut riscv_header: riscv_image_header = Default::default(); + kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?; + + kernel_image + .read_exact(riscv_header.as_mut_slice()) + .map_err(|_| Error::ReadImageHeader)?; + + if u32::from_le(riscv_header.magic2) != 0x05435352 { + return Err(Error::InvalidImageMagicNumber.into()); + } + + let text_offset = u64::from_le(riscv_header.text_offset); + + // Validate that kernel_offset is 2MiB aligned (for riscv64) + #[cfg(target_arch = "riscv64")] + if let Some(kernel_offset) = kernel_offset { + if kernel_offset.raw_value() % 0x0020_0000 != 0 { + return Err(Error::InvalidBaseAddrAlignment.into()); + } + } + + let mem_offset = kernel_offset + .unwrap_or(GuestAddress(0)) + .checked_add(text_offset) + .ok_or(Error::InvalidImage)?; + + let mut loader_result = KernelLoaderResult { + kernel_load: mem_offset, + ..Default::default() + }; + + kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?; + guest_mem + .read_exact_volatile_from(mem_offset, kernel_image, kernel_size) + .map_err(|_| Error::ReadKernelImage)?; + + loader_result.kernel_end = mem_offset + .raw_value() + .checked_add(kernel_size as GuestUsize) + .ok_or(KernelLoaderError::MemoryOverflow)?; + + Ok(loader_result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + use vm_memory::{Address, GuestAddress}; + type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; + + const MEM_SIZE: u64 = 0x100_0000; + + fn create_guest_mem() -> GuestMemoryMmap { + GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() + } + + fn make_image_bin() -> Vec { + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("test_image.bin")); + v + } + + #[test] + fn load_image() { + let gm = create_guest_mem(); + let mut image = make_image_bin(); + let kernel_addr = GuestAddress(0x400000); + + let loader_result = + PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap(); + assert_eq!(loader_result.kernel_load.raw_value(), 0x600000); + assert_eq!(loader_result.kernel_end, 0x601000); + + // Attempt to load the kernel at an address that is not aligned to 2MiB boundary + let kernel_offset = GuestAddress(0x0030_0000); + let loader_result = PE::load(&gm, Some(kernel_offset), &mut Cursor::new(&image), None); + assert_eq!( + loader_result, + Err(KernelLoaderError::Pe(Error::InvalidBaseAddrAlignment)) + ); + + image[0x38] = 0x0; + let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None); + assert_eq!( + loader_result, + Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber)) + ); + } +} diff --git a/src/loader/riscv/pe/test_image.bin b/src/loader/riscv/pe/test_image.bin new file mode 100644 index 00000000..6fa8b74d Binary files /dev/null and b/src/loader/riscv/pe/test_image.bin differ