Compare commits

...

79 Commits

Author SHA1 Message Date
Philipp Oppermann
c6dd37dcc5 Turn page of old P4 into guard page 2017-11-19 11:44:50 +01:00
Philipp Oppermann
2a3ce863fd Enable write protect bit to improve safety 2017-11-19 11:44:50 +01:00
Philipp Oppermann
ffaddeb84e Fix boot loop by enabling the NXE bit in the EFER register 2017-11-19 11:44:50 +01:00
Philipp Oppermann
0ce2b46ad1 Use the correct entry flags for kernel sections 2017-11-19 11:44:50 +01:00
Philipp Oppermann
11993b7e15 Also identity map the multiboot info structure 2017-11-19 11:44:50 +01:00
Philipp Oppermann
fdacfb24f0 Fix boot loop by identity mapping the vga buffer 2017-11-19 11:44:50 +01:00
Philipp Oppermann
564c6b64e9 Switch to the new page table after mapping all sections 2017-11-19 11:44:50 +01:00
Philipp Oppermann
fa3b572d68 Only print kernel/multiboot start/end in rust_main 2017-11-19 11:44:50 +01:00
Philipp Oppermann
40f1732ccb Call remap_the_kernel from rust_main 2017-11-19 11:44:49 +01:00
Philipp Oppermann
5d898d8474 Page align all sections as they will be individually mapped 2017-11-19 11:44:49 +01:00
Philipp Oppermann
e029eabe18 Add a remap_the_kernel function 2017-11-19 11:44:49 +01:00
Philipp Oppermann
60d7c736a5 Add a (now safe) with method 2017-11-19 11:44:49 +01:00
Philipp Oppermann
2f30b0f7cf Create a new ActivePageTable struct that derefs to Mapper 2017-11-19 11:44:49 +01:00
Philipp Oppermann
ce9c4d6e43 Refactor: Move ActivePageTable to new mapper submodule and rename to Mapper 2017-11-19 11:44:49 +01:00
Philipp Oppermann
4160cb6d7e Begin creating an ActivePageTable::with function 2017-11-19 11:42:33 +01:00
Philipp Oppermann
b2e79752fd Fix InactivePageTable::new function using a temporary page 2017-11-19 11:42:33 +01:00
Philipp Oppermann
11ae7e8aeb Create a temporary_page module 2017-11-19 11:42:33 +01:00
Philipp Oppermann
1737f48284 Create a InactivePageTable struct 2017-11-19 11:42:33 +01:00
Philipp Oppermann
1f1f6c62da Derive Copy and Clone for Page and add a Frame::clone method 2017-11-19 11:42:33 +01:00
Philipp Oppermann
bb3f17c0b9 Update Readme for “Remap the Kernel” post 2017-11-19 11:42:33 +01:00
Philipp Oppermann
1007c5157d Remove paging testing code again 2017-11-19 11:42:16 +01:00
Philipp Oppermann
55e602dc61 Fix unmap function by flushing the TLB 2017-11-19 11:42:16 +01:00
Philipp Oppermann
eb5c9afa51 Add a dependency on the x86_64 crate 2017-11-19 11:42:16 +01:00
Philipp Oppermann
0ee4ed738a Test the unmap function (it doesn't work correctly) 2017-11-19 11:42:16 +01:00
Philipp Oppermann
b49cb6986c Test the map_to function 2017-11-19 11:42:16 +01:00
Philipp Oppermann
15c9f43622 Add an (unfinished) unmap method 2017-11-19 11:42:16 +01:00
Philipp Oppermann
3696c7bacb Add map and identity_map functions for convenience 2017-11-19 11:42:16 +01:00
Philipp Oppermann
bb473c7907 Make translate and map_to safe by making them methods of a new ActivePageTable struct 2017-11-19 11:42:16 +01:00
Philipp Oppermann
dedf6e8959 Add a map_to function 2017-11-19 11:42:16 +01:00
Philipp Oppermann
83d56aa9de Add translate and translate_page functions 2017-11-19 11:42:16 +01:00
Philipp Oppermann
a7170a4a44 Use type system tricks to make next_table methods safe 2017-11-19 11:42:16 +01:00
Philipp Oppermann
0874625269 Add methods to retrieve a (mutual) reference to the next table 2017-11-19 10:39:17 +01:00
Philipp Oppermann
3706331a43 Add a P4 constant and a Table::next_table_address method 2017-11-19 10:39:17 +01:00
Philipp Oppermann
8fcea7951b Recursive map the P4 table 2017-11-19 10:39:17 +01:00
Philipp Oppermann
e50d70c02e Add a Table::zero method to clear all entries 2017-11-19 10:39:17 +01:00
Philipp Oppermann
2b7fa410fb Implement the Index and IndexMut traits for Table 2017-11-19 10:39:17 +01:00
Philipp Oppermann
62d655fbdd Create a memory::paging::table submodule with a Table struct 2017-11-19 10:39:17 +01:00
Philipp Oppermann
fe464463eb Add pointed_frame and set methods to Entry 2017-11-19 10:39:17 +01:00
Philipp Oppermann
148d506629 Use bitflags to create an EntryFlags type 2017-11-19 10:39:17 +01:00
Philipp Oppermann
f957f2ecde Add a dependency on the bitflags crate 2017-11-19 10:39:17 +01:00
Philipp Oppermann
c4c27c10e6 Create a memory::paging::entry submodule with an Entry struct 2017-11-19 10:39:17 +01:00
Philipp Oppermann
c7c02d7dca Create a memory::paging module with a Page struct 2017-11-19 10:39:17 +01:00
Philipp Oppermann
9f84e37e1b Update Readme for “Page Tables” post 2017-11-19 10:39:17 +01:00
Philipp Oppermann
9f1a69cafa Create a new AreaFrameAllocator and allocate maximum number of frames 2017-11-19 10:39:13 +01:00
Philipp Oppermann
edb2e693da Create an AreaFrameAllocator 2017-11-19 10:39:13 +01:00
Philipp Oppermann
84c337e6f0 Create a memory module with a Frame struct and FrameAllocator trait 2017-11-19 10:39:13 +01:00
Philipp Oppermann
98d97703f4 Calculate start and end of kernel and multiboot struct 2017-11-19 10:39:13 +01:00
Philipp Oppermann
402ec77bc0 Reduce number of ELF section by merging .text.* (etc.) sections together 2017-11-19 10:39:13 +01:00
Philipp Oppermann
9a86f60835 Print kernel ELF sections 2017-11-19 10:39:13 +01:00
Philipp Oppermann
cbc9e112bd Print a proper message on panics 2017-11-19 10:39:13 +01:00
Philipp Oppermann
f231f2c7f2 Print available memory areas 2017-11-19 10:39:13 +01:00
Philipp Oppermann
5f65e1d31f Add dependency on multiboot2 crate 2017-11-19 10:39:13 +01:00
Philipp Oppermann
28ce5310c8 Pass address of multiboot info structure to rust_main 2017-11-19 10:39:13 +01:00
Philipp Oppermann
08a4e795a4 Increase stack size to 16k 2017-11-19 10:39:13 +01:00
Philipp Oppermann
8040f8d565 Update Readme for “Allocating Frames” post 2017-11-19 10:39:13 +01:00
Philipp Oppermann
9f448fbe0e Avoid deadlock on nested print! invokation 2017-11-19 10:39:05 +01:00
Philipp Oppermann
59b8133396 Add print! and println! macros and a clear_screen function 2017-11-19 10:39:05 +01:00
Philipp Oppermann
40aed4fa0f Create a static WRITER protected by a spinlock 2017-11-19 10:39:05 +01:00
Philipp Oppermann
f24c7bc322 Implement the new_line method 2017-11-19 10:31:00 +01:00
Philipp Oppermann
5e0ccd5aa5 Implement the fmt::Write trait and print something with the write! macro 2017-11-19 10:31:00 +01:00
Philipp Oppermann
578717a9b8 Add a write_str method and print “Hello!” 2017-11-19 10:31:00 +01:00
Philipp Oppermann
0ed21fb943 Use volatile writes for printing to screen 2017-11-19 10:31:00 +01:00
Philipp Oppermann
6aa3f67331 Add a print_something function to print an H in the lower left 2017-11-19 10:30:48 +01:00
Philipp Oppermann
46d47f8d2e Create a Writer struct with a write_byte function 2017-11-19 10:30:48 +01:00
Philipp Oppermann
afc2c26a9d Create a vga_buffer module 2017-11-19 10:30:48 +01:00
Philipp Oppermann
db9a19b38a Update Readme for “Printing to Screen” post 2017-11-19 10:30:48 +01:00
Philipp Oppermann
bef5f13560 Print a “Hello World!” in Rust 2017-11-19 10:30:36 +01:00
Philipp Oppermann
a1743eb3dd Set the panic strategy to abort to fix _Unwind_Resume errors 2017-11-19 10:30:36 +01:00
Philipp Oppermann
13cffc3319 Add target directory to .gitignore 2017-11-19 10:30:36 +01:00
Philipp Oppermann
ff623a90e3 Use --gc-sections to remove unused program sections 2017-11-19 10:30:36 +01:00
Philipp Oppermann
5d8758df59 Add a dependency on rlibc 2017-11-18 11:10:17 +01:00
Philipp Oppermann
ca2ccc31aa Call the rust_main function from assembly 2017-11-18 11:10:17 +01:00
Philipp Oppermann
8dd179a6f4 Adjust the Makefile to build and link Rust code 2017-11-18 11:10:17 +01:00
Philipp Oppermann
9f578640d8 Add a target spcification that disables SSE and the red zone 2017-11-18 11:10:17 +01:00
Philipp Oppermann
8e5a85ece4 Create a new no_std cargo project 2017-05-12 21:31:28 +02:00
Philipp Oppermann
1c88c7f945 Update Readme for “Set Up Rust” post 2017-05-12 21:31:28 +02:00
Philipp Oppermann
9136a7b66f Zero all segment registers 2017-05-12 21:31:28 +02:00
Philipp Oppermann
415d27814f Do a far jump to long mode and print OKAY 2017-05-12 21:31:28 +02:00
Philipp Oppermann
e684bfd262 Create and load a 64-bit GDT 2017-05-12 21:31:28 +02:00
17 changed files with 1080 additions and 18 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
build
target

15
Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "blog_os"
version = "0.1.0"
authors = ["Philipp Oppermann <dev@phil-opp.com>"]
[lib]
crate-type = ["staticlib"]
[dependencies]
rlibc = "1.0"
volatile = "0.1.0"
spin = "0.4.5"
multiboot2 = "0.1.0"
bitflags = "0.7.0"
x86_64 = "0.1.2"

View File

@@ -1,6 +1,8 @@
arch ?= x86_64
kernel := build/kernel-$(arch).bin
iso := build/os-$(arch).iso
target ?= $(arch)-blog_os
rust_os := target/$(target)/debug/libblog_os.a
linker_script := src/arch/$(arch)/linker.ld
grub_cfg := src/arch/$(arch)/grub.cfg
@@ -8,7 +10,7 @@ assembly_source_files := $(wildcard src/arch/$(arch)/*.asm)
assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \
build/arch/$(arch)/%.o, $(assembly_source_files))
.PHONY: all clean run iso
.PHONY: all clean run iso kernel
all: $(kernel)
@@ -27,8 +29,12 @@ $(iso): $(kernel) $(grub_cfg)
@grub-mkrescue -o $(iso) build/isofiles 2> /dev/null
@rm -r build/isofiles
$(kernel): $(assembly_object_files) $(linker_script)
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files)
$(kernel): kernel $(rust_os) $(assembly_object_files) $(linker_script)
@ld -n --gc-sections -T $(linker_script) -o $(kernel) \
$(assembly_object_files) $(rust_os)
kernel:
@xargo build --target $(target)
# compile assembly files
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm

View File

@@ -1,12 +1,12 @@
# Blog OS (Entering Longmode)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_2)](https://travis-ci.org/phil-opp/blog_os/branches)
# Blog OS (Remap the Kernel)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_7)](https://travis-ci.org/phil-opp/blog_os/branches)
This repository contains the source code for the [Entering Longmode](http://os.phil-opp.com/entering-longmode.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
This repository contains the source code for the [Remap the Kernel](http://os.phil-opp.com/remap-the-kernel.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
**Check out the [master branch](https://github.com/phil-opp/blog_os) for more information.**
## Building
You need to have `nasm`, `grub-mkrescue`, `xorriso`, and `qemu` installed. Then you can run it using `make run`.
You need to have `nasm`, `grub-mkrescue`, `xorriso`, `qemu`, and a nightly Rust compiler installed. Then you can run it using `make run`.
Please file an issue if you have any problems.

View File

@@ -1,9 +1,11 @@
global start
extern long_mode_start
section .text
bits 32
start:
mov esp, stack_top
mov edi, ebx ; move Multiboot info pointer to edi
call check_multiboot
call check_cpuid
@@ -12,6 +14,11 @@ start:
call set_up_page_tables
call enable_paging
; load the 64-bit GDT
lgdt [gdt64.pointer]
jmp gdt64.code:long_mode_start
; print `OK` to screen
mov dword [0xb8000], 0x2f4b2f4f
hlt
@@ -78,6 +85,11 @@ check_long_mode:
jmp error
set_up_page_tables:
; map P4 table recursively
mov eax, p4_table
or eax, 0b11 ; present + writable
mov [p4_table + 511 * 8], eax
; map first P4 entry to P3 table
mov eax, p3_table
or eax, 0b11 ; present + writable
@@ -145,5 +157,14 @@ p3_table:
p2_table:
resb 4096
stack_bottom:
resb 64
resb 4096 * 4
stack_top:
section .rodata
gdt64:
dq 0 ; zero entry
.code: equ $ - gdt64 ; new
dq (1<<43) | (1<<44) | (1<<47) | (1<<53) ; code segment
.pointer:
dw $ - gdt64 - 1
dq gdt64

View File

@@ -1,16 +1,53 @@
ENTRY(start)
SECTIONS {
. = 1M;
. = 1M;
.boot :
{
/* ensure that the multiboot header is at the beginning */
*(.multiboot_header)
}
.rodata :
{
/* ensure that the multiboot header is at the beginning */
KEEP(*(.multiboot_header))
*(.rodata .rodata.*)
. = ALIGN(4K);
}
.text :
{
*(.text)
}
.text :
{
*(.text .text.*)
. = ALIGN(4K);
}
.data :
{
*(.data .data.*)
. = ALIGN(4K);
}
.bss :
{
*(.bss .bss.*)
. = ALIGN(4K);
}
.got :
{
*(.got)
. = ALIGN(4K);
}
.got.plt :
{
*(.got.plt)
. = ALIGN(4K);
}
.data.rel.ro : ALIGN(4K) {
*(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*)
. = ALIGN(4K);
}
.gcc_except_table : ALIGN(4K) {
*(.gcc_except_table)
. = ALIGN(4K);
}
}

