diff --git a/Cargo.lock b/Cargo.lock index 4d4d84e..bf132d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,6 +242,8 @@ dependencies = [ name = "riscv64" version = "0.1.0" dependencies = [ + "bit_field", + "bitflags 2.4.0", "port", ] diff --git a/aarch64/src/l.S b/aarch64/src/l.S index b5d1c84..529659b 100644 --- a/aarch64/src/l.S +++ b/aarch64/src/l.S @@ -73,10 +73,9 @@ PT_ISH = (3<<8) // Inner shareable (shared across CPUs) // This defines the kernel's virtual address location. // This value splits a 48 bit address space exactly in half, with the half // beginning with 1 going to the kernel. -KZERO = 0xffff800000000000 +KZERO = 0xffff800000000000 MiB = (1<<20) GiB = (1<<30) -KTZERO = (KZERO + 2*MiB) // Virtual base of kernel text // Constants for early uart setup MMIO_BASE_RPI3 = 0x3f000000 diff --git a/port/src/fdt.rs b/port/src/fdt.rs index ab097f0..20a65a0 100644 --- a/port/src/fdt.rs +++ b/port/src/fdt.rs @@ -1,8 +1,11 @@ +use crate::println; use core::{ ffi::CStr, mem::{self, MaybeUninit}, }; +const DEBUG_DT_PARSING: bool = false; + #[derive(Debug)] pub enum ParseError { InvalidHeader, @@ -66,6 +69,9 @@ impl<'a> DeviceTree<'a> { /// Given a pointer to the dtb as a u64, return a DeviceTree struct. pub unsafe fn from_u64(ptr: u64) -> Result { let u8ptr = ptr as *const mem::MaybeUninit; + if DEBUG_DT_PARSING { + println!(" DT @ {u8ptr:x?}"); + } // Extract the real length from the header let dtb_buf_for_header: &[mem::MaybeUninit] = @@ -77,7 +83,11 @@ impl<'a> DeviceTree<'a> { // Extract the buffer for real let dtb_buf: &[mem::MaybeUninit] = unsafe { core::slice::from_raw_parts(u8ptr as *const MaybeUninit, len) }; - FdtHeader::new(dtb_buf, false).map(|header| Self { data: dtb_buf, header }) + let h = FdtHeader::new(dtb_buf, false); + if DEBUG_DT_PARSING { + println!(" DT header {:#x?}", h); + } + h.map(|header| Self { data: dtb_buf, header }) } /// Return slice containing `structs` area in FDT diff --git a/riscv64/Cargo.toml b/riscv64/Cargo.toml index ef37c0b..8468937 100644 --- a/riscv64/Cargo.toml +++ b/riscv64/Cargo.toml @@ -8,6 +8,8 @@ default-target = "riscv64imac-unknown-none-elf" [dependencies] port = { path = "../port" } +bitflags = "2.3.3" +bit_field = "0.10.2" [features] opensbi = [] diff --git a/riscv64/lib/config_default.toml b/riscv64/lib/config_default.toml index 4b7abea..dac2db1 100644 --- a/riscv64/lib/config_default.toml +++ b/riscv64/lib/config_default.toml @@ -7,7 +7,7 @@ buildflags = [ [link] arch = 'riscv' script = 'riscv64/lib/kernel.ld' -load-address = '0x80200000' +load-address = '0xffffffffc0200000' [config] platform = "virt" diff --git a/riscv64/lib/kernel.ld b/riscv64/lib/kernel.ld index 46e28d6..e64e021 100644 --- a/riscv64/lib/kernel.ld +++ b/riscv64/lib/kernel.ld @@ -3,36 +3,38 @@ ENTRY(start) SECTIONS { . = ${LOAD-ADDRESS}; + + text = .; .text : ALIGN(4096) { *(.text.entry) *(.text*) . = ALIGN(2097152); - PROVIDE(etext = .); + etext = .; } + rodata = .; .rodata : ALIGN(4096) { *(.rodata*) - *(.srodata*) . = ALIGN(2097152); - PROVIDE(erodata = .); + erodata = .; } + data = .; .data : ALIGN(4096) { *(.data*) - *(.sdata*) . = ALIGN(2097152); - PROVIDE(edata = .); + edata = .; } + bss = .; .bss : ALIGN(4096) { *(.bss*) - *(.sbss*) *(COMMON) . = ALIGN(2097152); - PROVIDE(end = .); } + end = .; /DISCARD/ : { - *(.eh_frame) + *(.eh_frame .note.GNU-stack) } } diff --git a/riscv64/src/l.S b/riscv64/src/l.S index 2ff4a6e..7f22ec4 100644 --- a/riscv64/src/l.S +++ b/riscv64/src/l.S @@ -1,15 +1,118 @@ +// 0xFFFFFFFF40000000 +.equ PHYSICAL_MEMORY_OFFSET, (0xFFFFFFFFC0000000 - 0x80000000) + .section .text.entry .globl start start: - bnez a0, 1f - la sp, stack // set the stack pointer - li t0, 4096 * 4 - add sp, sp, t0 // add stack length - call main9 + // a0 == hartid + // pc == 0x80200000 + // sp == 0x800xxxxx + + // 1. set sp + // sp = bootstack + (hartid + 1) * 0x10000 + add t0, a0, 1 + slli t0, t0, 16 + lui sp, %hi(bootstack) + add sp, sp, t0 // add stack length + + // enable paging + // satp = (8 << 60) | PPN(boot_page_table) + // SATP = Supervisor Address Translation and Protection + // Register definition: + // | MODE | ASID | PPN | + // |[63..60]|[59..44]|[43..0]| + // PPN = Physical Page Number (of root page table) + // ASID = Address Space Identifier + // Volume II: RISC-V Privileged Architectures V20211203 p75 + // SXLEN 64, MODE 8: Sv39 aka Page-based 39-bit virtual addressing + lui t0, %hi(boot_page_table) + // page size is 4k (2^12) - drop lowest 12 bits + srli t4, t0, 12 + + // subtract offset to get the physical address of the root page table + li t1, PHYSICAL_MEMORY_OFFSET + sub t2, t0, t1 + + // page size is 4k (2^12) - drop lowest 12 bits + srli t3, t2, 12 + // final SATP + li t1, 8 << 60 // mode Sv39 + or t0, t3, t1 + + // t3 holds the root page table physical address divided by 4k; shift for flags + slli t1, t4, 10 + ori t1, t4, 0xc1 // DAGU XWRV + sd t1, 8*255(t2) // #255 + + // flush TLB + sfence.vma + csrw satp, t0 + // flush TLB + sfence.vma + + // 3. jump to main9 (absolute address) + lui t0, %hi(main9) + addi t0, t0, %lo(main9) + jr t0 + 1: wfi j 1b -.bss -.balign 4096 -stack: .space 4096 * 4 +/* STACK */ +.section .bss.stack +.align 12 # page align +.global bootstack + +bootstack: +.space 4096 * 4 * 8 +.global bootstacktop + +bootstacktop: +.section .data +.align 12 // page align + +/* PAGING */ +boot_page_table: +// PTE (Page Table Entry) definition (SV39) +// A virtual address (VA) in SV39 consists of 3 parts plus the final offset. +// Those parts make up the _virtual page number_ (VPN). +// Each VPN part is 9 bits long, used to point to an entry in a page table. +// The VA offset is another 12 bits, so that it can point to any of 4K bytes. +// 9 + 9 + 9 + 12 = 39 +// +// NOTE: A page table entry is 8 bytes; 512 entries are 512*8=4k. +// +// N is reserved for Svnapot, PBMT is Page-based Memory Types +// | N | PBMT | Rsvd. | +// |[63]|[62..61]|[60..54]| +// The Physical Page Number (PPN) is 26 + 9 + 9 = 44 bits long. +// | PPN[2] | PPN[1] | PPN[0] | +// |[53..28]|[27..19]|[18..10]| +// RSW is reserved for supervisor software control. +// D - dirty, A - accessed, G - global mapping, U - user mode access, +// XWR - exec/write/read (table 4.5, p80), V - entry is valid +// | RSW | D | A | G | U | X | W | R | V | +// |[9..8]|[7]|[6]|[5]|[4]|[3]|[2]|[1]|[0]| +// +// NOTE: quad = 4 words = 4 double bytes = 8 bytes +// https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as_7.html#SEC115 +// Those are leaf pages (at least on of RWX set), 1G (0x4000_0000) each. + +// Identity map in the beginning +// 1st page: 0x00000000_00000000 -> 0x00000000 (1G) +.quad (0x00000 << 10) | 0xcf // VRWXAD +// 2nd page: 0x00000000_40000000 -> 0x40000000 (1G) +.quad (0x40000 << 10) | 0xcf // VRWXAD +// 3rd page: 0x00000000_80000000 -> 0x80000000 (1G) +.quad (0x80000 << 10) | 0xcf // VRWXAD +// 504 empty entries + +// Remap first 3GB in the end +.zero 506 * 8 +// 3rd last page: 0xffffffff_40000000 -> 0x00000000 (1G) +.quad (0x00000 << 10) | 0xcf // VRWXAD +// 2nd last page: 0xffffffff_80000000 -> 0x40000000 (1G) +.quad (0x40000 << 10) | 0xcf // VRWXAD +// very last page: 0xffffffff_c0000000 -> 0x80000000 (1G) +.quad (0x80000 << 10) | 0xcf // VRWXAD diff --git a/riscv64/src/main.rs b/riscv64/src/main.rs index 9172a2c..5f247b4 100644 --- a/riscv64/src/main.rs +++ b/riscv64/src/main.rs @@ -1,34 +1,319 @@ #![feature(alloc_error_handler)] +#![feature(stdsimd)] #![feature(asm_const)] #![feature(panic_info_message)] +#![feature(ptr_to_from_bits)] #![cfg_attr(not(any(test, feature = "cargo-clippy")), no_std)] #![cfg_attr(not(test), no_main)] #![allow(clippy::upper_case_acronyms)] #![forbid(unsafe_op_in_unsafe_fn)] +mod memory; mod platform; mod runtime; mod sbi; mod uart16550; -use port::println; +use port::{print, println}; -use crate::platform::{devcons, platform_init}; +use crate::{ + memory::{phys_to_virt, PageTable, PageTableEntry, VirtualAddress}, + platform::{devcons, platform_init}, +}; +use core::{ffi::c_void, ptr::read_volatile, ptr::write_volatile, slice}; use port::fdt::DeviceTree; #[cfg(not(test))] core::arch::global_asm!(include_str!("l.S")); +const LOGO: &str = " + ️_______________________________________ ️ +| ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️| +| ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️💻💻💻 ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️| +| ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️💻 ️ ️ ️ ️ ️💻 ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️| +| ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️💻 ️ ️ ️999 ️ ️💻💻 ️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️| +| ️ ️ ️ ️ ️ ️ ️ ️ ️💻💻💻 ️ ️ ️9 ️ ️ ️9 ️ ️ ️💻💻 ️ ️ ️ ️ ️ ️ ️ ️ ️| +| ️ ️ ️ ️ ️ ️💻💻 ️ ️ ️ ️💻 ️ ️ ️9999 ️ ️ ️ ️ ️💻 ️ ️ ️ ️ ️ ️ ️ ️ ️| +| ️ ️ ️💻💻 ️ ️RRR ️ ️ ️💻 ️ ️ ️ ️ ️9 ️ ️💻️💻💻💻 ️ ️ ️ ️ ️ ️| +| ️💻💻 ️ ️ ️ ️R ️ ️R ️ ️ ️💻 ️999 ️ ️💻 ️ ️ ️ ️ ️ ️💻💻 ️ ️ ️| +| ️💻💻️ ️ ️ ️ ️RRR ️ ️ ️ ️ ️💻️ ️ ️ ️ ️💻 ️ ️⌨️ ️ ️🖱️ ️ ️ ️💻💻 ️| +| ️ ️💻💻️ ️ ️ ️R ️ ️R ️ ️ ️ ️ ️💻️💻️💻️ ️ ️ ️ ️ ️ ️ ️ ️ ️ ️💻💻 ️| +| ️ ️ ️ ️ ️💻💻💻💻💻💻💻💻💻💻💻💻💻💻💻💻 ️ ️| +|_______________________________________| +"; + +const WHERE_ARE_WE: bool = true; +const WALK_DT: bool = true; + +/// debug helper - dump some memory range +pub fn dump(addr: usize, length: usize) { + let s = unsafe { slice::from_raw_parts(addr as *const u8, length) }; + println!("dump {length} bytes @{addr:x}"); + for w in s.iter() { + print!("{:02x}", w); + } + println!(); +} + +/// debug helper - dump some memory range, in chunks +pub fn dump_block(base: usize, size: usize, step_size: usize) { + println!("dump_block {base:x}:{:x}/{step_size:x}", base + size); + for b in (base..base + size).step_by(step_size) { + dump(b, step_size); + } +} + +/// nobody wants to write this out, we know it's unsafe... +fn read32(a: usize) -> u32 { + unsafe { core::ptr::read_volatile(a as *mut u32) } +} + +/// nobody wants to write this out, we know it's unsafe... +fn write32(a: usize, v: u32) { + unsafe { core::ptr::write_volatile(a as *mut u32, v) } +} + +/// print a memory range from given pointers - name, start, end, and size +unsafe fn print_memory_range(name: &str, start: &*const c_void, end: &*const c_void) { + let start = start as *const _ as u64; + let end = end as *const _ as u64; + let size = end - start; + println!(" {name}{start:#x}-{end:#x} ({size:#x})"); +} + +/// Print binary sections of the kernel: text, rodata, data, bss, total range. +/// NOTE: This needs to align with the corresponding linker script, where the +/// sections are defined. +fn print_binary_sections() { + extern "C" { + static text: *const c_void; + static etext: *const c_void; + static rodata: *const c_void; + static erodata: *const c_void; + static data: *const c_void; + static edata: *const c_void; + static bss: *const c_void; + static end: *const c_void; + } + + println!("Binary sections:"); + unsafe { + print_memory_range("text:\t\t", &text, &etext); + dump((&text) as *const _ as usize, 0x20); + print_memory_range("rodata:\t", &rodata, &erodata); + print_memory_range("data:\t\t", &data, &edata); + print_memory_range("bss:\t\t", &bss, &end); + print_memory_range("total:\t", &text, &end); + } +} + +fn consume_dt_block(name: &str, a: u64, l: u64) { + let v = phys_to_virt(a as usize); + // let v = a as usize; + println!("- {name}: {a:016x}:{v:016x} ({l:x})"); + match name { + "test@100000" => { + let x = read32(v); + println!("{name}[0]:{x:x}"); + write32(v, x | 0x1234_5678); + let x = read32(v); + println!("{name}[0]:{x:x}"); + } + "uart@10000000" => { + println!("{name}: {l:x}"); + dump(v, 0x4); + } + "plic@c000000" | "clint@2000000" => { + let x = read32(v); + println!("{name}[0]:{x:x}"); + } + "virtio_mmio@10001000" | "virtio_mmio@10002000" => { + dump(v, 0x20); + } + "flash@20000000" => { + dump(v, 0x20); + } + "pci@30000000" => { + // NOTE: v+l overflows usize, hitting 0 + // dump_block(v, (l - 0x40) as usize, 0x40); + dump_block(v, 0x40, 0x10); + } + _ => {} + } +} + +fn walk_dt(dt: &DeviceTree) { + dt.nodes().for_each(|n| { + let name = dt.node_name(&n); + if true { + if let Some(name) = name { + let p = dt.property(&n, "start"); + println!("{name:?}: {n:#?} {p:?}"); + } + } + dt.property_translated_reg_iter(n).next().and_then(|i| { + let b = i.regblock(); + if let Some(b) = b { + // println!("{b:#?}"); + let a = b.addr; + if let Some(name) = name { + if let Some(l) = b.len { + consume_dt_block(name, a, l); + } + } + } + b + }); + }); +} + +/// check on memory mapping foo +fn where_are_we() { + let x = "test"; + let p = x.as_ptr(); + // e.g., 0xffffffffc0400096 + println!("YOU ARE HERE (approx.): {p:#x?}"); +} + +fn flush_tlb() { + // unsafe { core::arch::riscv64::sinval_vma_all() } + unsafe { core::arch::asm!("sfence.vma") } +} + +const KERNEL_LOAD_ADDR: usize = 0x8020_0000; + +// fixed 25 bits, used 39 bits +const VFIXED: usize = 0xff_ff_ff_80__00_00_00_00; + +fn check_dtb_mapping(dtb_ptr: u64) { + let ptr = dtb_ptr as usize; + // NOTE: The DTB signature is big-endian. + let sig = u32::from_be(read32(ptr)); + if sig != 0xd00dfeed { + println!(" 0x{dtb_ptr:016x}: 0x{sig:08x}"); + panic!("DTB not found :("); + } + + // We remap the first 3G via the last 3 PTEs + let ppn2 = 511 << (9 + 9 + 12); + let vaddr = VFIXED | ppn2 | ptr; + + let sig = u32::from_be(read32(vaddr)); + if sig != 0xd00dfeed { + println!(" 0x{vaddr:016x}: 0x{sig:08x}"); + panic!("DTB not found :("); + } +} + #[no_mangle] pub extern "C" fn main9(hartid: usize, dtb_ptr: u64) -> ! { + // devcons::init_sbi(); + // println!("\n--> SBI devcons\n"); + // QEMU: dtb@bf000000 + // println!("dtb@{dtb_ptr:x}"); let dt = unsafe { DeviceTree::from_u64(dtb_ptr).unwrap() }; - crate::devcons::init(&dt); - platform_init(); - println!(); + devcons::init(&dt); + println!("\n--> DT / native devcons\n"); + + platform_init(); println!("r9 from the Internet"); + println!("{LOGO}"); println!("Domain0 Boot HART = {hartid}"); println!("DTB found at: {dtb_ptr:#x}"); + print_binary_sections(); + + if WALK_DT { + walk_dt(&dt); + } + + if WHERE_ARE_WE { + where_are_we(); + } + + println!(); + println!(); + + extern "C" { + static boot_page_table: *const c_void; + } + + let bpt_addr = unsafe { (&boot_page_table) as *const _ as u64 }; + let bpt = PageTable::new(bpt_addr); + println!(" boot page table @ 0x{:016x} (0x{:08x})", bpt.get_vaddr(), bpt.get_paddr()); + + println!(); + bpt.print_entry(0); + bpt.print_entry(1); + bpt.print_entry(2); + bpt.print_entry(3); + println!(); + + bpt.print_entry(255); + println!(); + + bpt.print_entry(508); + bpt.print_entry(509); + bpt.print_entry(510); + bpt.print_entry(511); + println!(); + println!(); + + check_dtb_mapping(dtb_ptr); + + // Let's create a new PT :) + println!("=== create new PT"); + let pt_at = 100; + bpt.print_entry(pt_at); + let pt = bpt.create_pt_at(pt_at); + println!(" new pt @ {:016x} ({:08x})", pt.get_vaddr(), pt.get_paddr()); + bpt.print_entry(pt_at); + println!(); + + // Let's create a PTE for the kernel :) + let kernel_entry_pos = 4; + bpt.print_entry(kernel_entry_pos); + // create an entry resolving to the kernel's base addr + let _ = bpt.create_entry_for(KERNEL_LOAD_ADDR as u64, kernel_entry_pos); + bpt.print_entry(kernel_entry_pos); + println!(); + + println!("=== create self reference"); + let self_ref_pos = 5; + println!(" boot page table before: "); + bpt.print_entry(self_ref_pos); + let spt = bpt.create_self_ref(self_ref_pos); + flush_tlb(); + println!(); + println!(" boot page table after: "); + bpt.print_entry(self_ref_pos); + println!(" self reference pt: "); + spt.print_entry(self_ref_pos); + println!(); + println!(); + + // point to first byte of the kernel + let vaddr = VirtualAddress { + vpn2: memory::SizedInteger::<9>(self_ref_pos as u64), + vpn1: memory::SizedInteger::<9>(kernel_entry_pos as u64), + vpn0: memory::SizedInteger::<9>(0), + offset: memory::SizedInteger::<12>(0), + }; + let va = vaddr.get() as usize; + println!(" 0x{va:016x} = {vaddr:?}"); + dump(va, 64); + // write32(va, 0x1234_5678); + println!(); + + let vaddr = VirtualAddress { + vpn2: memory::SizedInteger::<9>(self_ref_pos as u64), + vpn1: memory::SizedInteger::<9>(self_ref_pos as u64), + vpn0: memory::SizedInteger::<9>(kernel_entry_pos as u64), + offset: memory::SizedInteger::<12>(0), + }; + let va = vaddr.get() as usize; + println!(" 0x{va:016x} = {vaddr:?}"); + dump(va, 64); #[cfg(not(test))] sbi::shutdown(); diff --git a/riscv64/src/memory.rs b/riscv64/src/memory.rs new file mode 100644 index 0000000..b89a786 --- /dev/null +++ b/riscv64/src/memory.rs @@ -0,0 +1,252 @@ +use crate::platform::PHYSICAL_MEMORY_OFFSET; +use bit_field::BitField; +use bitflags::{bitflags, Flags}; +use core::ptr::{read_volatile, write_volatile}; +use port::println; + +const DEBUG_PHYS_TO_VIRT: bool = false; + +pub const PAGE_TABLE_SIZE: u64 = 4096; + +/// Convert physical address to virtual address +/// See 4.3.2 Virtual Address Translation Process, +/// Volume II: RISC-V Privileged Architectures V20211203 p82 +/// va.off = pa.off +/// +/// Physical address: +/// | VPN[2] | VPN[1] | VPN[0] | offset | +/// |[38..30]|[29..21]|[20..12]|[11..0] | +/// Virtual address: +/// | PPN[2] | PPN[1] | PPN[0] | offset | +/// | [55..30] |[29..21]|[20..12]|[11..0] | +/// NOTE: PPN[2] is 26 bits wide, VPN[2] only 9 +#[inline] +pub fn phys_to_virt(paddr: usize) -> usize { + let vaddr = PHYSICAL_MEMORY_OFFSET + paddr; + if DEBUG_PHYS_TO_VIRT { + println!("Physical address {paddr:x} translates to {vaddr:x}"); + } + vaddr +} + +#[derive(Debug)] +pub struct VirtualAddress { + pub vpn2: SizedInteger<9>, + pub vpn1: SizedInteger<9>, + pub vpn0: SizedInteger<9>, + pub offset: SizedInteger<12>, +} + +impl VirtualAddress { + pub fn get(&self) -> u64 { + // self.vpn2 << 30 | self.vpn1 << 21 | self.vpn0 << 12 | self.offset + let mut out = 0u64; + out.set_bits(30..=38, self.vpn2.into()); + out.set_bits(21..=29, self.vpn1.into()); + out.set_bits(12..=20, self.vpn0.into()); + out.set_bits(0..=11, self.offset.into()); + out + } +} + +impl From for VirtualAddress { + fn from(value: u64) -> Self { + Self { + vpn2: value.get_bits(30..=38).try_into().unwrap(), + vpn1: value.get_bits(21..=29).try_into().unwrap(), + vpn0: value.get_bits(12..=20).try_into().unwrap(), + offset: value.get_bits(0..=11).try_into().unwrap(), + } + } +} + +#[derive(Clone, Copy)] +pub struct SizedInteger(pub u64); + +impl core::fmt::Debug for SizedInteger { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:010x}", self.0) + } +} + +#[derive(Debug)] +pub struct NumberTooLarge; + +impl TryFrom for SizedInteger { + type Error = NumberTooLarge; + + fn try_from(value: u64) -> Result { + if (value.leading_zeros() as usize) < 64 - N { + return Err(NumberTooLarge); + } + Ok(Self(value)) + } +} + +impl From> for u64 { + fn from(value: SizedInteger) -> Self { + value.0 + } +} + +bitflags! { + #[derive(Debug)] + pub struct PageTableFlags: u8 { + const D = 1 << 7; + const A = 1 << 6; + const G = 1 << 5; + const U = 1 << 4; + const X = 1 << 3; // execute + const W = 1 << 2; // write + const R = 1 << 1; // read + const V = 1; // valid + } +} + +#[derive(Debug)] +pub struct PageTableEntry { + ppn2: SizedInteger<26>, + ppn1: SizedInteger<9>, + ppn0: SizedInteger<9>, + flags: PageTableFlags, +} + +impl PageTableEntry { + pub fn serialize(&self) -> u64 { + let mut out = 0u64; + out.set_bits(0..=7, self.flags.bits() as _); + out.set_bits(10..=18, self.ppn0.into()); + out.set_bits(19..=27, self.ppn1.into()); + out.set_bits(28..=53, self.ppn2.into()); + out + } + + pub fn write_to(&self, addr: u64) { + unsafe { write_volatile(addr as *mut u64, self.serialize()) } + } + + pub fn from_paddr(paddr: u64, flags: PageTableFlags) -> PageTableEntry { + // shifted phyiscal address: pages align to 4k + let spaddr = paddr >> 12; + let ppn2 = (spaddr >> 18) & 0x3ff_ffff; + let ppn1 = (spaddr >> 9) & 0x1ff; + let ppn0 = spaddr & 0x1ff; + println!(" {spaddr:x} / {ppn2:x} {ppn1:x} {ppn0:x}"); + PageTableEntry { + ppn2: ppn2.try_into().unwrap(), + ppn1: ppn1.try_into().unwrap(), + ppn0: ppn0.try_into().unwrap(), + flags, + } + } +} + +impl From for PageTableEntry { + fn from(value: u64) -> Self { + let flags = PageTableFlags::from_bits(value.get_bits(0..=7) as _).unwrap(); + Self { + ppn2: value.get_bits(28..=53).try_into().unwrap(), + ppn1: value.get_bits(19..=27).try_into().unwrap(), + ppn0: value.get_bits(10..=18).try_into().unwrap(), + flags, + } + } +} + +static mut ALLOC_I: u64 = 100; + +#[derive(Debug)] +pub struct PageTable { + addr: u64, +} + +impl PageTable { + const ENTRY_SIZE: u64 = 8; + + pub fn new(addr: u64) -> Self { + Self { addr } + } + + pub fn get_paddr(&self) -> u64 { + self.addr - crate::platform::PHYSICAL_MEMORY_OFFSET as u64 + } + + pub fn get_vaddr(&self) -> u64 { + self.addr + } + + pub fn get_entry(&self, at: u16) -> PageTableEntry { + let addr = self.addr + (at as u64 * Self::ENTRY_SIZE); + let high = unsafe { read_volatile((addr + 4) as *const u64) }; + let low = unsafe { read_volatile(addr as *const u64) }; + ((high << 32) | low).into() + } + + pub fn create_entry(&self) {} + + pub fn print_entry(&self, at: u16) { + println!(" PTE 0x{at:03x} ({at:03}) {:?}", self.get_entry(at)); + } + + pub fn create_entry_for(&self, paddr: u64, at: u16) -> PageTableEntry { + println!(" creating entry for 0x{paddr:08x} "); + let flags = PageTableFlags::W.union(PageTableFlags::R).union(PageTableFlags::V); + let e = PageTableEntry::from_paddr(paddr, flags); + // The entry goes to this table's own address + offset for the entry + let eaddr = self.addr + Self::ENTRY_SIZE * at as u64; + unsafe { e.write_to(eaddr) }; + e + } + + pub fn create_pt_at(&self, at: u16) -> PageTable { + let paddr = self.get_paddr() + PAGE_TABLE_SIZE; + println!(" creating new PT @ 0x{paddr:08x} "); + let flags = PageTableFlags::A.union(PageTableFlags::V); + let e = PageTableEntry::from_paddr(paddr, flags); + let eaddr = self.addr + Self::ENTRY_SIZE * at as u64; + unsafe { e.write_to(eaddr) }; + PageTable::new(paddr) + } + + pub fn create_self_ref(&self, at: u16) -> PageTable { + let a = self.get_paddr(); + println!(" self ref entry PT @ 0x{a:08x} "); + let flags = PageTableFlags::V.union(PageTableFlags::A); + let e = PageTableEntry::from_paddr(a, flags); + // The entry goes to this table's own address + offset for the entry + let eaddr = self.addr + Self::ENTRY_SIZE * at as u64; + unsafe { e.write_to(eaddr) }; + PageTable::new(self.addr) + } +} + +#[cfg(test)] +mod tests { + use crate::{PageTableEntry, PageTableFlags}; + + #[test] + fn test_pagetableentry() { + { + let entry = PageTableEntry { + ppn2: 0.try_into().unwrap(), + ppn1: 1.try_into().unwrap(), + ppn0: 2.try_into().unwrap(), + flags: PageTableFlags::W.union(PageTableFlags::R), + }; + assert_eq!(entry.serialize(), 0b1_000000010_00_00000110); + } + + { + let entry = PageTableEntry { + ppn2: 0x03f0_0000.try_into().unwrap(), + ppn1: 1.try_into().unwrap(), + ppn0: 2.try_into().unwrap(), + flags: PageTableFlags::W.union(PageTableFlags::R), + }; + assert_eq!( + entry.serialize(), + 0b11_1111_0000_0000_0000_0000_0000__000000001__000000010__00__00000110 + ); + } + } +} diff --git a/riscv64/src/platform/virt/devcons.rs b/riscv64/src/platform/virt/devcons.rs index 49f2e73..dd3e848 100644 --- a/riscv64/src/platform/virt/devcons.rs +++ b/riscv64/src/platform/virt/devcons.rs @@ -2,6 +2,7 @@ use core::mem::MaybeUninit; +use crate::sbi::Sbi; use crate::uart16550::Uart16550; use port::{devcons::Console, fdt::DeviceTree}; @@ -14,7 +15,9 @@ pub fn init(dt: &DeviceTree) { .unwrap(); Console::new(|| { - let mut uart = Uart16550::new(ns16550a_reg); + let addr = ns16550a_reg.addr as usize; + let mut uart = Uart16550::new(addr); + uart.init(115_200); static mut UART: MaybeUninit = MaybeUninit::uninit(); @@ -25,3 +28,16 @@ pub fn init(dt: &DeviceTree) { } }); } + +pub fn init_sbi() { + Console::new(|| { + let uart = Sbi::new(); + + static mut UART: MaybeUninit = MaybeUninit::uninit(); + + unsafe { + UART.write(uart); + UART.assume_init_mut() + } + }); +} diff --git a/riscv64/src/platform/virt/mod.rs b/riscv64/src/platform/virt/mod.rs index 1947ba4..fb0d3e9 100644 --- a/riscv64/src/platform/virt/mod.rs +++ b/riscv64/src/platform/virt/mod.rs @@ -1,3 +1,8 @@ pub mod devcons; +use port::println; -pub fn platform_init() {} +pub const PHYSICAL_MEMORY_OFFSET: usize = 0xFFFF_FFFF_4000_0000; + +pub fn platform_init() { + println!("platform_init"); +} diff --git a/riscv64/src/sbi.rs b/riscv64/src/sbi.rs index 395ce76..4a7b9fb 100644 --- a/riscv64/src/sbi.rs +++ b/riscv64/src/sbi.rs @@ -4,6 +4,10 @@ #![cfg_attr(not(target_arch = "riscv64"), allow(dead_code))] +use core::fmt::Error; +use core::fmt::Write; +use port::devcons::Uart; + const SBI_SET_TIMER: usize = 0; const SBI_CONSOLE_PUTCHAR: usize = 1; const SBI_CONSOLE_GETCHAR: usize = 2; @@ -39,7 +43,7 @@ pub fn _set_timer(timer: usize) { } #[deprecated = "expected to be deprecated; no replacement"] -pub fn _consputb(c: u8) { +pub fn consputb(c: u8) { sbi_call_legacy(SBI_CONSOLE_PUTCHAR, c as usize, 0, 0); } @@ -52,3 +56,26 @@ pub fn shutdown() -> ! { sbi_call_legacy(SBI_SHUTDOWN, 0, 0, 0); panic!("shutdown failed!"); } + +pub struct Sbi {} + +impl Write for Sbi { + fn write_str(&mut self, out: &str) -> Result<(), Error> { + for c in out.bytes() { + consputb(c); + } + Ok(()) + } +} + +impl Uart for Sbi { + fn putb(&self, b: u8) { + consputb(b); + } +} + +impl Sbi { + pub fn new() -> Self { + Sbi {} + } +} diff --git a/riscv64/src/uart16550.rs b/riscv64/src/uart16550.rs index 594d02f..a8fa208 100644 --- a/riscv64/src/uart16550.rs +++ b/riscv64/src/uart16550.rs @@ -3,10 +3,9 @@ use core::fmt::Error; use core::fmt::Write; use port::devcons::Uart; -use port::fdt::RegBlock; pub struct Uart16550 { - pub ns16550a_reg: RegBlock, + base: *mut u8, } impl Write for Uart16550 { @@ -20,7 +19,7 @@ impl Write for Uart16550 { impl Uart for Uart16550 { fn putb(&self, b: u8) { - let ptr = self.ns16550a_reg.addr as *mut u8; + let ptr = self.base; unsafe { ptr.add(0).write_volatile(b); } @@ -28,29 +27,37 @@ impl Uart for Uart16550 { } impl Uart16550 { - pub fn new(ns16550a_reg: RegBlock) -> Self { - Uart16550 { ns16550a_reg } + pub fn new(addr: usize) -> Self { + Uart16550 { base: addr as *mut u8 } } + // see also https://www.lookrs232.com/rs232/dlab.htm pub fn init(&mut self, baud: u32) { - let ptr = self.ns16550a_reg.addr as *mut u8; + let ptr = self.base; + let divisor: u16 = (2_227_900 / (baud * 16)) as u16; // set baud rate + let divisor_least: u8 = (divisor & 0xff).try_into().unwrap(); + let divisor_most: u8 = (divisor >> 8).try_into().unwrap(); + let word_length = 3; unsafe { - let lcr = 3; // word length - ptr.add(3).write_volatile(lcr); // set word length - ptr.add(2).write_volatile(1); // enable FIFO - ptr.add(1).write_volatile(1); // enable receiver interrupts - let divisor: u16 = (2_227_900 / (baud * 16)) as u16; // set baud rate - let divisor_least: u8 = (divisor & 0xff).try_into().unwrap(); - let divisor_most: u8 = (divisor >> 8).try_into().unwrap(); - ptr.add(3).write_volatile(lcr | 1 << 7); // access DLAB - ptr.add(0).write_volatile(divisor_least); // DLL - ptr.add(1).write_volatile(divisor_most); // DLM - ptr.add(3).write_volatile(lcr); // close DLAB + // set word length + ptr.add(3).write_volatile(word_length); + // enable FIFO + ptr.add(2).write_volatile(1); + // enable receiver interrupts + ptr.add(1).write_volatile(1); + // access DLAB (Divisor Latch Access Bit) + ptr.add(3).write_volatile(word_length | 1 << 7); + // divisor low byte + ptr.add(0).write_volatile(divisor_least); + // divisor high byte + ptr.add(1).write_volatile(divisor_most); + // close DLAB + ptr.add(3).write_volatile(word_length); } } pub fn put(&mut self, c: u8) { - let ptr = self.ns16550a_reg.addr as *mut u8; + let ptr = self.base; unsafe { ptr.add(0).write_volatile(c); } @@ -58,7 +65,7 @@ impl Uart16550 { #[allow(dead_code)] pub fn get(&mut self) -> Option { - let ptr = self.ns16550a_reg.addr as *mut u8; + let ptr = self.base; unsafe { if ptr.add(5).read_volatile() & 1 == 0 { None diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 0097244..a62131a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -77,7 +77,9 @@ impl BuildParams { } } - fn add_build_arg(&self, cmd: &mut Command) { + /// Append build arguments to the given command. + /// NOTE: Be careful to do this _before_ any `--`, so that `rustc` gets it. + fn add_to_cmd(&self, cmd: &mut Command) { if let Profile::Release = self.profile { cmd.arg("--release"); } @@ -259,7 +261,7 @@ fn build(build_params: &BuildParams) -> Result<()> { cmd.arg("--workspace"); cmd.arg("--exclude").arg("xtask"); exclude_other_arches(build_params.arch, &mut cmd); - build_params.add_build_arg(&mut cmd); + build_params.add_to_cmd(&mut cmd); if build_params.verbose { println!("Executing {cmd:?}"); } @@ -274,12 +276,12 @@ fn expand(build_params: &BuildParams) -> Result<()> { let mut cmd = Command::new(cargo()); cmd.current_dir(workspace()); cmd.arg("rustc"); + build_params.add_to_cmd(&mut cmd); cmd.arg("-Z").arg("build-std=core,alloc"); cmd.arg("-p").arg(build_params.arch.to_string().to_lowercase()); cmd.arg("--target").arg(format!("lib/{}.json", build_params.target())); cmd.arg("--"); cmd.arg("-Z").arg("unpretty=expanded"); - build_params.add_build_arg(&mut cmd); if build_params.verbose { println!("Executing {cmd:?}"); } @@ -294,11 +296,11 @@ fn kasm(build_params: &BuildParams) -> Result<()> { let mut cmd = Command::new(cargo()); cmd.current_dir(workspace()); cmd.arg("rustc"); + build_params.add_to_cmd(&mut cmd); cmd.arg("-Z").arg("build-std=core,alloc"); cmd.arg("-p").arg(build_params.arch.to_string().to_lowercase()); cmd.arg("--target").arg(format!("lib/{}.json", build_params.target())); cmd.arg("--").arg("--emit").arg("asm"); - build_params.add_build_arg(&mut cmd); if build_params.verbose { println!("Executing {cmd:?}"); } @@ -398,7 +400,7 @@ fn test(build_params: &BuildParams) -> Result<()> { cmd.arg("test"); cmd.arg("--workspace"); cmd.arg("--target").arg("x86_64-unknown-linux-gnu"); - build_params.add_build_arg(&mut cmd); + build_params.add_to_cmd(&mut cmd); if build_params.verbose { println!("Executing {cmd:?}"); } @@ -420,7 +422,7 @@ fn clippy(build_params: &BuildParams) -> Result<()> { cmd.current_dir(workspace()); cmd.arg("--workspace"); exclude_other_arches(build_params.arch, &mut cmd); - build_params.add_build_arg(&mut cmd); + build_params.add_to_cmd(&mut cmd); if build_params.verbose { println!("Executing {cmd:?}"); } @@ -501,7 +503,11 @@ fn run(build_params: &BuildParams) -> Result<()> { } cmd.arg("-d").arg("guest_errors,unimp"); cmd.arg("-kernel"); - cmd.arg(format!("target/{}/{}/riscv64", build_params.target(), build_params.dir())); + cmd.arg(format!( + "target/{}/{}/riscv64-qemu", + build_params.target(), + build_params.dir() + )); cmd.current_dir(workspace()); if build_params.verbose { println!("Executing {cmd:?}");