-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkernel.rs
189 lines (159 loc) · 6.56 KB
/
kernel.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#![cfg(target_arch = "x86_64")]
use std::fs::File;
use std::path::PathBuf;
use std::result;
use linux_loader::bootparam::boot_params;
use linux_loader::cmdline::Cmdline;
use linux_loader::configurator::{linux::LinuxBootConfigurator, BootConfigurator, BootParams};
use linux_loader::loader::{elf::Elf, load_cmdline, KernelLoader, KernelLoaderResult};
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
use crate::{Error, Result};
/// x86_64 boot constants. See <https://www.kernel.org/doc/Documentation/x86/boot.txt> for the full
/// documentation.
/// Header field: `boot_flag`. Must contain 0xaa55. This is the closest thing old Linux kernels
/// have to a magic number.
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
/// Header field: `header`. Must contain the magic number `HdrS` (0x5372_6448).
const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
/// Header field: `type_of_loader`. Unless using a pre-registered bootloader (which we aren't), this
/// field must be set to 0xff.
const KERNEL_LOADER_OTHER: u8 = 0xff;
/// Header field: `kernel_alignment`. Alignment unit required by a relocatable kernel.
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x0100_0000;
/// Start address for the EBDA (Extended Bios Data Area). Older computers (like the one this VMM
/// emulates) typically use 1 KiB for the EBDA, starting at 0x9fc00.
/// See <https://wiki.osdev.org/Memory_Map_(x86)> for more information.
const EBDA_START: u64 = 0x0009_fc00;
/// RAM memory type.
const E820_RAM: u32 = 1;
/// Address of the zeropage, where Linux kernel boot parameters are written.
pub(crate) const ZEROPG_START: u64 = 0x7000;
const HIMEM_START: u64 = 0x0010_0000; // 1 MB
/// Address where the kernel command line is written.
const CMDLINE_START: u64 = 0x0002_0000;
/// Default command line
pub const DEFAULT_CMDLINE: &str = "i8042.nokbd reboot=k panic=1 pci=off";
/// Add a new memory map entry to the e820 table.
fn add_e820_entry(
params: &mut boot_params,
addr: u64,
size: u64,
mem_type: u32,
) -> result::Result<(), Error> {
if params.e820_entries >= params.e820_table.len() as u8 {
return Err(Error::E820Configuration);
}
params.e820_table[params.e820_entries as usize].addr = addr;
params.e820_table[params.e820_entries as usize].size = size;
params.e820_table[params.e820_entries as usize].type_ = mem_type;
params.e820_entries += 1;
Ok(())
}
/// Build boot parameters for ELF kernels following the Linux boot protocol.
///
/// # Arguments
///
/// * `guest_memory` - guest memory
/// * `himem_start` - address where high memory starts.
pub fn build_bootparams(
guest_memory: &GuestMemoryMmap,
himem_start: GuestAddress,
) -> std::result::Result<boot_params, Error> {
let mut params = boot_params::default();
params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
params.hdr.header = KERNEL_HDR_MAGIC;
params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
// Add an entry for EBDA itself.
add_e820_entry(&mut params, 0, EBDA_START, E820_RAM)?;
// Add entries for the usable RAM regions.
let last_addr = guest_memory.last_addr();
add_e820_entry(
&mut params,
himem_start.raw_value() as u64,
last_addr
.checked_offset_from(himem_start)
.ok_or(Error::HimemStartPastMemEnd)?,
E820_RAM,
)?;
Ok(params)
}
/// Set guest kernel up in the memory.
/// This loads the kernel and initramfs into guest memory and configures the boot parameters.
/// Returns the address where the kernel was loaded : [`KernelLoaderResult`](KernelLoaderResult).
///
/// # Arguments
///
/// * `guest_memory` - Guest memory map
/// * `kernel_path` - Path to the kernel image
/// * `initramfs_path` - Path to the initramfs image to load into guest memory
/// * `cmdline` - Kernel command line, use [`DEFAULT_CMDLINE`](DEFAULT_CMDLINE) as a default
pub fn kernel_setup(
guest_memory: &GuestMemoryMmap,
kernel_path: PathBuf,
initramfs_path: Option<String>,
cmdline: &Cmdline,
) -> Result<KernelLoaderResult> {
let mut kernel_image = File::open(kernel_path).map_err(Error::IO)?;
let zero_page_addr = GuestAddress(ZEROPG_START);
// Load the kernel into guest memory.
let kernel_load = Elf::load(
guest_memory,
None,
&mut kernel_image,
Some(GuestAddress(HIMEM_START)),
)
.map_err(Error::KernelLoad)?;
// Generate boot parameters.
let mut bootparams = build_bootparams(guest_memory, GuestAddress(HIMEM_START))?;
let cmdline_str = cmdline
.as_cstring()
.map_err(Error::Cmdline)?
.into_string()
.map_err(Error::IntoStringError)?;
let cmdline_size = cmdline_str.len() as u32;
// Add the kernel command line to the boot parameters.
bootparams.hdr.cmd_line_ptr = CMDLINE_START as u32;
bootparams.hdr.cmdline_size = cmdline_size + 1;
// Shrink the command line to the actual size.
let mut shrinked_cmdline =
linux_loader::cmdline::Cmdline::new(cmdline_size as usize + 1).map_err(Error::Cmdline)?;
shrinked_cmdline
.insert_str(&cmdline_str)
.map_err(Error::Cmdline)?;
// Add the initramfs to the boot parameters if one was provided.
if let Some(initramfs_path) = initramfs_path {
// Open the initramfs file
let mut initramfs_file = File::open(initramfs_path).map_err(Error::IO)?;
let initramfs_size = initramfs_file.metadata().unwrap().len() as usize;
// Find the address where the initramfs should be loaded.
// The initramfs is loaded right after the kernel.
let initramfs_address = kernel_load.kernel_end + 1;
// Load the initramfs into guest memory.
guest_memory
.read_from(
GuestAddress(initramfs_address),
&mut initramfs_file,
initramfs_size,
)
.map_err(|_| Error::InitramfsLoad)?;
// Set the initramfs address and size in the boot parameters.
bootparams.hdr.ramdisk_image = initramfs_address as u32;
bootparams.hdr.ramdisk_size = initramfs_size as u32;
}
// Load the kernel command line into guest memory.
load_cmdline(
guest_memory,
GuestAddress(CMDLINE_START),
// Safe because the command line is valid.
cmdline,
)
.map_err(Error::KernelLoad)?;
// Write the boot parameters in the zeropage.
LinuxBootConfigurator::write_bootparams::<GuestMemoryMmap>(
&BootParams::new::<boot_params>(&bootparams, zero_page_addr),
guest_memory,
)
.map_err(Error::BootConfigure)?;
Ok(kernel_load)
}