View File

@@ -0,0 +1,22 @@
global long_mode_start
extern rust_main
section .text
bits 64
long_mode_start:
; load 0 into all data segment registers
mov ax, 0
mov ss, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; call the rust main
extern rust_main
call rust_main
; print `OKAY` to screen
mov rax, 0x2f592f412f4b2f4f
mov qword [0xb8000], rax
hlt

80
src/lib.rs Normal file
View File

@@ -0,0 +1,80 @@
#![feature(lang_items)]
#![feature(const_fn)]
#![feature(const_unique_new)]
#![feature(unique)]
#![no_std]
extern crate rlibc;
extern crate volatile;
extern crate spin;
extern crate multiboot2;
#[macro_use]
extern crate bitflags;
extern crate x86_64;
#[macro_use]
mod vga_buffer;
mod memory;
#[no_mangle]
pub extern fn rust_main(multiboot_information_address: usize) {
use memory::FrameAllocator;
vga_buffer::clear_screen();
println!("Hello World{}", "!");
let boot_info = unsafe{ multiboot2::load(multiboot_information_address) };
let memory_map_tag = boot_info.memory_map_tag()
.expect("Memory map tag required");
let elf_sections_tag = boot_info.elf_sections_tag()
.expect("Elf sections tag required");
let kernel_start = elf_sections_tag.sections().map(|s| s.addr)
.min().unwrap();
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size)
.max().unwrap();
let multiboot_start = multiboot_information_address;
let multiboot_end = multiboot_start + (boot_info.total_size as usize);
println!("kernel start: 0x{:x}, kernel end: 0x{:x}",
kernel_start, kernel_end);
println!("multiboot start: 0x{:x}, multiboot end: 0x{:x}",
multiboot_start, multiboot_end);
let mut frame_allocator = memory::AreaFrameAllocator::new(
kernel_start as usize, kernel_end as usize, multiboot_start,
multiboot_end, memory_map_tag.memory_areas());
enable_nxe_bit();
enable_write_protect_bit();
memory::remap_the_kernel(&mut frame_allocator, boot_info);
println!("It did not crash!");
loop {}
}
fn enable_nxe_bit() {
use x86_64::registers::msr::{IA32_EFER, rdmsr, wrmsr};
let nxe_bit = 1 << 11;
unsafe {
let efer = rdmsr(IA32_EFER);
wrmsr(IA32_EFER, efer | nxe_bit);
}
}
fn enable_write_protect_bit() {
use x86_64::registers::control_regs::{cr0, cr0_write, Cr0};
unsafe { cr0_write(cr0() | Cr0::WRITE_PROTECT) };
}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn panic_fmt(fmt: core::fmt::Arguments, file: &'static str, line: u32) -> ! {
println!("\n\nPANIC in {} at line {}:", file, line);
println!(" {}", fmt);
loop{}
}

