Skip to content

Commit da47fa0

Browse files
committed
loader: riscv: Initial support
Initial porting to support loading Linux PE Image and dtb on RISC-V platform. Signed-off-by: Tan En De <ende.tan@starfivetech.com>
1 parent ddb2072 commit da47fa0

File tree

6 files changed

+313
-8
lines changed

6 files changed

+313
-8
lines changed

.cargo/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[target.aarch64-unknown-linux-musl]
22
rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc" ]
33

4+
[target.riscv64gc-unknown-linux-gnu]
5+
linker = "riscv64-unknown-linux-gnu-gcc"

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
[![crates.io](https://img.shields.io/crates/v/linux-loader)](https://crates.io/crates/linux-loader)
44
[![docs.rs](https://img.shields.io/docsrs/linux-loader)](https://docs.rs/linux-loader/)
55

6-
The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) and
7-
compressed big zImage (`bzImage`) format kernel images on `x86_64` and PE
8-
(`Image`) kernel images on `aarch64`. ELF support includes the
6+
The `linux-loader` crate offers support for loading
7+
- raw ELF (`vmlinux`) and compressed big zImage (`bzImage`) format kernel images on `x86_64`
8+
- PE (`Image`) kernel images on `aarch64` and `riscv64`.
9+
ELF support includes the
910
[Linux](https://www.kernel.org/doc/Documentation/x86/boot.txt) and
1011
[PVH](https://xenbits.xen.org/docs/unstable/misc/pvh.html) boot protocols.
1112

@@ -17,8 +18,9 @@ much of the boot process remains the VMM's responsibility. See [Usage] for detai
1718
- Parsing and loading kernel images into guest memory.
1819
- `x86_64`: `vmlinux` (raw ELF image), `bzImage`
1920
- `aarch64`: `Image`
21+
- `riscv64`: `Image`
2022
- Parsing and building the kernel command line.
21-
- Loading device tree blobs (`aarch64`).
23+
- Loading device tree blobs (`aarch64` and `riscv64`).
2224
- Configuring boot parameters using the exported primitives.
2325
- `x86_64` Linux boot:
2426
- [`setup_header`](https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/bootparam.h#L65)

src/loader/mod.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Copyright (c) 2023 StarFive Technology Co., Ltd. All rights reserved.
12
// Copyright © 2020, Oracle and/or its affiliates.
23
//
34
// Copyright (c) 2019 Intel Corporation. All rights reserved.
@@ -41,6 +42,11 @@ mod aarch64;
4142
#[cfg(target_arch = "aarch64")]
4243
pub use aarch64::*;
4344

45+
#[cfg(target_arch = "riscv64")]
46+
mod riscv;
47+
#[cfg(target_arch = "riscv64")]
48+
pub use riscv::*;
49+
4450
#[derive(Debug, PartialEq, Eq)]
4551
/// Kernel loader errors.
4652
pub enum Error {
@@ -53,7 +59,13 @@ pub enum Error {
5359
Elf(elf::Error),
5460

5561
/// Failed to load PE image.
56-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
62+
#[cfg(all(
63+
feature = "pe",
64+
any(
65+
target_arch = "aarch64",
66+
target_arch = "riscv64"
67+
)
68+
))]
5769
Pe(pe::Error),
5870

5971
/// Invalid command line.
@@ -80,7 +92,13 @@ impl fmt::Display for Error {
8092
Error::Bzimage(ref _e) => "failed to load bzImage kernel image",
8193
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
8294
Error::Elf(ref _e) => "failed to load ELF kernel image",
83-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
95+
#[cfg(all(
96+
feature = "pe",
97+
any(
98+
target_arch = "aarch64",
99+
target_arch = "riscv64"
100+
)
101+
))]
84102
Error::Pe(ref _e) => "failed to load PE kernel image",
85103

86104
Error::InvalidCommandLine => "invalid command line provided",
@@ -101,7 +119,13 @@ impl std::error::Error for Error {
101119
Error::Bzimage(ref e) => Some(e),
102120
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
103121
Error::Elf(ref e) => Some(e),
104-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
122+
#[cfg(all(
123+
feature = "pe",
124+
any(
125+
target_arch = "aarch64",
126+
target_arch = "riscv64"
127+
)
128+
))]
105129
Error::Pe(ref e) => Some(e),
106130

107131
Error::InvalidCommandLine => None,
@@ -127,7 +151,13 @@ impl From<bzimage::Error> for Error {
127151
}
128152
}
129153

