Files
RedBear-OS/src/startup/memory.rs
T
bjorn3 ff65afd003 Don't depend on the stack setup by the bootloader
This way we can choose our own size for the stack and don't have to
identity map it manually. Also this way the bootloader doesn't have to
change the stack pointer right before calling into the kernel (which it
currently does in an unsound way)
2025-10-19 06:23:39 -06:00

461 lines
15 KiB
Rust

use crate::{
arch::{consts::KERNEL_OFFSET, paging::entry::EntryFlags, rmm::page_flags, CurrentRmmArch},
memory::PAGE_SIZE,
startup::{memory::BootloaderMemoryKind::Null, KernelArgs},
};
use core::{
cell::SyncUnsafeCell,
cmp::{max, min},
mem,
slice::{self, Iter},
};
use rmm::{
Arch, BumpAllocator, MemoryArea, PageFlags, PageMapper, PhysicalAddress, TableKind,
VirtualAddress, KILOBYTE, MEGABYTE,
};
// Keep synced with OsMemoryKind in bootloader
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u64)]
#[allow(dead_code)]
pub enum BootloaderMemoryKind {
Null = 0,
Free = 1,
Reclaim = 2,
Reserved = 3,
// These are local to kernel
Kernel = 0x100,
Device = 0x101,
IdentityMap = 0x102,
}
// Keep synced with OsMemoryEntry in bootloader
#[derive(Clone, Copy, Debug)]
#[repr(C, packed(8))]
struct BootloaderMemoryEntry {
pub base: u64,
pub size: u64,
pub kind: BootloaderMemoryKind,
}
#[derive(Clone, Copy, Debug)]
struct MemoryEntry {
pub start: usize,
pub end: usize,
pub kind: BootloaderMemoryKind,
}
impl MemoryEntry {
fn intersect(&self, other: &Self) -> Option<Self> {
let start = max(self.start, other.start);
let end = min(self.end, other.end);
if start < end {
Some(Self {
start,
end,
kind: self.kind,
})
} else {
None
}
}
fn combine(&self, other: &Self) -> Option<Self> {
if self.start <= other.end && self.end >= other.start {
Some(Self {
start: min(self.start, other.start),
end: max(self.end, other.end),
kind: self.kind,
})
} else {
None
}
}
}
struct MemoryMap {
entries: [MemoryEntry; 512],
size: usize,
}
impl MemoryMap {
fn register(&mut self, base: usize, size: usize, kind: BootloaderMemoryKind) {
if self.size >= self.entries.len() {
panic!("Early memory map overflow!");
}
let start = if kind == BootloaderMemoryKind::Free {
align_up(base)
} else {
align_down(base)
};
let end = base.saturating_add(size);
let end = if kind == BootloaderMemoryKind::Free {
align_down(end)
} else {
align_up(end)
};
if start < end {
self.entries[self.size] = MemoryEntry { start, end, kind };
self.size += 1;
}
}
fn iter(&self) -> Iter<MemoryEntry> {
self.entries[0..self.size].iter()
}
pub fn free(&self) -> impl Iterator<Item = &MemoryEntry> {
self.iter().filter(|x| x.kind == BootloaderMemoryKind::Free)
}
pub fn non_free(&self) -> impl Iterator<Item = &MemoryEntry> {
self.iter().filter(|x| x.kind != BootloaderMemoryKind::Free)
}
pub fn kernel(&self) -> Option<&MemoryEntry> {
self.iter().find(|x| x.kind == BootloaderMemoryKind::Kernel)
}
pub fn devices(&self) -> impl Iterator<Item = &MemoryEntry> {
self.iter()
.filter(|x| x.kind == BootloaderMemoryKind::Device)
}
pub fn identity_mapped(&self) -> impl Iterator<Item = &MemoryEntry> {
self.iter()
.filter(|x| x.kind == BootloaderMemoryKind::IdentityMap)
}
}
static MEMORY_MAP: SyncUnsafeCell<MemoryMap> = SyncUnsafeCell::new(MemoryMap {
entries: [MemoryEntry {
start: 0,
end: 0,
kind: BootloaderMemoryKind::Null,
}; 512],
size: 0,
});
fn align_up(x: usize) -> usize {
(x.saturating_add(PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE
}
fn align_down(x: usize) -> usize {
x / PAGE_SIZE * PAGE_SIZE
}
fn register_memory_from_kernel_args(args: &KernelArgs) {
register_bootloader_areas(args.areas_base as usize, args.areas_size as usize);
#[cfg(dtb)]
if let Some(dt) = args.dtb() {
crate::dtb::register_dev_memory_ranges(&dt);
}
register_memory_region(
args.kernel_base as usize,
args.kernel_size as usize,
BootloaderMemoryKind::Kernel,
);
register_memory_region(
args.env_base as usize,
args.env_size as usize,
BootloaderMemoryKind::IdentityMap,
);
register_memory_region(
args.hwdesc_base as usize,
args.hwdesc_size as usize,
BootloaderMemoryKind::IdentityMap,
);
register_memory_region(
args.bootstrap_base as usize,
args.bootstrap_size as usize,
BootloaderMemoryKind::IdentityMap,
);
}
pub fn register_memory_region(base: usize, size: usize, kind: BootloaderMemoryKind) {
if kind != Null && size != 0 {
debug!("Registering {:?} memory {:X} size {:X}", kind, base, size);
unsafe { (*MEMORY_MAP.get()).register(base, size, kind) }
}
}
fn register_bootloader_areas(areas_base: usize, areas_size: usize) {
let bootloader_areas = unsafe {
slice::from_raw_parts(
areas_base as *const BootloaderMemoryEntry,
areas_size / mem::size_of::<BootloaderMemoryEntry>(),
)
};
for bootloader_area in bootloader_areas.iter() {
register_memory_region(
bootloader_area.base as usize,
bootloader_area.size as usize,
bootloader_area.kind,
)
}
}
unsafe fn add_memory(areas: &mut [MemoryArea], area_i: &mut usize, mut area: MemoryEntry) {
unsafe {
for reservation in (*MEMORY_MAP.get()).non_free() {
if area.end > reservation.start && area.end <= reservation.end {
info!(
"Memory {:X}:{:X} overlaps with reservation {:X}:{:X}",
area.start, area.end, reservation.start, reservation.end
);
area.end = reservation.start;
}
if area.start >= area.end {
return;
}
if area.start >= reservation.start && area.start < reservation.end {
info!(
"Memory {:X}:{:X} overlaps with reservation {:X}:{:X}",
area.start, area.end, reservation.start, reservation.end
);
area.start = reservation.end;
}
if area.start >= area.end {
return;
}
if area.start <= reservation.start && area.end > reservation.start {
info!(
"Memory {:X}:{:X} contains reservation {:X}:{:X}",
area.start, area.end, reservation.start, reservation.end
);
debug_assert!(area.start < reservation.start && reservation.end < area.end,
"Should've contained reservation entirely: memory block {:X}:{:X} reservation {:X}:{:X}",
area.start, area.end,
reservation.start, reservation.end
);
// recurse on first part of split memory block
add_memory(
areas,
area_i,
MemoryEntry {
end: reservation.start,
..area
},
);
// and continue with the second part
area.start = reservation.end;
}
debug_assert!(
area.intersect(reservation).is_none(),
"Intersects with reservation! memory block {:X}:{:X} reservation {:X}:{:X}",
area.start,
area.end,
reservation.start,
reservation.end
);
debug_assert!(
area.start < area.end,
"Empty memory block {:X}:{:X}",
area.start,
area.end
);
}
// Combine overlapping memory areas
let mut other_i = 0;
while other_i < *area_i {
let other = &areas[other_i];
let other = MemoryEntry {
start: other.base.data(),
end: other.base.data().saturating_add(other.size),
kind: BootloaderMemoryKind::Free,
};
if let Some(union) = area.combine(&other) {
debug!(
"{:X}:{:X} overlaps with area {:X}:{:X}, combining into {:X}:{:X}",
area.start, area.end, other.start, other.end, union.start, union.end
);
area = union;
*area_i -= 1; // delete the original memory chunk
areas[other_i] = areas[*area_i];
} else {
other_i += 1;
}
}
areas[*area_i].base = PhysicalAddress::new(area.start);
areas[*area_i].size = area.end - area.start;
*area_i += 1;
}
}
unsafe fn map_memory<A: Arch>(areas: &[MemoryArea], mut bump_allocator: &mut BumpAllocator<A>) {
unsafe {
let mut mapper = PageMapper::<A, _>::create(TableKind::Kernel, &mut bump_allocator)
.expect("failed to create Mapper");
if cfg!(target_arch = "x86") {
// Pre-allocate all kernel PD entries so that when the page table is copied,
// these entries are synced between processes
for i in 512..1024 {
use rmm::{FrameAllocator, PageEntry};
let phys = mapper
.allocator_mut()
.allocate_one()
.expect("failed to map page table");
let flags = A::ENTRY_FLAG_READWRITE | A::ENTRY_FLAG_DEFAULT_TABLE;
mapper
.table()
.set_entry(i, PageEntry::new(phys.data(), flags));
}
}
// Map all physical areas at PHYS_OFFSET
for area in areas.iter() {
for i in 0..area.size / PAGE_SIZE {
let phys = area.base.add(i * PAGE_SIZE);
let virt = A::phys_to_virt(phys);
let flags = page_flags::<A>(virt);
let flush = mapper
.map_phys(virt, phys, flags)
.expect("failed to map frame");
flush.ignore(); // Not the active table
}
}
let kernel_area = (*MEMORY_MAP.get()).kernel().unwrap();
let kernel_base = kernel_area.start;
let kernel_size = kernel_area.end - kernel_area.start;
// Map kernel at KERNEL_OFFSET and identity map too
for i in 0..kernel_size / A::PAGE_SIZE {
let phys = PhysicalAddress::new(kernel_base + i * PAGE_SIZE);
let virt = VirtualAddress::new(KERNEL_OFFSET + i * PAGE_SIZE);
let flags = page_flags::<A>(virt);
let flush = mapper
.map_phys(virt, phys, flags)
.expect("failed to map frame");
flush.ignore(); // Not the active table
let virt = A::phys_to_virt(phys);
let flush = mapper
.map_phys(virt, phys, flags)
.expect("failed to map frame");
flush.ignore(); // Not the active table
}
for area in (*MEMORY_MAP.get()).identity_mapped() {
let base = area.start;
let size = area.end - area.start;
for i in 0..size / PAGE_SIZE {
let phys = PhysicalAddress::new(base + i * PAGE_SIZE);
let virt = A::phys_to_virt(phys);
let flags = page_flags::<A>(virt);
let flush = mapper
.map_phys(virt, phys, flags)
.expect("failed to map frame");
flush.ignore(); // Not the active table
}
}
//map dev mem
for area in (*MEMORY_MAP.get()).devices() {
let base = area.start;
let size = area.end - area.start;
for i in 0..size / PAGE_SIZE {
let phys = PhysicalAddress::new(base + i * PAGE_SIZE);
let virt = A::phys_to_virt(phys);
// use the same mair_el1 value with bootloader,
// mair_el1 == 0x00000000000044FF
// set mem_attr == device memory
let flags = page_flags::<A>(virt).custom_flag(EntryFlags::DEV_MEM.bits(), true);
let flush = mapper
.map_phys(virt, phys, flags)
.expect("failed to map frame");
flush.ignore(); // Not the active table
}
}
// Ensure graphical debug region remains paged
{
use crate::devices::graphical_debug::FRAMEBUFFER;
let (phys, virt, size) = *FRAMEBUFFER.lock();
let pages = size.div_ceil(PAGE_SIZE);
for i in 0..pages {
let phys = PhysicalAddress::new(phys + i * PAGE_SIZE);
let virt = VirtualAddress::new(virt + i * PAGE_SIZE);
let flags = PageFlags::new().write(true).write_combining(true);
let flush = mapper
.map_phys(virt, phys, flags)
.expect("failed to map frame");
flush.ignore(); // Not the active table
}
}
debug!("Table: {:X}", mapper.table().phys().data());
for i in 0..A::PAGE_ENTRIES {
if let Some(entry) = mapper.table().entry(i) {
if entry.present() {
debug!("{}: {:X}", i, entry.data());
}
}
}
// Use the new table
mapper.make_current();
}
}
pub unsafe fn init(args: &KernelArgs, low_limit: Option<usize>, high_limit: Option<usize>) {
register_memory_from_kernel_args(args);
unsafe {
let physmem_limit = MemoryEntry {
start: align_up(low_limit.unwrap_or(0)),
end: align_down(high_limit.unwrap_or(usize::MAX)),
kind: BootloaderMemoryKind::Free,
};
let areas = &mut *crate::memory::AREAS.get();
let mut area_i = 0;
// Copy initial memory map, and page align it
for area in (*MEMORY_MAP.get()).free() {
debug!("{:X}:{:X}", area.start, area.end);
if let Some(area) = area.intersect(&physmem_limit) {
add_memory(areas, &mut area_i, area);
}
}
areas[..area_i].sort_unstable_by_key(|area| area.base);
crate::memory::AREA_COUNT.get().write(area_i as u16);
// free memory map in now ready
let areas = crate::memory::areas();
// First, calculate how much memory we have
let mut size = 0;
for area in areas.iter() {
if area.size > 0 {
debug!("{:X?}", area);
size += area.size;
}
}
info!("Memory: {} MB", size.div_ceil(MEGABYTE));
// Create a basic allocator for the first pages
let mut bump_allocator = BumpAllocator::<CurrentRmmArch>::new(areas, 0);
map_memory(areas, &mut bump_allocator);
// Create the physical memory map
let offset = bump_allocator.offset();
info!("Permanently used: {} KB", offset.div_ceil(KILOBYTE));
crate::memory::init_mm(bump_allocator);
}
}