View File

@@ -0,0 +1,88 @@
use memory::{Frame, FrameAllocator};
use multiboot2::{MemoryAreaIter, MemoryArea};
pub struct AreaFrameAllocator {
next_free_frame: Frame,
current_area: Option<&'static MemoryArea>,
areas: MemoryAreaIter,
kernel_start: Frame,
kernel_end: Frame,
multiboot_start: Frame,
multiboot_end: Frame,
}
impl FrameAllocator for AreaFrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame> {
if let Some(area) = self.current_area {
// "Clone" the frame to return it if it's free. Frame doesn't
// implement Clone, but we can construct an identical frame.
let frame = Frame{ number: self.next_free_frame.number };
// the last frame of the current area
let current_area_last_frame = {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize)
};
if frame > current_area_last_frame {
// all frames of current area are used, switch to next area
self.choose_next_area();
} else if frame >= self.kernel_start && frame <= self.kernel_end {
// `frame` is used by the kernel
self.next_free_frame = Frame {
number: self.kernel_end.number + 1
};
} else if frame >= self.multiboot_start && frame <= self.multiboot_end {
// `frame` is used by the multiboot information structure
self.next_free_frame = Frame {
number: self.multiboot_end.number + 1
};
} else {
// frame is unused, increment `next_free_frame` and return it
self.next_free_frame.number += 1;
return Some(frame);
}
// `frame` was not valid, try it again with the updated `next_free_frame`
self.allocate_frame()
} else {
None // no free frames left
}
}
fn deallocate_frame(&mut self, _frame: Frame) {
unimplemented!()
}
}
impl AreaFrameAllocator {
pub fn new(kernel_start: usize, kernel_end: usize,
multiboot_start: usize, multiboot_end: usize,
memory_areas: MemoryAreaIter) -> AreaFrameAllocator
{
let mut allocator = AreaFrameAllocator {
next_free_frame: Frame::containing_address(0),
current_area: None,
areas: memory_areas,
kernel_start: Frame::containing_address(kernel_start),
kernel_end: Frame::containing_address(kernel_end),
multiboot_start: Frame::containing_address(multiboot_start),
multiboot_end: Frame::containing_address(multiboot_end),
};
allocator.choose_next_area();
allocator
}
fn choose_next_area(&mut self) {
self.current_area = self.areas.clone().filter(|area| {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize) >= self.next_free_frame
}).min_by_key(|area| area.base_addr);
if let Some(area) = self.current_area {
let start_frame = Frame::containing_address(area.base_addr as usize);
if self.next_free_frame < start_frame {
self.next_free_frame = start_frame;
}
}
}
}

