Skip to content

Commit 484db1b

Browse files
endeneerRuoqingHe
authored andcommitted
riscv64: Initial linux-loader support
Initial porting to support loading Linux PE Image on RISC-V platform. Signed-off-by: Tan En De <ende.tan@starfivetech.com>
1 parent 69fc3b3 commit 484db1b

File tree

6 files changed

+232
-7
lines changed

6 files changed

+232
-7
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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
[![docs.rs](https://img.shields.io/docsrs/linux-loader)](https://docs.rs/linux-loader/)
55

66
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
7+
compressed big zImage (`bzImage`) format kernel images on `x86_64`,
8+
and 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: 10 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,7 @@ 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(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
5763
Pe(pe::Error),
5864

5965
/// Invalid command line.
@@ -80,7 +86,7 @@ impl fmt::Display for Error {
8086
Error::Bzimage(ref _e) => "failed to load bzImage kernel image",
8187
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
8288
Error::Elf(ref _e) => "failed to load ELF kernel image",
83-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
89+
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
8490
Error::Pe(ref _e) => "failed to load PE kernel image",
8591

8692
Error::InvalidCommandLine => "invalid command line provided",
@@ -101,7 +107,7 @@ impl std::error::Error for Error {
101107
Error::Bzimage(ref e) => Some(e),
102108
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
103109
Error::Elf(ref e) => Some(e),
104-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
110+
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
105111
Error::Pe(ref e) => Some(e),
106112

107113
Error::InvalidCommandLine => None,
@@ -127,7 +133,7 @@ impl From<bzimage::Error> for Error {
127133
}
128134
}
129135

130-
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
136+
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
131137
impl From<pe::Error> for Error {
132138
fn from(err: pe::Error) -> Self {
133139
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: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
19+
use vm_memory::{Address, ByteValued, GuestAddress, GuestMemory, GuestUsize, ReadVolatile};
20+
21+
use super::super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result};
22+
23+
/// RISC-V Image (PE) format support
24+
pub struct PE;
25+
26+
// SAFETY: The layout of the structure is fixed and can be initialized by
27+
// reading its content from byte array.
28+
unsafe impl ByteValued for riscv_image_header {}
29+
30+
#[derive(Debug, PartialEq, Eq)]
31+
/// PE kernel loader errors.
32+
pub enum Error {
33+
/// Unable to seek to Image end.
34+
SeekImageEnd,
35+
/// Unable to seek to Image header.
36+
SeekImageHeader,
37+
/// Unable to read kernel image.
38+
ReadKernelImage,
39+
/// Unable to read Image header.
40+
ReadImageHeader,
41+
/// Invalid Image binary.
42+
InvalidImage,
43+
/// Invalid Image magic2 number.
44+
InvalidImageMagicNumber,
45+
/// Invalid base address alignment
46+
InvalidBaseAddrAlignment,
47+
}
48+
49+
impl fmt::Display for Error {
50+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51+
let desc = match self {
52+
Error::SeekImageEnd => "unable to seek Image end",
53+
Error::SeekImageHeader => "unable to seek Image header",
54+
Error::ReadImageHeader => "unable to read Image header",
55+
Error::InvalidImage => "invalid Image",
56+
Error::InvalidImageMagicNumber => "invalid Image magic2 number",
57+
Error::ReadKernelImage => "unable to read kernel image",
58+
Error::InvalidBaseAddrAlignment => "base address not aligned to 2MiB (for riscv64)",
59+
};
60+
61+
write!(f, "PE Kernel Loader: {}", desc)
62+
}
63+
}
64+
65+
impl std::error::Error for Error {}
66+
67+
#[repr(C)]
68+
#[derive(Debug, Copy, Clone, Default)]
69+
// See kernel doc Documentation/riscv/boot-image-header.rst
70+
// All these fields should be little endian.
71+
struct riscv_image_header {
72+
code0: u32,
73+
code1: u32,
74+
text_offset: u64,
75+
image_size: u64,
76+
flags: u64,
77+
version: u32,
78+
res1: u32,
79+
res2: u64,
80+
magic: u64,
81+
magic2: u32,
82+
res3: u32,
83+
}
84+
85+
impl KernelLoader for PE {
86+
/// Loads a PE Image into guest memory.
87+
///
88+
/// # Arguments
89+
///
90+
/// * `guest_mem` - The guest memory where the kernel image is loaded.
91+
/// * `kernel_offset` - 2MiB-aligned (for riscv64) base address in guest memory at which to load the kernel.
92+
/// * `kernel_image` - Input Image format kernel image.
93+
/// * `highmem_start_address` - ignored on RISC-V.
94+
///
95+
/// # Returns
96+
/// * KernelLoaderResult
97+
fn load<F, M: GuestMemory>(
98+
guest_mem: &M,
99+
kernel_offset: Option<GuestAddress>,
100+
kernel_image: &mut F,
101+
_highmem_start_address: Option<GuestAddress>,
102+
) -> Result<KernelLoaderResult>
103+
where
104+
F: ReadVolatile + Read + Seek,
105+
{
106+
let kernel_size = kernel_image
107+
.seek(SeekFrom::End(0))
108+
.map_err(|_| Error::SeekImageEnd)? as usize;
109+
let mut riscv_header: riscv_image_header = Default::default();
110+
kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?;
111+
112+
kernel_image
113+
.read_exact(riscv_header.as_mut_slice())
114+
.map_err(|_| Error::ReadImageHeader)?;
115+
116+
if u32::from_le(riscv_header.magic2) != 0x05435352 {
117+
return Err(Error::InvalidImageMagicNumber.into());
118+
}
119+
120+
let text_offset = u64::from_le(riscv_header.text_offset);
121+
122+
// Validate that kernel_offset is 2MiB aligned (for riscv64)
123+
#[cfg(target_arch = "riscv64")]
124+
if let Some(kernel_offset) = kernel_offset {
125+
if kernel_offset.raw_value() % 0x0020_0000 != 0 {
126+
return Err(Error::InvalidBaseAddrAlignment.into());
127+
}
128+
}
129+
130+
let mem_offset = kernel_offset
131+
.unwrap_or(GuestAddress(0))
132+
.checked_add(text_offset)
133+
.ok_or(Error::InvalidImage)?;
134+
135+
let mut loader_result = KernelLoaderResult {
136+
kernel_load: mem_offset,
137+
..Default::default()
138+
};
139+
140+
kernel_image.rewind().map_err(|_| Error::SeekImageHeader)?;
141+
guest_mem
142+
.read_exact_volatile_from(mem_offset, kernel_image, kernel_size)
143+
.map_err(|_| Error::ReadKernelImage)?;
144+
145+
loader_result.kernel_end = mem_offset
146+
.raw_value()
147+
.checked_add(kernel_size as GuestUsize)
148+
.ok_or(KernelLoaderError::MemoryOverflow)?;
149+
150+
Ok(loader_result)
151+
}
152+
}
153+
154+
#[cfg(test)]
155+
mod tests {
156+
use super::*;
157+
use std::io::Cursor;
158+
use vm_memory::{Address, GuestAddress};
159+
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>;
160+
161+
const MEM_SIZE: u64 = 0x100_0000;
162+
163+
fn create_guest_mem() -> GuestMemoryMmap {
164+
GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap()
165+
}
166+
167+
fn make_image_bin() -> Vec<u8> {
168+
let mut v = Vec::new();
169+
v.extend_from_slice(include_bytes!("test_image.bin"));
170+
v
171+
}
172+
173+
#[test]
174+
fn load_image() {
175+
let gm = create_guest_mem();
176+
let mut image = make_image_bin();
177+
let kernel_addr = GuestAddress(0x400000);
178+
179+
let loader_result =
180+
PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap();
181+
assert_eq!(loader_result.kernel_load.raw_value(), 0x600000);
182+
assert_eq!(loader_result.kernel_end, 0x601000);
183+
184+
// Attempt to load the kernel at an address that is not aligned to 2MiB boundary
185+
let kernel_offset = GuestAddress(0x0030_0000);
186+
let loader_result = PE::load(&gm, Some(kernel_offset), &mut Cursor::new(&image), None);
187+
assert_eq!(
188+
loader_result,
189+
Err(KernelLoaderError::Pe(Error::InvalidBaseAddrAlignment))
190+
);
191+
192+
image[0x38] = 0x0;
193+
let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None);
194+
assert_eq!(
195+
loader_result,
196+
Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber))
197+
);
198+
}
199+
}

src/loader/riscv/pe/test_image.bin

4 KB
Binary file not shown.

0 commit comments

Comments
 (0)