Skip to content

Commit 2555127

Browse files
Guard the lower 1MB of memory
This commit fixes the review changes in #317. Most of the credit should go to Jason Couture <jasonc@alertr.info> Co-authored-by: Jason Couture <jasonc@alertr.info>
1 parent 1e52e4c commit 2555127

File tree

7 files changed

+168
-4
lines changed

7 files changed

+168
-4
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ members = [
2626
"tests/test_kernels/lto",
2727
"tests/test_kernels/ramdisk",
2828
"tests/test_kernels/min_stack",
29+
"tests/test_kernels/lower_memory_free",
2930
]
3031
exclude = ["examples/basic", "examples/test_framework"]
3132

@@ -67,6 +68,7 @@ test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target =
6768
test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" }
6869
test_kernel_config_file = { path = "tests/test_kernels/config_file", artifact = "bin", target = "x86_64-unknown-none" }
6970
test_kernel_min_stack = { path = "tests/test_kernels/min_stack", artifact = "bin", target = "x86_64-unknown-none" }
71+
test_kernel_lower_memory_free = { path = "tests/test_kernels/lower_memory_free", artifact = "bin", target = "x86_64-unknown-none" }
7072

7173
[profile.dev]
7274
panic = "abort"

common/src/legacy_memory_region.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ pub struct LegacyFrameAllocator<I, D> {
2828
memory_map: I,
2929
current_descriptor: Option<D>,
3030
next_frame: PhysFrame,
31+
min_frame: PhysFrame,
3132
}
3233

34+
/// Start address of the first frame that is not part of the lower 1MB of frames
35+
const LOWER_MEMORY_END_PAGE: u64 = 0x100_000;
36+
3337
impl<I, D> LegacyFrameAllocator<I, D>
3438
where
3539
I: ExactSizeIterator<Item = D> + Clone,
@@ -40,20 +44,26 @@ where
4044
/// Skips the frame at physical address zero to avoid potential problems. For example
4145
/// identity-mapping the frame at address zero is not valid in Rust, because Rust's `core`
4246
/// library assumes that references can never point to virtual address `0`.
47+
/// Also skips the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI).
4348
pub fn new(memory_map: I) -> Self {
4449
// skip frame 0 because the rust core library does not see 0 as a valid address
45-
let start_frame = PhysFrame::containing_address(PhysAddr::new(0x1000));
50+
// Also skip at least the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI).
51+
let start_frame = PhysFrame::containing_address(PhysAddr::new(LOWER_MEMORY_END_PAGE));
4652
Self::new_starting_at(start_frame, memory_map)
4753
}
4854

4955
/// Creates a new frame allocator based on the given legacy memory regions. Skips any frames
50-
/// before the given `frame`.
56+
/// before the given `frame` or `0x10000`(1MB) whichever is higher, there are use cases that require
57+
/// lower conventional memory access (Such as SMP SIPI).
5158
pub fn new_starting_at(frame: PhysFrame, memory_map: I) -> Self {
59+
let lower_mem_end = PhysFrame::containing_address(PhysAddr::new(LOWER_MEMORY_END_PAGE));
60+
let frame = core::cmp::max(frame, lower_mem_end);
5261
Self {
5362
original: memory_map.clone(),
5463
memory_map,
5564
current_descriptor: None,
5665
next_frame: frame,
66+
min_frame: frame,
5767
}
5868
}
5969