58
src/memory/mod.rs Normal file
View File

@@ -0,0 +1,58 @@
pub use self::area_frame_allocator::AreaFrameAllocator;
pub use self::paging::remap_the_kernel;
use self::paging::PhysicalAddress;
mod area_frame_allocator;
mod paging;
pub const PAGE_SIZE: usize = 4096;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Frame {
number: usize,
}
impl Frame {
fn containing_address(address: usize) -> Frame {
Frame{ number: address / PAGE_SIZE }
}
fn start_address(&self) -> PhysicalAddress {
self.number * PAGE_SIZE
}
fn clone(&self) -> Frame {
Frame { number: self.number }
}
fn range_inclusive(start: Frame, end: Frame) -> FrameIter {
FrameIter {
start: start,
end: end,
}
}
}
struct FrameIter {
start: Frame,
end: Frame,
}
impl Iterator for FrameIter {
type Item = Frame;
fn next(&mut self) -> Option<Frame> {
if self.start <= self.end {
let frame = self.start.clone();
self.start.number += 1;
Some(frame)
} else {
None
}
}
}
pub trait FrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame>;
fn deallocate_frame(&mut self, frame: Frame);
}

View File

@@ -0,0 +1,71 @@
use memory::Frame;
use multiboot2::ElfSection;
pub struct Entry(u64);
impl Entry {
pub fn is_unused(&self) -> bool {
self.0 == 0
}
pub fn set_unused(&mut self) {
self.0 = 0;
}
pub fn flags(&self) -> EntryFlags {
EntryFlags::from_bits_truncate(self.0)
}
pub fn pointed_frame(&self) -> Option<Frame> {
if self.flags().contains(PRESENT) {
Some(Frame::containing_address(
self.0 as usize & 0x000fffff_fffff000
))
} else {
None
}
}
pub fn set(&mut self, frame: Frame, flags: EntryFlags) {
assert!(frame.start_address() & !0x000fffff_fffff000 == 0);
self.0 = (frame.start_address() as u64) | flags.bits();
}
}
bitflags! {
pub flags EntryFlags: u64 {
const PRESENT = 1 << 0,
const WRITABLE = 1 << 1,
const USER_ACCESSIBLE = 1 << 2,
const WRITE_THROUGH = 1 << 3,
const NO_CACHE = 1 << 4,
const ACCESSED = 1 << 5,
const DIRTY = 1 << 6,
const HUGE_PAGE = 1 << 7,
const GLOBAL = 1 << 8,
const NO_EXECUTE = 1 << 63,
}
}
impl EntryFlags {
pub fn from_elf_section_flags(section: &ElfSection) -> EntryFlags {
use multiboot2::{ELF_SECTION_ALLOCATED, ELF_SECTION_WRITABLE,
ELF_SECTION_EXECUTABLE};
let mut flags = EntryFlags::empty();
if section.flags().contains(ELF_SECTION_ALLOCATED) {
// section is loaded to memory
flags = flags | PRESENT;
}
if section.flags().contains(ELF_SECTION_WRITABLE) {
flags = flags | WRITABLE;
}
if !section.flags().contains(ELF_SECTION_EXECUTABLE) {
flags = flags | NO_EXECUTE;
}
flags
}
}

