Skip to content

Commit

Permalink
loader: riscv: Initial support
Browse files Browse the repository at this point in the history
Initial porting to support loading Linux PE Image and dtb on RISC-V platform.

Signed-off-by: Tan En De <ende.tan@starfivetech.com>
  • Loading branch information
endeneer committed Sep 29, 2023
1 parent ddb2072 commit da47fa0
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .cargo/config
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
[![crates.io](https://img.shields.io/crates/v/linux-loader)](https://crates.io/crates/linux-loader)
[![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
The `linux-loader` crate offers support for loading
- raw ELF (`vmlinux`) and compressed big zImage (`bzImage`) format kernel images on `x86_64`
- 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.

Expand All @@ -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)
Expand Down
38 changes: 34 additions & 4 deletions src/loader/mod.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -53,7 +59,13 @@ 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.
Expand All @@ -80,7 +92,13 @@ 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",
Expand All @@ -101,7 +119,13 @@ 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,
Expand All @@ -127,7 +151,13 @@ impl From<bzimage::Error> for Error {
}
}

#[cfg(all(feature = "pe", target_arch = "aarch64"))]
#[cfg(all(
feature = "pe",
any(
target_arch = "aarch64",
target_arch = "riscv64"
)
))]
impl From<pe::Error> for Error {
fn from(err: pe::Error) -> Self {
Error::Pe(err)
Expand Down
16 changes: 16 additions & 0 deletions src/loader/riscv/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
255 changes: 255 additions & 0 deletions src/loader/riscv/pe/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// 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 std::mem;

use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize};

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 seek to DTB start.
SeekDtbStart,
/// Unable to seek to DTB end.
SeekDtbEnd,
/// Device tree binary too big.
DtbTooBig,
/// Unable to read kernel image.
ReadKernelImage,
/// Unable to read Image header.
ReadImageHeader,
/// Unable to read DTB image
ReadDtbImage,
/// 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::ReadDtbImage => "unable to read DTB image",
Error::SeekDtbStart => "unable to seek DTB start",
Error::SeekDtbEnd => "unable to seek DTB end",
Error::InvalidImage => "invalid Image",
Error::InvalidImageMagicNumber => "invalid Image magic2 number",
Error::DtbTooBig => "device tree image too big",
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<F, M: GuestMemory>(
guest_mem: &M,
kernel_offset: Option<GuestAddress>,
kernel_image: &mut F,
_highmem_start_address: Option<GuestAddress>,
) -> Result<KernelLoaderResult>
where
F: 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)?;

riscv_header
.as_bytes()
.read_from(0, kernel_image, mem::size_of::<riscv_image_header>())
.map_err(|_| Error::ReadImageHeader)?;

// Skip testing `magic` field as it's deprecated as of RISC-V boot image header version 0.2
// if u64::from_le(riscv_header.magic) != 0x5643534952 {
// return Err(Error::InvalidImageMagicNumber.into());
// }

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_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)
}
}

/// Writes the device tree to the given memory slice.
///
/// # Arguments
///
/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob.
/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob.
/// * `dtb_image` - The device tree blob.
pub fn load_dtb<F, M: GuestMemory>(
guest_mem: &M,
guest_addr: GuestAddress,
dtb_image: &mut F,
) -> Result<()>
where
F: Read + Seek,
{
let dtb_size = dtb_image
.seek(SeekFrom::End(0))
.map_err(|_| Error::SeekDtbEnd)? as usize;
guest_mem
.checked_offset(guest_addr, dtb_size)
.ok_or(Error::DtbTooBig)?;
dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?;
guest_mem
.read_exact_from(guest_addr, dtb_image, dtb_size)
.map_err(|_| Error::ReadDtbImage.into())
}

#[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<u8> {
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))
);

// Skip testing `magic` field as it's deprecated as of RISC-V boot image header version 0.2
// image[0x30] = 0x0;
// let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None);
// assert_eq!(
// loader_result,
// Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber))
// );

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))
);
}
}
Binary file added src/loader/riscv/pe/test_image.bin
Binary file not shown.

0 comments on commit da47fa0

Please sign in to comment.