-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
mod.rs
278 lines (242 loc) · 9.37 KB
/
mod.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions 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 THIRD-PARTY file.
mod gdt;
/// Contains logic for setting up Advanced Programmable Interrupt Controller (local version).
pub mod interrupts;
/// Layout for the x86_64 system.
pub mod layout;
mod mptable;
/// Logic for configuring x86_64 registers.
pub mod regs;
use std::mem;
use arch_gen::x86::bootparam::{boot_params, E820_RAM};
use memory_model::{DataInit, GuestAddress, GuestMemory};
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `DataInit`) where:
// * the type that is implementing the trait is foreign or
// * all of the parameters being passed to the trait (if there are any) are also foreign
// is prohibited.
#[derive(Copy, Clone)]
struct BootParamsWrapper(boot_params);
// It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints).
unsafe impl DataInit for BootParamsWrapper {}
/// Errors thrown while configuring x86_64 system.
#[derive(Debug, PartialEq)]
pub enum Error {
/// Invalid e820 setup params.
E820Configuration,
/// Error writing MP table to memory.
MpTableSetup(mptable::Error),
/// The zero page extends past the end of guest_mem.
ZeroPagePastRamEnd,
/// Error writing the zero page of guest memory.
ZeroPageSetup,
}
// Where BIOS/VGA magic would live on a real PC.
const EBDA_START: u64 = 0x9fc00;
const FIRST_ADDR_PAST_32BITS: usize = (1 << 32);
const MEM_32BIT_GAP_SIZE: usize = (768 << 20);
/// Returns a Vec of the valid memory addresses.
/// These should be used to configure the GuestMemory structure for the platform.
/// For x86_64 all addresses are valid from the start of the kernel except a
/// carve out at the end of 32bit address space.
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize)> {
let memory_gap_start = GuestAddress(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE);
let memory_gap_end = GuestAddress(FIRST_ADDR_PAST_32BITS);
let requested_memory_size = GuestAddress(size);
let mut regions = Vec::new();
// case1: guest memory fits before the gap
if requested_memory_size <= memory_gap_start {
regions.push((GuestAddress(0), size));
// case2: guest memory extends beyond the gap
} else {
// push memory before the gap
regions.push((GuestAddress(0), memory_gap_start.offset()));
regions.push((
memory_gap_end,
requested_memory_size.offset_from(memory_gap_start),
));
}
regions
}
/// X86 specific memory hole/memory mapped devices/reserved area.
pub fn get_32bit_gap_start() -> usize {
FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE
}
/// Returns the memory address where the kernel could be loaded.
pub fn get_kernel_start() -> usize {
layout::HIMEM_START
}
/// Configures the system and should be called once per vm before starting vcpu threads.
///
/// # Arguments
///
/// * `guest_mem` - The memory to be used by the guest.
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
pub fn configure_system(
guest_mem: &GuestMemory,
cmdline_addr: GuestAddress,
cmdline_size: usize,
num_cpus: u8,
) -> super::Result<()> {
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
const KERNEL_LOADER_OTHER: u8 = 0xff;
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x0100_0000; // Must be non-zero.
let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS);
let end_32bit_gap_start = GuestAddress(get_32bit_gap_start());
let himem_start = GuestAddress(layout::HIMEM_START);
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
params.0.hdr.header = KERNEL_HDR_MAGIC;
params.0.hdr.cmd_line_ptr = cmdline_addr.offset() as u32;
params.0.hdr.cmdline_size = cmdline_size as u32;
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
add_e820_entry(&mut params.0, 0, EBDA_START, E820_RAM)?;
let mem_end = guest_mem.end_addr();
if mem_end < end_32bit_gap_start {
add_e820_entry(
&mut params.0,
himem_start.offset() as u64,
mem_end.offset_from(himem_start) as u64,
E820_RAM,
)?;
} else {
add_e820_entry(
&mut params.0,
himem_start.offset() as u64,
end_32bit_gap_start.offset_from(himem_start) as u64,
E820_RAM,
)?;
if mem_end > first_addr_past_32bits {
add_e820_entry(
&mut params.0,
first_addr_past_32bits.offset() as u64,
mem_end.offset_from(first_addr_past_32bits) as u64,
E820_RAM,
)?;
}
}
let zero_page_addr = GuestAddress(layout::ZERO_PAGE_START);
guest_mem
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
.ok_or(Error::ZeroPagePastRamEnd)?;
guest_mem
.write_obj_at_addr(params, zero_page_addr)
.map_err(|_| Error::ZeroPageSetup)?;
Ok(())
}
/// Add an e820 region to the e820 map.
/// Returns Ok(()) if successful, or an error if there is no space left in the map.
fn add_e820_entry(
params: &mut boot_params,
addr: u64,
size: u64,
mem_type: u32,
) -> super::Result<()> {
if params.e820_entries >= params.e820_map.len() as u8 {
return Err(Error::E820Configuration);
}
params.e820_map[params.e820_entries as usize].addr = addr;
params.e820_map[params.e820_entries as usize].size = size;
params.e820_map[params.e820_entries as usize].type_ = mem_type;
params.e820_entries += 1;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use arch_gen::x86::bootparam::e820entry;
#[test]
fn regions_lt_4gb() {
let regions = arch_memory_regions(1usize << 29);
assert_eq!(1, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(1usize << 29, regions[0].1);
}
#[test]
fn regions_gt_4gb() {
let regions = arch_memory_regions((1usize << 32) + 0x8000);
assert_eq!(2, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(GuestAddress(1usize << 32), regions[1].0);
}
#[test]
fn test_32bit_gap() {
assert_eq!(
get_32bit_gap_start(),
FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE
);
}
#[test]
fn test_system_configuration() {
let no_vcpus = 4;
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let config_err = configure_system(&gm, GuestAddress(0), 0, 1);
assert!(config_err.is_err());
assert_eq!(
config_err.unwrap_err(),
super::Error::MpTableSetup(mptable::Error::NotEnoughMemory)
);
// Now assigning some memory that falls before the 32bit memory hole.
let mem_size = 128 << 20;
let arch_mem_regions = arch_memory_regions(mem_size);
let gm = GuestMemory::new(&arch_mem_regions).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus).unwrap();
// Now assigning some memory that is equal to the start of the 32bit memory hole.
let mem_size = 3328 << 20;
let arch_mem_regions = arch_memory_regions(mem_size);
let gm = GuestMemory::new(&arch_mem_regions).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus).unwrap();
// Now assigning some memory that falls after the 32bit memory hole.
let mem_size = 3330 << 20;
let arch_mem_regions = arch_memory_regions(mem_size);
let gm = GuestMemory::new(&arch_mem_regions).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus).unwrap();
}
#[test]
fn test_add_e820_entry() {
let e820_map = [(e820entry {
addr: 0x1,
size: 4,
type_: 1,
}); 128];
let expected_params = boot_params {
e820_map,
e820_entries: 1,
..Default::default()
};
let mut params: boot_params = Default::default();
add_e820_entry(
&mut params,
e820_map[0].addr,
e820_map[0].size,
e820_map[0].type_,
)
.unwrap();
assert_eq!(
format!("{:?}", params.e820_map[0]),
format!("{:?}", expected_params.e820_map[0])
);
assert_eq!(params.e820_entries, expected_params.e820_entries);
// Exercise the scenario where the field storing the length of the e820 entry table is
// is bigger than the allocated memory.
params.e820_entries = params.e820_map.len() as u8 + 1;
assert!(add_e820_entry(
&mut params,
e820_map[0].addr,
e820_map[0].size,
e820_map[0].type_
)
.is_err());
}
}