118
src/memory/paging/mapper.rs Normal file
View File

@@ -0,0 +1,118 @@
use super::{VirtualAddress, PhysicalAddress, Page, ENTRY_COUNT};
use super::entry::*;
use super::table::{self, Table, Level4, Level1};
use memory::{PAGE_SIZE, Frame, FrameAllocator};
use core::ptr::Unique;
pub struct Mapper {
p4: Unique<Table<Level4>>,
}
impl Mapper {
pub unsafe fn new() -> Mapper {
Mapper {
p4: Unique::new_unchecked(table::P4),
}
}
pub fn p4(&self) -> &Table<Level4> {
unsafe { self.p4.as_ref() }
}
pub fn p4_mut(&mut self) -> &mut Table<Level4> {
unsafe { self.p4.as_mut() }
}
pub fn translate(&self, virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
let offset = virtual_address % PAGE_SIZE;
self.translate_page(Page::containing_address(virtual_address))
.map(|frame| frame.number * PAGE_SIZE + offset)
}
pub fn translate_page(&self, page: Page) -> Option<Frame> {
let p3 = self.p4().next_table(page.p4_index());
let huge_page = || {
p3.and_then(|p3| {
let p3_entry = &p3[page.p3_index()];
// 1GiB page?
if let Some(start_frame) = p3_entry.pointed_frame() {
if p3_entry.flags().contains(HUGE_PAGE) {
// address must be 1GiB aligned
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
return Some(Frame {
number: start_frame.number + page.p2_index() *
ENTRY_COUNT + page.p1_index(),
});
}
}
if let Some(p2) = p3.next_table(page.p3_index()) {
let p2_entry = &p2[page.p2_index()];
// 2MiB page?
if let Some(start_frame) = p2_entry.pointed_frame() {
if p2_entry.flags().contains(HUGE_PAGE) {
// address must be 2MiB aligned
assert!(start_frame.number % ENTRY_COUNT == 0);
return Some(Frame {
number: start_frame.number + page.p1_index()
});
}
}
}
None
})
};
p3.and_then(|p3| p3.next_table(page.p3_index()))
.and_then(|p2| p2.next_table(page.p2_index()))
.and_then(|p1| p1[page.p1_index()].pointed_frame())
.or_else(huge_page)
}
pub fn map_to<A>(&mut self, page: Page, frame: Frame, flags: EntryFlags,
allocator: &mut A)
where A: FrameAllocator
{
let p4 = self.p4_mut();
let mut p3 = p4.next_table_create(page.p4_index(), allocator);
let mut p2 = p3.next_table_create(page.p3_index(), allocator);
let mut p1 = p2.next_table_create(page.p2_index(), allocator);
assert!(p1[page.p1_index()].is_unused());
p1[page.p1_index()].set(frame, flags | PRESENT);
}
pub fn map<A>(&mut self, page: Page, flags: EntryFlags, allocator: &mut A)
where A: FrameAllocator
{
let frame = allocator.allocate_frame().expect("out of memory");
self.map_to(page, frame, flags, allocator)
}
pub fn identity_map<A>(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A)
where A: FrameAllocator
{
let page = Page::containing_address(frame.start_address());
self.map_to(page, frame, flags, allocator)
}
pub fn unmap<A>(&mut self, page: Page, allocator: &mut A)
where A: FrameAllocator
{
use x86_64::instructions::tlb;
use x86_64::VirtualAddress;
assert!(self.translate(page.start_address()).is_some());
let p1 = self.p4_mut()
.next_table_mut(page.p4_index())
.and_then(|p3| p3.next_table_mut(page.p3_index()))
.and_then(|p2| p2.next_table_mut(page.p2_index()))
.expect("mapping code does not support huge pages");
let frame = p1[page.p1_index()].pointed_frame().unwrap();
p1[page.p1_index()].set_unused();
tlb::flush(VirtualAddress(page.start_address()));
// TODO free p(1,2,3) table if empty
//allocator.deallocate_frame(frame);
}
}