@@ -71,6 +81,7 @@ where
7181
if self.next_frame <= end_frame {
7282
let ret = self.next_frame;
7383
self.next_frame += 1;
84+
7485
Some(ret)
7586
} else {
7687
None
@@ -125,14 +136,44 @@ where
125136
let next_free = self.next_frame.start_address();
126137
let kind = match descriptor.kind() {
127138
MemoryRegionKind::Usable => {
128-
if end <= next_free {
139+
if end <= next_free && start >= self.min_frame.start_address() {
129140
MemoryRegionKind::Bootloader
130141
} else if descriptor.start() >= next_free {
131142
MemoryRegionKind::Usable
143+
} else if end <= self.min_frame.start_address() {
144+
// treat regions before min_frame as usable
145+
// this allows for access to the lower 1MB of frames
146+
MemoryRegionKind::Usable
147+
} else if end <= next_free {
148+
// part of the region is used -> add it separately
149+
// first part of the region is in lower 1MB, later part is used
150+
let free_region = MemoryRegion {
151+
start: descriptor.start().as_u64(),
152+
end: self.min_frame.start_address().as_u64(),
153+
kind: MemoryRegionKind::Usable,
154+
};
155+
Self::add_region(free_region, regions, &mut next_index);
156+
157+
// add bootloader part normally
158+
start = self.min_frame.start_address();
159+
MemoryRegionKind::Bootloader
132160
} else {
161+
if start < self.min_frame.start_address() {
162+
// part of the region is in lower memory
163+
let lower_region = MemoryRegion {
164+
start: start.as_u64(),
165+
end: self.min_frame.start_address().as_u64(),
166+
kind: MemoryRegionKind::Usable,
167+
};
168+
Self::add_region(lower_region, regions, &mut next_index);
169+
170+
start = self.min_frame.start_address();
171+
}
172+
133173
// part of the region is used -> add it separately
174+
// first part of the region is used, later part is free
134175
let used_region = MemoryRegion {
135-
start: descriptor.start().as_u64(),
176+
start: start.as_u64(),
136177
end: next_free.as_u64(),
137178
kind: MemoryRegionKind::Bootloader,
138179
};

tests/lower_memory_free.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use bootloader_test_runner::run_test_kernel;
2+
#[test]
3+
fn lower_memory_free() {
4+
run_test_kernel(env!(
5+
"CARGO_BIN_FILE_TEST_KERNEL_LOWER_MEMORY_FREE_lower_memory_free"
6+
));
7+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "test_kernel_lower_memory_free"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
bootloader_api = { path = "../../../api" }
8+
x86_64 = { version = "0.14.7", default-features = false, features = [
9+
"instructions",
10+
"inline_asm",
11+
] }
12+
uart_16550 = "0.2.10"
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#![no_std] // don't link the Rust standard library
2+
#![no_main] // disable all Rust-level entry points
3+
4+
use bootloader_api::{
5+
config::Mapping, entry_point, info::MemoryRegionKind, BootInfo, BootloaderConfig,
6+
};
7+
use test_kernel_lower_memory_free::{exit_qemu, QemuExitCode};
8+
9+
const LOWER_MEMORY_END_PAGE: u64 = 0x0010_0000;
10+
const WRITE_TEST_UNTIL: u64 = 0x4000_0000;
11+
12+
pub const BOOTLOADER_CONFIG: BootloaderConfig = {
13+
let mut config = BootloaderConfig::new_default();
14+
config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_4000_0000_0000));
15+
config
16+
};
17+
18+
entry_point!(kernel_main, config = &BOOTLOADER_CONFIG);
19+
20+
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
21+
use core::fmt::Write;
22+
use test_kernel_lower_memory_free::serial;
23+
24+
let phys_mem_offset = boot_info.physical_memory_offset.into_option().unwrap();
25+
26+
let mut count = 0;
27+
for region in boot_info.memory_regions.iter() {
28+
writeln!(
29+
serial(),
30+
"Region: {:016x}-{:016x} - {:?}",
31+
region.start,
32+
region.end,
33+
region.kind
34+
)
35+
.unwrap();
36+
if region.kind == MemoryRegionKind::Usable && region.start < LOWER_MEMORY_END_PAGE {
37+
let end = core::cmp::min(region.end, LOWER_MEMORY_END_PAGE);
38+
let pages = (end - region.start) / 4096;
39+
count += pages;
40+
}
41+
if region.kind == MemoryRegionKind::Usable && region.start < WRITE_TEST_UNTIL {
42+
let end = core::cmp::min(region.end, WRITE_TEST_UNTIL);
43+
// ensure region is actually writable
44+
let addr = phys_mem_offset + region.start;
45+
let size = end - region.start;
46+
unsafe {
47+
core::ptr::write_bytes(addr as *mut u8, 0xff, size as usize);
48+
}
49+
}
50+
}
51+
52+
writeln!(serial(), "Free lower memory page count: {}", count).unwrap();
53+
assert!(count > 0x10); // 0x10 chosen arbirarily, we need _some_ free conventional memory, but not all of it. Some, especially on BIOS, may be reserved for hardware.
54+
exit_qemu(QemuExitCode::Success);
55+
}
56+
57+
/// This function is called on panic.
58+
#[panic_handler]
59+
#[cfg(not(test))]
60+
fn panic(info: &core::panic::PanicInfo) -> ! {
61+
use core::fmt::Write;
62+
63+
let _ = writeln!(test_kernel_lower_memory_free::serial(), "PANIC: {}", info);
64+
exit_qemu(QemuExitCode::Failed);
65+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![no_std]
2+
3+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4+
#[repr(u32)]
5+
pub enum QemuExitCode {
6+
Success = 0x10,
7+
Failed = 0x11,
8+
}
9+
10+
pub fn exit_qemu(exit_code: QemuExitCode) -> ! {
11+
use x86_64::instructions::{nop, port::Port};
12+
13+
unsafe {
14+
let mut port = Port::new(0xf4);
15+
port.write(exit_code as u32);
16+
}
17+
18+
loop {
19+
nop();
20+
}
21+
}
22+
23+
pub fn serial() -> uart_16550::SerialPort {
24+
let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
25+
port.init();
26+
port
27+
}

0 commit comments

Comments
 (0)