130-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
154+
#[cfg(all(
155+
feature = "pe",
156+
any(
157+
target_arch = "aarch64",
158+
target_arch = "riscv64"
159+
)
160+
))]
131161
impl From<pe::Error> for Error {
132162
fn from(err: pe::Error) -> Self {
133163
Error::Pe(err)

src/loader/riscv/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2023 StarFive Technology Co., Ltd. All rights reserved.
2+
// Copyright (c) 2019 Intel Corporation. All rights reserved.
3+
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4+
//
5+
// Copyright 2017 The Chromium OS Authors. All rights reserved.
6+
// Use of this source code is governed by a BSD-style license that can be
7+
// found in the LICENSE-BSD-3-Clause file.
8+
//
9+
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
10+
11+
//! Traits and structs for loading `riscv` kernels into guest memory.
12+
13+
#![cfg(target_arch = "riscv64")]
14+
15+
#[cfg(feature = "pe")]
16+
pub mod pe;

src/loader/riscv/pe/mod.rs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
// Copyright (c) 2023 StarFive Technology Co., Ltd. All rights reserved.
2+
// Copyright © 2020, Oracle and/or its affiliates.
3+
// Copyright (c) 2019 Intel Corporation. All rights reserved.
4+
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
//
6+
// Copyright 2017 The Chromium OS Authors. All rights reserved.
7+
// Use of this source code is governed by a BSD-style license that can be
8+
// found in the LICENSE-BSD-3-Clause file.
9+
//
10+
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
11+
12+
//! Traits and structs for loading pe image kernels into guest memory.
13+
14+
#![cfg(feature = "pe")]
15+
16+
use std::fmt;
17+
use std::io::{Read, Seek, SeekFrom};
18+
use std::mem;
19+
20+
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize};
21+
22+
use super::super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result};
23+
24+
/// RISC-V Image (PE) format support
25+
pub struct PE;
26+
27+
// SAFETY: The layout of the structure is fixed and can be initialized by
28+
// reading its content from byte array.
29+
unsafe impl ByteValued for riscv_image_header {}
30+
31+
#[derive(Debug, PartialEq, Eq)]
32+
/// PE kernel loader errors.
33+
pub enum Error {
34+
/// Unable to seek to Image end.
35+
SeekImageEnd,
36+
/// Unable to seek to Image header.
37+
SeekImageHeader,
38+
/// Unable to seek to DTB start.
39+
SeekDtbStart,
40+
/// Unable to seek to DTB end.
41+
SeekDtbEnd,
42+
/// Device tree binary too big.
43+
DtbTooBig,
44+
/// Unable to read kernel image.
45+
ReadKernelImage,
46+
/// Unable to read Image header.
47+
ReadImageHeader,
48+
/// Unable to read DTB image
49+
ReadDtbImage,
50+
/// Invalid Image binary.
51+
InvalidImage,
52+
/// Invalid Image magic2 number.
53+
InvalidImageMagicNumber,
54+
/// Invalid base address alignment
55+
InvalidBaseAddrAlignment,
56+
}
57+
58+
impl fmt::Display for Error {
59+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60+
let desc = match self {
61+
Error::SeekImageEnd => "unable to seek Image end",
62+
Error::SeekImageHeader => "unable to seek Image header",
63+
Error::ReadImageHeader => "unable to read Image header",
64+
Error::ReadDtbImage => "unable to read DTB image",
65+
Error::SeekDtbStart => "unable to seek DTB start",
66+
Error::SeekDtbEnd => "unable to seek DTB end",
67+
Error::InvalidImage => "invalid Image",
68+
Error::InvalidImageMagicNumber => "invalid Image magic2 number",
69+
Error::DtbTooBig => "device tree image too big",
70+
Error::ReadKernelImage => "unable to read kernel image",
71+
Error::InvalidBaseAddrAlignment => {
72+
"base address not aligned to 2MiB (for riscv64)"
73+
}
74+
};
75+
76+
write!(f, "PE Kernel Loader: {}", desc)
77+
}
78+
}
79+
80+
impl std::error::Error for Error {}
81+
82+
#[repr(C)]
83+
#[derive(Debug, Copy, Clone, Default)]
84+
// See kernel doc Documentation/riscv/boot-image-header.rst
85+
// All these fields should be little endian.
86+
struct riscv_image_header {
87+
code0: u32,
88+
code1: u32,
89+
text_offset: u64,
90+
image_size: u64,
91+
flags: u64,
92+
version: u32,
93+
res1: u32,
94+
res2: u64,
95+
magic: u64,
96+
magic2: u32,
97+
res3: u32,
98+
}
99+
100+
impl KernelLoader for PE {
101+
/// Loads a PE Image into guest memory.
102+
///
103+
/// # Arguments
104+
///
105+
/// * `guest_mem` - The guest memory where the kernel image is loaded.
106+
/// * `kernel_offset` - 2MiB-aligned (for riscv64) base address in guest memory at which to load the kernel.
107+
/// * `kernel_image` - Input Image format kernel image.
108+
/// * `highmem_start_address` - ignored on RISC-V.
109+
///
110+
/// # Returns
111+
/// * KernelLoaderResult
112+
fn load<F, M: GuestMemory>(
113+
guest_mem: &M,
114+
kernel_offset: Option<GuestAddress>,
115+
kernel_image: &mut F,
116+
_highmem_start_address: Option<GuestAddress>,
117+
) -> Result<KernelLoaderResult>
118+
where
119+
F: Read + Seek,
120+
{
121+
let kernel_size = kernel_image
122+
.seek(SeekFrom::End(0))
123+
.map_err(|_| Error::SeekImageEnd)? as usize;
124+
let mut riscv_header: riscv_image_header = Default::default();
125+
kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?;
126+
127+
riscv_header
128+
.as_bytes()
129+
.read_from(0, kernel_image, mem::size_of::<riscv_image_header>())
130+
.map_err(|_| Error::ReadImageHeader)?;
131+
132+
// Skip testing `magic` field as it's deprecated as of RISC-V boot image header version 0.2
133+
// if u64::from_le(riscv_header.magic) != 0x5643534952 {
134+
// return Err(Error::InvalidImageMagicNumber.into());
135+
// }
136+
137+
if u32::from_le(riscv_header.magic2) != 0x05435352 {
138+
return Err(Error::InvalidImageMagicNumber.into());
139+
}
140+
141+
let text_offset = u64::from_le(riscv_header.text_offset);
142+
143+
// Validate that kernel_offset is 2MiB aligned (for riscv64)
144+
#[cfg(target_arch = "riscv64")]
145+
if let Some(kernel_offset) = kernel_offset {
146+
if kernel_offset.raw_value() % 0x0020_0000 != 0 {
147+
return Err(Error::InvalidBaseAddrAlignment.into());
148+
}
149+
}
150+
151+
let mem_offset = kernel_offset
152+
.unwrap_or(GuestAddress(0))
153+
.checked_add(text_offset)
154+
.ok_or(Error::InvalidImage)?;
155+
156+
let mut loader_result = KernelLoaderResult {
157+
kernel_load: mem_offset,
158+
..Default::default()
159+
};
160+
161+
kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?;
162+
guest_mem
163+
.read_exact_from(mem_offset, kernel_image, kernel_size)
164+
.map_err(|_| Error::ReadKernelImage)?;
165+
166+
loader_result.kernel_end = mem_offset
167+
.raw_value()
168+
.checked_add(kernel_size as GuestUsize)
169+
.ok_or(KernelLoaderError::MemoryOverflow)?;
170+
171+
Ok(loader_result)
172+
}
173+
}
174+
175+
/// Writes the device tree to the given memory slice.
176+
///
177+
/// # Arguments
178+
///
179+
/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob.
180+
/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob.
181+
/// * `dtb_image` - The device tree blob.
182+
pub fn load_dtb<F, M: GuestMemory>(
183+
guest_mem: &M,
184+
guest_addr: GuestAddress,
185+
dtb_image: &mut F,
186+
) -> Result<()>
187+
where
188+
F: Read + Seek,
189+
{
190+
let dtb_size = dtb_image
191+
.seek(SeekFrom::End(0))
192+
.map_err(|_| Error::SeekDtbEnd)? as usize;
193+
guest_mem
194+
.checked_offset(guest_addr, dtb_size)
195+
.ok_or(Error::DtbTooBig)?;
196+
dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?;
197+
guest_mem
198+
.read_exact_from(guest_addr, dtb_image, dtb_size)
199+
.map_err(|_| Error::ReadDtbImage.into())
200+
}
201+
202+
#[cfg(test)]
203+
mod tests {
204+
use super::*;
205+
use std::io::Cursor;
206+
use vm_memory::{Address, GuestAddress};
207+
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>;
208+
209+
const MEM_SIZE: u64 = 0x100_0000;
210+
211+
fn create_guest_mem() -> GuestMemoryMmap {
212+
GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap()
213+
}
214+
215+
fn make_image_bin() -> Vec<u8> {
216+
let mut v = Vec::new();
217+
v.extend_from_slice(include_bytes!("test_image.bin"));
218+
v
219+
}
220+
221+
#[test]
222+
fn load_image() {
223+
let gm = create_guest_mem();
224+
let mut image = make_image_bin();
225+
let kernel_addr = GuestAddress(0x400000);
226+
227+
let loader_result =
228+
PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap();
229+
assert_eq!(loader_result.kernel_load.raw_value(), 0x600000);
230+
assert_eq!(loader_result.kernel_end, 0x601000);
231+
232+
// Attempt to load the kernel at an address that is not aligned to 2MiB boundary
233+
let kernel_offset = GuestAddress(0x0030_0000);
234+
let loader_result = PE::load(&gm, Some(kernel_offset), &mut Cursor::new(&image), None);
235+
assert_eq!(
236+
loader_result,
237+
Err(KernelLoaderError::Pe(Error::InvalidBaseAddrAlignment))
238+
);
239+
240+
// Skip testing `magic` field as it's deprecated as of RISC-V boot image header version 0.2
241+
// image[0x30] = 0x0;
242+
// let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None);
243+
// assert_eq!(
244+
// loader_result,
245+
// Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber))
246+
// );
247+
248+
image[0x38] = 0x0;
249+
let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None);
250+
assert_eq!(
251+
loader_result,
252+
Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber))
253+
);
254+
}
255+
}

src/loader/riscv/pe/test_image.bin

4 KB
Binary file not shown.

0 commit comments

Comments
 (0)