206
src/memory/paging/mod.rs Normal file
View File

@@ -0,0 +1,206 @@
pub use self::entry::*;
pub use self::mapper::Mapper;
use core::ops::{Deref, DerefMut};
use core::ptr::Unique;
use memory::{PAGE_SIZE, Frame, FrameAllocator};
use multiboot2::BootInformation;
use self::table::{Table, Level4};
use self::temporary_page::TemporaryPage;
mod entry;
mod table;
mod temporary_page;
mod mapper;
const ENTRY_COUNT: usize = 512;
pub type PhysicalAddress = usize;
pub type VirtualAddress = usize;
#[derive(Debug, Clone, Copy)]
pub struct Page {
number: usize,
}
impl Page {
pub fn containing_address(address: VirtualAddress) -> Page {
assert!(address < 0x0000_8000_0000_0000 ||
address >= 0xffff_8000_0000_0000,
"invalid address: 0x{:x}", address);
Page { number: address / PAGE_SIZE }
}
fn start_address(&self) -> usize {
self.number * PAGE_SIZE
}
fn p4_index(&self) -> usize {
(self.number >> 27) & 0o777
}
fn p3_index(&self) -> usize {
(self.number >> 18) & 0o777
}
fn p2_index(&self) -> usize {
(self.number >> 9) & 0o777
}
fn p1_index(&self) -> usize {
(self.number >> 0) & 0o777
}
}
pub struct ActivePageTable {
mapper: Mapper,
}
impl Deref for ActivePageTable {
type Target = Mapper;
fn deref(&self) -> &Mapper {
&self.mapper
}
}
impl DerefMut for ActivePageTable {
fn deref_mut(&mut self) -> &mut Mapper {
&mut self.mapper
}
}
impl ActivePageTable {
unsafe fn new() -> ActivePageTable {
ActivePageTable {
mapper: Mapper::new(),
}
}
pub fn with<F>(&mut self,
table: &mut InactivePageTable,
temporary_page: &mut temporary_page::TemporaryPage, // new
f: F)
where F: FnOnce(&mut Mapper)
{
use x86_64::instructions::tlb;
use x86_64::registers::control_regs;
{
let backup = Frame::containing_address(
control_regs::cr3().0 as usize);
// map temporary_page to current p4 table
let p4_table = temporary_page.map_table_frame(backup.clone(), self);
// overwrite recursive mapping
self.p4_mut()[511].set(table.p4_frame.clone(), PRESENT | WRITABLE);
tlb::flush_all();
// execute f in the new context
f(self);
// restore recursive mapping to original p4 table
p4_table[511].set(backup, PRESENT | WRITABLE);
tlb::flush_all();
}
temporary_page.unmap(self);
}
pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable {
use x86_64::PhysicalAddress;
use x86_64::registers::control_regs;
let old_table = InactivePageTable {
p4_frame: Frame::containing_address(
control_regs::cr3().0 as usize
),
};
unsafe {
control_regs::cr3_write(PhysicalAddress(
new_table.p4_frame.start_address() as u64));
}
old_table
}
}
pub struct InactivePageTable {
p4_frame: Frame,
}
impl InactivePageTable {
pub fn new(frame: Frame,
active_table: &mut ActivePageTable,
temporary_page: &mut TemporaryPage)
-> InactivePageTable {
{
let table = temporary_page.map_table_frame(frame.clone(),
active_table);
// now we are able to zero the table
table.zero();
// set up recursive mapping for the table
table[511].set(frame.clone(), PRESENT | WRITABLE);
}
temporary_page.unmap(active_table);
InactivePageTable { p4_frame: frame }
}
}
pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
where A: FrameAllocator
{
let mut temporary_page = TemporaryPage::new(Page { number: 0xcafebabe },
allocator);
let mut active_table = unsafe { ActivePageTable::new() };
let mut new_table = {
let frame = allocator.allocate_frame().expect("no more frames");
InactivePageTable::new(frame, &mut active_table, &mut temporary_page)
};
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
let elf_sections_tag = boot_info.elf_sections_tag()
.expect("Memory map tag required");
for section in elf_sections_tag.sections() {
use self::entry::WRITABLE;
if !section.is_allocated() {
// section is not loaded to memory
continue;
}
assert!(section.start_address() % PAGE_SIZE == 0,
"sections need to be page aligned");
println!("mapping section at addr: {:#x}, size: {:#x}",
section.addr, section.size);
let flags = EntryFlags::from_elf_section_flags(section);
let start_frame = Frame::containing_address(section.start_address());
let end_frame = Frame::containing_address(section.end_address() - 1);
for frame in Frame::range_inclusive(start_frame, end_frame) {
mapper.identity_map(frame, flags, allocator);
}
}
// identity map the VGA text buffer
let vga_buffer_frame = Frame::containing_address(0xb8000);
mapper.identity_map(vga_buffer_frame, WRITABLE, allocator);
// identity map the multiboot info structure
let multiboot_start = Frame::containing_address(boot_info.start_address());
let multiboot_end = Frame::containing_address(boot_info.end_address() - 1);
for frame in Frame::range_inclusive(multiboot_start, multiboot_end) {
mapper.identity_map(frame, PRESENT, allocator);
}
});
let old_table = active_table.switch(new_table);
println!("NEW TABLE!!!");
// turn the old p4 page into a guard page
let old_p4_page = Page::containing_address(
old_table.p4_frame.start_address()
);
active_table.unmap(old_p4_page, allocator);
println!("guard page at {:#x}", old_p4_page.start_address());
}

100
src/memory/paging/table.rs Normal file
View File

@@ -0,0 +1,100 @@
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};
use memory::FrameAllocator;
use memory::paging::entry::*;
use memory::paging::ENTRY_COUNT;
pub const P4: *mut Table<Level4> = 0xffffffff_fffff000 as *mut _;
pub struct Table<L: TableLevel> {
entries: [Entry; ENTRY_COUNT],
level: PhantomData<L>,
}
impl<L> Table<L> where L: TableLevel {
pub fn zero(&mut self) {
for entry in self.entries.iter_mut() {
entry.set_unused();
}
}
}
impl<L> Table<L> where L: HierarchicalLevel {
fn next_table_address(&self, index: usize) -> Option<usize> {
let entry_flags = self[index].flags();
if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) {
let table_address = self as *const _ as usize;
Some((table_address << 9) | (index << 12))
} else {
None
}
}
pub fn next_table(&self, index: usize) -> Option<&Table<L::NextLevel>> {
self.next_table_address(index)
.map(|address| unsafe { &*(address as *const _) })
}
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<L::NextLevel>> {
self.next_table_address(index)
.map(|address| unsafe { &mut *(address as *mut _) })
}
pub fn next_table_create<A>(&mut self,
index: usize,
allocator: &mut A)
-> &mut Table<L::NextLevel>
where A: FrameAllocator
{
if self.next_table(index).is_none() {
assert!(!self.entries[index].flags().contains(HUGE_PAGE),
"mapping code does not support huge pages");
let frame = allocator.allocate_frame().expect("no frames available");
self.entries[index].set(frame, PRESENT | WRITABLE);
self.next_table_mut(index).unwrap().zero();
}
self.next_table_mut(index).unwrap()
}
}
impl<L> Index<usize> for Table<L> where L: TableLevel {
type Output = Entry;
fn index(&self, index: usize) -> &Entry {
&self.entries[index]
}
}
impl<L> IndexMut<usize> for Table<L> where L: TableLevel {
fn index_mut(&mut self, index: usize) -> &mut Entry {
&mut self.entries[index]
}
}
pub trait TableLevel {}
pub enum Level4 {}
pub enum Level3 {}
pub enum Level2 {}
pub enum Level1 {}
impl TableLevel for Level4 {}
impl TableLevel for Level3 {}
impl TableLevel for Level2 {}
impl TableLevel for Level1 {}
pub trait HierarchicalLevel: TableLevel {
type NextLevel: TableLevel;
}
impl HierarchicalLevel for Level4 {
type NextLevel = Level3;
}
impl HierarchicalLevel for Level3 {
type NextLevel = Level2;
}
impl HierarchicalLevel for Level2 {
type NextLevel = Level1;
}

View File

@@ -0,0 +1,79 @@
use super::{Page, ActivePageTable, VirtualAddress};
use super::table::{Table, Level1};
use memory::{Frame, FrameAllocator};
pub struct TemporaryPage {
page: Page,
allocator: TinyAllocator,
}
impl TemporaryPage {
pub fn new<A>(page: Page, allocator: &mut A) -> TemporaryPage
where A: FrameAllocator
{
TemporaryPage {
page: page,
allocator: TinyAllocator::new(allocator),
}
}
/// Maps the temporary page to the given frame in the active table.
/// Returns the start address of the temporary page.
pub fn map(&mut self, frame: Frame, active_table: &mut ActivePageTable)
-> VirtualAddress
{
use super::entry::WRITABLE;
assert!(active_table.translate_page(self.page).is_none(),
"temporary page is already mapped");
active_table.map_to(self.page, frame, WRITABLE, &mut self.allocator);
self.page.start_address()
}
/// Unmaps the temporary page in the active table.
pub fn unmap(&mut self, active_table: &mut ActivePageTable) {
active_table.unmap(self.page, &mut self.allocator)
}
/// Maps the temporary page to the given page table frame in the active
/// table. Returns a reference to the now mapped table.
pub fn map_table_frame(&mut self,
frame: Frame,
active_table: &mut ActivePageTable)
-> &mut Table<Level1> {
unsafe { &mut *(self.map(frame, active_table) as *mut Table<Level1>) }
}
}
struct TinyAllocator([Option<Frame>; 3]);
impl TinyAllocator {
fn new<A>(allocator: &mut A) -> TinyAllocator
where A: FrameAllocator
{
let mut f = || allocator.allocate_frame();
let frames = [f(), f(), f()];
TinyAllocator(frames)
}
}
impl FrameAllocator for TinyAllocator {
fn allocate_frame(&mut self) -> Option<Frame> {
for frame_option in &mut self.0 {
if frame_option.is_some() {
return frame_option.take();
}
}
None
}
fn deallocate_frame(&mut self, frame: Frame) {
for frame_option in &mut self.0 {
if frame_option.is_none() {
*frame_option = Some(frame);
return;
}
}
panic!("Tiny allocator can hold only 3 frames.");
}
}

147
src/vga_buffer.rs Normal file
View File

@@ -0,0 +1,147 @@
use core::fmt;
use core::ptr::Unique;
use spin::Mutex;
use volatile::Volatile;
pub static WRITER: Mutex<Writer> = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::LightGreen, Color::Black),
buffer: unsafe { Unique::new_unchecked(0xb8000 as *mut _) },
});
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum Color {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Magenta = 5,
Brown = 6,
LightGray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
Pink = 13,
Yellow = 14,
White = 15,
}
#[derive(Debug, Clone, Copy)]
struct ColorCode(u8);
impl ColorCode {
const fn new(foreground: Color, background: Color) -> ColorCode {
ColorCode((background as u8) << 4 | (foreground as u8))
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct ScreenChar {
ascii_character: u8,
color_code: ColorCode,
}
const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
pub struct Writer {
column_position: usize,
color_code: ColorCode,
buffer: Unique<Buffer>,
}
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
if self.column_position >= BUFFER_WIDTH {
self.new_line();
}
let row = BUFFER_HEIGHT - 1;
let col = self.column_position;
let color_code = self.color_code;
self.buffer().chars[row][col].write(ScreenChar {
ascii_character: byte,
color_code: color_code,
});
self.column_position += 1;
}
}
}
pub fn write_str(&mut self, s: &str) {
for byte in s.bytes() {
self.write_byte(byte)
}
}
fn buffer(&mut self) -> &mut Buffer {
unsafe{ self.buffer.as_mut() }
}
fn new_line(&mut self) {
for row in 1..BUFFER_HEIGHT {
for col in 0..BUFFER_WIDTH {
let buffer = self.buffer();
let character = buffer.chars[row][col].read();
buffer.chars[row - 1][col].write(character);
}
}
self.clear_row(BUFFER_HEIGHT-1);
self.column_position = 0;
}
fn clear_row(&mut self, row: usize) {
let blank = ScreenChar {
ascii_character: b' ',
color_code: self.color_code,
};
for col in 0..BUFFER_WIDTH {
self.buffer().chars[row][col].write(blank);
}
}
}
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.write_byte(byte)
}
Ok(())
}
}
macro_rules! print {
($($arg:tt)*) => ({
$crate::vga_buffer::print(format_args!($($arg)*));
});
}
macro_rules! println {
($fmt:expr) => (print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
pub fn print(args: fmt::Arguments) {
use core::fmt::Write;
WRITER.lock().write_fmt(args).unwrap();
}
pub fn clear_screen() {
for _ in 0..BUFFER_HEIGHT {
println!("");
}
}

13
x86_64-blog_os.json Normal file
View File

@@ -0,0 +1,13 @@
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"linker-flavor": "gcc",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"arch": "x86_64",
"os": "none",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float",
"panic-strategy": "abort"
}