diff --git a/kernel-rs/.gitignore b/kernel-rs/.gitignore new file mode 100644 index 00000000..dd39fc22 --- /dev/null +++ b/kernel-rs/.gitignore @@ -0,0 +1,3 @@ +target +build +Cargo.lock diff --git a/kernel-rs/.gitmodules b/kernel-rs/.gitmodules new file mode 100644 index 00000000..6b9b26f1 --- /dev/null +++ b/kernel-rs/.gitmodules @@ -0,0 +1,6 @@ +[submodule "multiboot2-elf64"] + path = multiboot2-elf64 + url = https://github.com/jzck/multiboot2-elf64.git +[submodule "x86"] + path = x86 + url = https://github.com/jzck/x86.git diff --git a/kernel-rs/.travis.yml b/kernel-rs/.travis.yml new file mode 100644 index 00000000..462f1d9d --- /dev/null +++ b/kernel-rs/.travis.yml @@ -0,0 +1,18 @@ +language: rust +before_install: + - sudo apt-get update + - sudo apt-get install -y nasm build-essential gcc + - cargo install xargo + - rustup override add nightly + - rustup component add rust-src +script: + - make +after_success: | + cargo doc && + echo "" > target/doc/index.html && + sudo pip install ghp-import && + ghp-import -n target/doc && + git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages +env: + global: + secure: KugnyzyUuT4x292BIJoXYBcHbGb1o8961gzrmQzdxCkn0sjg+UI9WUmQj2rZqxxRXCHK9VOaT/vYk9oYXoRv3cISDmsQbJpisUapmil8u3NWn2UVU6oePQrdj2+gF+hy7/K8NeEFE2X8Gv8MX6lsv7NZLTiN67UEpkuxWTrX4RKHDvIQDonUlI+56vgY3hvl4/kI6mE+BSVw9RPK8OWD1T0wwMzAjTCKERonAwI846HDNadWjXRq0+j+n84BJekxICAQRquO2MM94j+MQgCIV0ZQsa9bpG7CfvgLVXLh3NWjEYwkmWFz6hyxLhY4XnG9MNlBh+dN391tDuRoRUlsoI7P1Ym10aYAz1uEEfTQKemVWXOI8DqZDJL0MO3y9+LePb9hTjJ9GOoQ7q9v6v1l9MzWYCy4KBv4QWDEpIKsa7WM/ExfqlZ7MuVW6rzTZmmCScElSk295Z6aH993FrzsyREP16Ch2RniewA598lwinahYR8eYXKjkasi1WTJcLMvQN9nnUe0vhFUFNXPDQPLeX7ZEvzvvcOBY7kZG5zHUeXzIWq7D7tXZTqu/48cgwTvjQ5Q/FARXoffd+RaX/nZInVMdeI7phruOEawUNPXSEdNjNxIedi4PPnIRXT+DgstQeBSKrgl7HYNwnFC3Q+NfoU7oWoyivUtcQ1xZZj03f8= diff --git a/kernel-rs/Cargo.toml b/kernel-rs/Cargo.toml new file mode 100644 index 00000000..293bdd48 --- /dev/null +++ b/kernel-rs/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "bluesnow" +version = "0.1.0" +authors = ["Jack Halford ", "William Escande "] + +[lib] +crate-type = ["staticlib"] + +[dependencies] +rlibc = "1.0" +bitflags = "1.0.1" +spin = "0.4" +slab_allocator = "0.3.5" +x86 = { path = "x86" } # forked for IA-32 compat +multiboot2 = { path = "multiboot2-elf64" } # forked to add rsdp tag + +[dependencies.raw-cpuid] +# need to use github/master because of features not yet on crates.io +git = "https://github.com/gz/rust-cpuid" +features = ["nightly"] + +[dependencies.lazy_static] +version = "1.0.0" +features = ["spin_no_std"] + +#[dependencies.compiler_builtins] +##I'm waiting for somebody to port i386/udivdi3.S ... :((( +#git = "https://github.com/rust-lang-nursery/compiler-builtins" diff --git a/kernel-rs/Makefile b/kernel-rs/Makefile new file mode 100644 index 00000000..cf398440 --- /dev/null +++ b/kernel-rs/Makefile @@ -0,0 +1,46 @@ +SHELL := /bin/bash + +ARCH := x86 +OS := bluesnow +TARGET ?= $(ARCH)-$(OS) + +all: + @printf "make kernel\t# build kernel binary\n" + @printf "make iso\t# build iso cdrom\n" + @printf "make qemu\t# run iso in qemu\n" + +## COMPILE ASM (nasm) +asm_source := $(wildcard src/arch/$(ARCH)/*.asm) +asm_object := $(patsubst src/arch/$(ARCH)/%.asm, build/arch/$(ARCH)/%.o, $(asm_source)) +NASM := /usr/bin/nasm -f elf -gdwarf +build/arch/$(ARCH)/%.o: src/arch/$(ARCH)/%.asm Makefile + @mkdir -p $(shell dirname $@) + $(NASM) $< -o $@ + +## COMPILE RUST (xargo) +rust_os := target/$(TARGET)/debug/lib$(OS).a +$(rust_os): $(TARGET).json Makefile + TERM=xterm RUST_TARGET_PATH="$(shell pwd)" xargo build --target $(TARGET) + +## LINKAGE +KERNEL := build/$(OS)-$(ARCH).bin +linker_script := src/arch/$(ARCH)/linker.ld +LD := /usr/bin/ld -m elf_i386 -L ./ -n --gc-sections +$(KERNEL): $(rust_os) $(asm_object) $(linker_script) Makefile + $(LD) -o $@ -T $(linker_script) $(asm_object) $(rust_os) + +clean: + xargo clean + rm -rf build + +.PHONY: clean kernel iso $(rust_os) + +# Bootloader recipes +ISO := $(KERNEL:.bin=.iso) +iso: $(ISO) +include mk/grub.mk + +# Emulation recipes +include mk/qemu.mk + +kernel: $(KERNEL) diff --git a/kernel-rs/README.md b/kernel-rs/README.md new file mode 100644 index 00000000..cb0340cc --- /dev/null +++ b/kernel-rs/README.md @@ -0,0 +1,48 @@ +# !! Switched to ziglang -> [new repo here](https://github.com/jzck/kernel-zig) !! + +Kernel from scratch (KFS) series of projects at Ecole 42 ! + +### [documentation](https://jzck.github.io/kernel/bluesnow/index.html) + +# building + +`git submodule udpate --init` + + - `nasm` compiles the bootcode + - `ld` links the bootcode and rust binary + - `grub-mkrescue` builds the iso (need xorriso and mtools) + - `xargo` builds rust code + +See `.travis.yml` to get an ubuntu environment ready +on archlinux `pacman -S rustup make grub xorriso mtools binutils gcc qemu` +on voidlinux `xbps-install -S rustup make grub xorriso mtools binutils gcc qemu nasm` + + +#### rust setup + +We build on nightly channel because of some cool features not yet in stable. +We need the rust sources to build with xargo for cross-compiling to custom platform. + +``` +rustup component add rust-src +rustup override add nightly +rustup default nightly +cargo install xargo +``` + +# running + + - `make iso` builds a bootable iso with grub + - `make qemu` runs the iso, + - `make qemu-reload` reloads the CD + +# todo + + - remove assembly for a pure rust entry point + - replace grub with something lighter (no bootloader at all with `qemu -kernel` ?) + +# inspiration + + - [wiki.osdev.org](https://wiki.osdev.org) is a fucking goldmine + - [Phil Opperman's "Writing an OS in rust"](https://os.phil-opp.com/) + - [Redox kernel](https://github.com/redox/kernel) diff --git a/kernel-rs/Xargo.toml b/kernel-rs/Xargo.toml new file mode 100644 index 00000000..91b4c5ff --- /dev/null +++ b/kernel-rs/Xargo.toml @@ -0,0 +1,2 @@ +[target.x86-bluesnow.dependencies] +alloc = {} diff --git a/kernel-rs/mk/grub.mk b/kernel-rs/mk/grub.mk new file mode 100644 index 00000000..78240426 --- /dev/null +++ b/kernel-rs/mk/grub.mk @@ -0,0 +1,8 @@ +grub-cfg := src/arch/$(ARCH)/grub.cfg +isodir := build/isofiles + +$(ISO): $(KERNEL) $(grub-cfg) Makefile + @mkdir -p $(isodir)/boot/grub + @cp $(grub-cfg) $(isodir)/boot/grub + @cp $(KERNEL) $(isodir)/boot/$(OS) + grub-mkrescue -o $@ $(isodir) 2>/dev/null diff --git a/kernel-rs/mk/qemu.mk b/kernel-rs/mk/qemu.mk new file mode 100644 index 00000000..014477be --- /dev/null +++ b/kernel-rs/mk/qemu.mk @@ -0,0 +1,27 @@ +QEMU_SOCKET := /tmp/qemu.sock +QEMU_MONITOR := socat - unix-connect:$(QEMU_SOCKET) +QEMU_GDB_PORT := 4242 + +qemu: + qemu-system-i386\ + -cdrom $(ISO)\ + -S\ + -enable-kvm\ + -curses\ + -gdb tcp::$(QEMU_GDB_PORT)\ + -monitor unix:${QEMU_SOCKET},server,nowait\ + -drive file=disk,if=ide,index=1 + +qemu-gdb: + gdb\ + -q\ + -symbols "$(KERNEL)" \ + -ex "target remote :$(QEMU_GDB_PORT)"\ + -ex "set arch i386" + +qemu-monitor: + $(QEMU_MONITOR) +qemu-reload: + echo "stop" | $(QEMU_MONITOR) &>/dev/null + echo "change ide1-cd0 $(ISO)" | $(QEMU_MONITOR) &>/dev/null + echo "system_reset" | $(QEMU_MONITOR) &>/dev/null diff --git a/kernel-rs/multiboot2-elf64 b/kernel-rs/multiboot2-elf64 new file mode 160000 index 00000000..32552947 --- /dev/null +++ b/kernel-rs/multiboot2-elf64 @@ -0,0 +1 @@ +Subproject commit 32552947aa30414a96d9a8e291aceaf18320b3ef diff --git a/kernel-rs/src/acpi/dsdt.rs b/kernel-rs/src/acpi/dsdt.rs new file mode 100644 index 00000000..b93d407e --- /dev/null +++ b/kernel-rs/src/acpi/dsdt.rs @@ -0,0 +1,98 @@ +use super::{check_signature, ACPISDTHeader}; +use core::mem; +use x86::devices::io::{Io, Pio}; + +static mut DSDT: DSDT = DSDT { + valid: false, + dsdt: None, + s5_ptr: 0, + slp_typ_a: 0, + slp_typ_b: 0, +}; + +struct DSDT { + valid: bool, + dsdt: Option<&'static ACPISDTHeader>, + s5_ptr: u32, + slp_typ_a: u16, + slp_typ_b: u16, +} + +impl DSDT { + fn init(&mut self, addr: u32) -> Result<(), &'static str> { + self.dsdt = Some(unsafe { &(*(addr as *const ACPISDTHeader)) }); + self.s5_ptr = self.find_s5(addr)?; + self.parse_s5(); + self.valid = true; + Ok(()) + } + + fn find_s5(&self, addr: u32) -> Result { + let dsdt_start = addr + mem::size_of::() as u32; + let dsdt_end = dsdt_start + self.dsdt.unwrap().length; + for addr in dsdt_start..dsdt_end { + if check_signature(addr, "_S5_") { + if (check_signature(addr - 1, "\x08") || check_signature(addr - 2, "\x08\\")) + && check_signature(addr + 4, "\x12") + { + return Ok(addr); + } + } + } + Err("Can not find S5 section in DSDT") + } + + fn parse_s5(&mut self) { + let ptr = self.s5_ptr + 5; + let ptr = ((unsafe { *(ptr as *const u8) } & 0xC0) >> 6) + 2; + let ptr = if unsafe { *(ptr as *const u8) } == 0x0A { + ptr + 1 + } else { + ptr + }; // Skip bytePrefix + self.slp_typ_a = (unsafe { *(ptr as *const u8) } as u16) << 10; + let ptr = ptr + 1; + let ptr = if unsafe { *(ptr as *const u8) } == 0x0A { + ptr + 1 + } else { + ptr + }; // Skip bytePrefix + self.slp_typ_b = (unsafe { *(ptr as *const u8) } as u16) << 10; + } +} + +fn is_init() -> Result<(), &'static str> { + match unsafe { DSDT.valid } { + true => Ok(()), + false => match unsafe { DSDT.dsdt } { + Some(_) => Err("Differentiated System Description Pointer (DSDP) is not valid"), + None => Err("Differentiated System Description Pointer (DSDP) is not initialized"), + }, + } +} + +/// ## Initialize Differentiated System Description Table (DSDT) +/// input param addr is contain in FADT +pub fn init(addr: u32) -> Result<(), &'static str> { + if ACPISDTHeader::valid(addr, "DSDT") { + return unsafe { DSDT.init(addr) }; + } + return Err("Can not find Differentiated System Description Table (DSDT)."); +} + +/// NOT COMPATIBLE WITH VIRTUALBOX +/// Send shutdown signal +/// outw(PM1a_CNT_BLK, SLP_TYPx | SLP_EN) +pub fn shutdown(pm1_cnt: [u16; 2]) -> Result<(), &'static str> { + is_init()?; + let slp_typ = unsafe { DSDT.slp_typ_a } | (1 << 13); + let mut pin: Pio = Pio::new(pm1_cnt[0]); + pin.write(slp_typ); + if pm1_cnt[1] != 0 { + let slp_typ = unsafe { DSDT.slp_typ_b } | (1 << 13); + let mut pin: Pio = Pio::new(pm1_cnt[1]); + pin.write(slp_typ); + // cpuio::outw(pm1_cnt[1], slp_typ); + } + Ok(()) +} diff --git a/kernel-rs/src/acpi/fadt.rs b/kernel-rs/src/acpi/fadt.rs new file mode 100644 index 00000000..95f7c244 --- /dev/null +++ b/kernel-rs/src/acpi/fadt.rs @@ -0,0 +1,165 @@ +use super::{ACPISDTHeader, ACPISDTIter}; +use x86::devices::io::{Io, Pio}; + +#[repr(C)] +#[derive(Debug, Clone)] +struct GenericAddressStructure { + addressspace: u8, + bitwidth: u8, + bitoffset: u8, + accesssize: u8, + noused: u32, + address: u64, +} + +#[repr(C)] +#[derive(Debug, Clone)] +struct FADT { + header: ACPISDTHeader, + firmwarectrl: u32, + dsdt: u32, + + // field used in acpi 1.0; no longer in use, for compatibility only + reserved: u8, + + preferredpowermanagementprofile: u8, + sci_interrupt: u16, + smi_commandport: u32, + acpi_enable: u8, + acpidisable: u8, + s4bios_req: u8, //no use + pstate_control: u8, //no use + pm1aeventblock: u32, //no use + pm1beventblock: u32, //no use + pm1acontrolblock: u32, + pm1bcontrolblock: u32, + pm2controlblock: u32, //no use + pmtimerblock: u32, //no use + gpe0block: u32, //no use + gpe1block: u32, //no use + pm1eventlength: u8, //no use + pm1controllength: u8, + pm2controllength: u8, //no use + pmtimerlength: u8, //no use + gpe0length: u8, //no use + gpe1length: u8, //no use + gpe1base: u8, //no use + cstatecontrol: u8, //no use + worstc2latency: u16, //no use + worstc3latency: u16, //no use + flushsize: u16, //no use + flushstride: u16, //no use + dutyoffset: u8, //no use + dutywidth: u8, //no use + dayalarm: u8, //no use + monthalarm: u8, //no use + century: u8, //no use + + // reserved in acpi 1.0; used since acpi 2.0+ + bootarchitectureflags: u16, + + reserved2: u8, + flags: u32, + + // 12 byte structure; see below for details + resetreg: GenericAddressStructure, + + resetvalue: u8, + reserved3: [u8; 3], + + // 64bit pointers - Available on ACPI 2.0+ + x_firmwarecontrol: u64, + x_dsdt: u64, + + x_pm1aeventblock: GenericAddressStructure, + x_pm1beventblock: GenericAddressStructure, + x_pm1acontrolblock: GenericAddressStructure, + x_pm1bcontrolblock: GenericAddressStructure, + x_pm2controlblock: GenericAddressStructure, + x_pmtimerblock: GenericAddressStructure, + x_gpe0block: GenericAddressStructure, + x_gpe1block: GenericAddressStructure, +} + +static mut FADT: Option = None; + +/// ## Initialize Fixed ACPI Description Table (FADT) +/// input param addr is contain in other ptr of rsdt +pub fn init(sdt_iter: ACPISDTIter) -> Result<(), &'static str> { + for sdt_ptr in sdt_iter { + if ACPISDTHeader::valid(sdt_ptr, "FACP") { + // Where is "FADT"? Shut up is magic + let fadt_tmp: FADT = unsafe { (*(sdt_ptr as *const FADT)).clone() }; + unsafe { FADT = Some(fadt_tmp.clone()) }; + if !is_enable()? { + // TODO do i have to check if enabled before init ??? + let smi_cmd = fadt_tmp.smi_commandport as u16; // TODO WHY DO I NEED THIS FUCKING CAST + let acpi_enable = fadt_tmp.acpi_enable; + //TODO not sexy it ! + let mut pin: Pio = Pio::new(smi_cmd); + pin.write(acpi_enable); + // cpuio::outb(smi_cmd, acpi_enable); // send acpi enable command + } + return Ok(()); + } + } + return Err("Can not find Fixed ACPI Description Table (FADT)."); +} + +fn is_init() -> Result { + match unsafe { FADT.clone() } { + Some(fadt) => Ok(fadt), + None => Err("Fixed ACPI Description Table (FADT) is not initialized"), + } +} + +/// Return Dsdt address +/// FADT must have been initialized first +pub fn dsdtaddr() -> Result { + let fadt = is_init()?; + return Ok(fadt.dsdt); +} + +fn get_cnt(fadt: FADT) -> [u16; 2] { + [fadt.pm1acontrolblock as u16, fadt.pm1bcontrolblock as u16] // TODO WHY DO I NEED THIS FUCKING CAST +} + +/// Return true/false depending of acpi is enable +pub fn is_enable() -> Result { + let fadt = is_init()?; + let pm1_cnt = get_cnt(fadt); + let pin: Pio = Pio::new(pm1_cnt[0]); + if pm1_cnt[1] == 0 { + Ok(pin.read() & 0x1 == 0x1) + // Ok(cpuio::inw(pm1_cnt[0]) & 0x1 == 0x1) + } else { + let pin2: Pio = Pio::new(pm1_cnt[1]); + Ok(pin.read() & 0x1 == 0x1 || pin2.read() & 0x1 == 0x1) + // Ok(cpuio::inw(pm1_cnt[0]) & 0x1 == 0x1 || cpuio::inw(pm1_cnt[1]) & 0x1 == 0x1) + } +} + +/// Return a array with [pm1a, pm1b] +/// FADT must have been initialized first +pub fn get_controlblock() -> Result<[u16; 2], &'static str> { + if !is_enable()? { + Err("ACPI is not enabled") + } else { + Ok(get_cnt(is_init()?)) // TODO redondant call to is_init + } +} +pub fn reboot() -> Result<(), &'static str> { + if !is_enable()? { + Err("ACPI is not enabled") + } else { + let fadt = is_init()?; + println!( + "fadt on {} ({}), value is {}", + fadt.resetreg.address as u32, fadt.resetreg.address as u16, fadt.resetvalue + ); + let mut pin: Pio = Pio::new(fadt.resetreg.address as u16); + pin.write(fadt.resetvalue); + // cpuio::outb(fadt.resetreg.address as u16, fadt.resetvalue); //TODO do it work + Ok(()) + } +} diff --git a/kernel-rs/src/acpi/mod.rs b/kernel-rs/src/acpi/mod.rs new file mode 100644 index 00000000..58882099 --- /dev/null +++ b/kernel-rs/src/acpi/mod.rs @@ -0,0 +1,167 @@ +mod rsdp; +mod rsdt; +mod xsdt; +mod fadt; +mod dsdt; + +use core; +use core::mem; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct ACPISDTHeader { + signature: [u8; 4], + length: u32, + revision: u8, + checksum: u8, + oemid: [u8; 6], + oemtableid: [u8; 8], + oemrevision: u32, + creatorid: u32, + creatorrevision: u32, +} + +impl ACPISDTHeader { + pub fn valid(addr: u32, signature: &str) -> bool { + if check_signature(addr, signature) { + let ptr_tmp = addr as *const ACPISDTHeader; + if check_checksum(addr, unsafe { (*ptr_tmp).length } as usize) { + return true; + } + } + return false; + } +} + +static mut ACPI: Acpi = Acpi { + valid: false, + v2: false, +}; + +struct Acpi { + valid: bool, + v2: bool, +} + +impl Acpi { + fn common_init(&mut self) -> Result<(), &'static str> { + if self.v2 { + // Xsdt Address: + // 64-bit physical address of the XSDT table. If you detect ACPI Version 2.0 you should use this table instead of RSDT even on x86, casting the address to uint32_t. + xsdt::init(rsdp::xsdtaddr()?)?; + fadt::init(xsdt::iter()?)?; + } else { + rsdt::init(rsdp::rsdtaddr()?)?; + fadt::init(rsdt::iter()?)?; + } + dsdt::init(fadt::dsdtaddr()?)?; + self.valid = true; + Ok(()) + } + fn init(&mut self) -> Result<(), &'static str> { + self.v2 = rsdp::init()?; + self.common_init() + } + fn load(&mut self, rsdp_addr: u32) -> Result<(), &'static str> { + self.v2 = rsdp::load(rsdp_addr)?; + self.common_init() + } +} + +fn check_signature(addr: u32, id: &str) -> bool { + let signature = match core::str::from_utf8(unsafe { + core::slice::from_raw_parts_mut(addr as *mut u8, id.len()) + }) { + Ok(y) => y, + Err(_) => return false, + }; + return signature == id; +} + +fn check_checksum(addr: u32, len: usize) -> bool { + let byte_array = unsafe { core::slice::from_raw_parts_mut(addr as *mut u8, len) }; + let mut sum: u32 = 0; + for byte in byte_array { + sum += *byte as u32; + } + return sum as u8 == 0; +} + +pub struct ACPISDTIter { + pos: usize, + width: usize, + sdt: u32, + len: usize, +} + +impl ACPISDTIter { + fn new( + acpi_sdt: Option<*const ACPISDTHeader>, + ptr_len: usize, + ) -> Result { + match acpi_sdt { + None => Err("There is no ACPI System Description Table (ACPISDTHeader) to iter on."), + Some(ptr) => Ok(ACPISDTIter { + pos: 0, + width: ptr_len, + sdt: ptr as u32 + mem::size_of::() as u32, + len: (unsafe { (*ptr).length } as usize - mem::size_of::()) + / ptr_len, + }), + } + } +} + +impl Iterator for ACPISDTIter { + type Item = u32; + + fn next(&mut self) -> Option { + self.pos += 1; + if self.pos > self.len { + return None; + } + let ret = Some(unsafe { *(self.sdt as *const u32) }); + self.sdt += self.width as u32; + return ret; + } +} + +/// Initalized the ACPI module +pub fn init() -> Result<(), &'static str> { + if unsafe{ACPI.valid} { return Ok(()); } + unsafe { ACPI.init() } +} + +/// Load the ACPI module, addr given is a ptr to RSDP +pub fn load(rsdp_addr: u32) -> Result<(), &'static str> { + if unsafe{!ACPI.valid} { return Ok(()); } + unsafe { ACPI.load(rsdp_addr) } +} + +/// Proceed to ACPI shutdown +/// This function doesn't work with Virtual Box yet +pub fn shutdown() { + if unsafe{!ACPI.valid} { return; } + dsdt::shutdown(fadt::get_controlblock().unwrap()).unwrap(); +} + +/// Proceed to ACPI reboot +/// This function need ACPI in v2 +pub fn reboot() { + if unsafe {!ACPI.valid} { println!("ACPI not initialized"); } + if unsafe { ACPI.v2 } { + fadt::reboot().unwrap() + } else { + println!("ACPI reboot only available in ACPI v2+"); + } +} + +/// Display state of ACPI +pub fn info() { + if unsafe { !ACPI.valid } { println!("ACPI not initialized"); return } + match fadt::is_enable() { + Ok(true) => println!("ACPI is disabled"), + Ok(false) => println!("ACPI is disabled"), + Err(msg) => println!("error while checking ACPI: {}", msg) + } +} diff --git a/kernel-rs/src/acpi/rsdp.rs b/kernel-rs/src/acpi/rsdp.rs new file mode 100644 index 00000000..028f1451 --- /dev/null +++ b/kernel-rs/src/acpi/rsdp.rs @@ -0,0 +1,85 @@ +use super::{check_checksum, check_signature}; +use core::mem; + +#[repr(C)] +#[derive(Clone)] +struct RSDP { + signature: [u8; 8], + checksum: u8, + oemid: [u8; 6], + revision: u8, + rsdtaddr: u32, +} + +#[repr(C)] +#[derive(Clone)] +pub struct RSDP20 { + rsdp: RSDP, + length: u32, + xsdtaddress: u64, + extendedchecksum: u8, + reserved: [u8; 3], +} + +static mut RSDPTR: Option = None; + +/// RSDP load will check is RSDP is present at the addr sent. +/// Return a bool +/// true => RSDP is V2 +/// false => RSDP is V1 +pub fn load(addr: u32) -> Result { + if check_signature(addr, "RSD PTR ") { + let rsdp_tmp: RSDP20 = unsafe { (*(addr as *const RSDP20)).clone() }; + let revision = rsdp_tmp.rsdp.revision; + if (revision == 0 && check_checksum(addr, mem::size_of::())) + || (revision == 2 && check_checksum(addr, mem::size_of::())) + { + unsafe { RSDPTR = Some(rsdp_tmp) }; + return Ok(revision == 2); + } + } + Err("Not a valid RSD ptr") +} + +fn memory_finding() -> Result { + let mut i = 0; + while i < 0x1000000 { + i += 8; + if let Ok(result) = load(i) { + return Ok(result); + } + } + Err("Can not find Root System Description Pointer (RSDP).") +} + +fn is_init() -> Result { + match unsafe { RSDPTR.clone() } { + Some(rsdptr) => Ok(rsdptr), + None => Err("Root System Description Pointer (RSDP) is not initialized"), + } +} + +/// Return a ptr on xsdt +/// RSDP must have been initialized first +pub fn xsdtaddr() -> Result { + let rsdptr = is_init()?; + let revision = rsdptr.rsdp.revision; + if revision != 2 { + return Err("Wrong RSDP version asked"); + } + return Ok(rsdptr.xsdtaddress); +} + +/// Return a ptr on rsdt +/// RSDP must have been initialized first +pub fn rsdtaddr() -> Result { + let rsdptr = is_init()?; + return Ok(rsdptr.rsdp.rsdtaddr); +} + +/// RSDP init will iter on addr in [0x0 - 0x1000000] to find "RSDP PTR " +/// if you already know the location, you should prefer to use load function +/// return an Error if there is no RSDP in memory, or return the value of load function +pub fn init() -> Result { + memory_finding() +} diff --git a/kernel-rs/src/acpi/rsdt.rs b/kernel-rs/src/acpi/rsdt.rs new file mode 100644 index 00000000..59ed0fb0 --- /dev/null +++ b/kernel-rs/src/acpi/rsdt.rs @@ -0,0 +1,20 @@ +use super::{ACPISDTHeader, ACPISDTIter}; + +//TODO this can work only if pagging is disabled +static mut RSDT: Option<*const ACPISDTHeader> = None; + +/// ## Initialize Root System Description Table (RSDT) +/// input param addr is contain in RSDP +pub fn init(addr: u32) -> Result<(), &'static str> { + if ACPISDTHeader::valid(addr, "RSDT") { + unsafe { RSDT = Some(addr as *const ACPISDTHeader) }; + return Ok(()); + } + return Err("Can not find Root System Description Table (RSDT)."); +} + +/// Return a iterable of ptr contained in RSDT +/// RSDT must have been initialized first +pub fn iter() -> Result { + ACPISDTIter::new(unsafe { RSDT }, 4) +} diff --git a/kernel-rs/src/acpi/xsdt.rs b/kernel-rs/src/acpi/xsdt.rs new file mode 100644 index 00000000..eb05c1ae --- /dev/null +++ b/kernel-rs/src/acpi/xsdt.rs @@ -0,0 +1,22 @@ +use super::{ACPISDTHeader, ACPISDTIter}; + +//TODO this can work only if pagging is disabled +static mut XSDT: Option<*const ACPISDTHeader> = None; + +/// ## Initialize Root System Description Table (XSDT) +/// input param addr is contain in RSDP +pub fn init(addr: u64) -> Result<(), &'static str> { + assert!((addr as u32) as u64 == addr); + let addr: u32 = addr as u32; + if ACPISDTHeader::valid(addr, "XSDT") { + unsafe { XSDT = Some(addr as *const ACPISDTHeader) }; + return Ok(()); + } + return Err("Can not find eXtended System Descriptor Table (XSDT)."); +} + +/// Return a iterable of ptr contained in XSDT +/// XSDT must have been initialized first +pub fn iter() -> Result { + ACPISDTIter::new(unsafe { XSDT }, 8) +} diff --git a/kernel-rs/src/allocator/mod.rs b/kernel-rs/src/allocator/mod.rs new file mode 100644 index 00000000..6c5fb136 --- /dev/null +++ b/kernel-rs/src/allocator/mod.rs @@ -0,0 +1,27 @@ +// pub use self::ALLOCATOR; + +use x86::structures::paging::*; +use arch::x86::paging::*; + +fn map_heap(active_table: &mut ActivePageTable) { + //zone for heap is predefined in `consts.rs` + for page in ::KERNEL_HEAP_RANGE { + active_table.map(page, PageTableFlags::WRITABLE); + } +} + +/// should be called only once +pub unsafe fn init(active_table: &mut ActivePageTable) { + let offset = ::KERNEL_HEAP_OFFSET; + let size = ::KERNEL_HEAP_SIZE; + + map_heap(active_table); + + //slab allocator + super::ALLOCATOR.init(offset.as_u32() as usize, size as usize); +} + +#[alloc_error_handler] +fn foo(_: core::alloc::Layout) -> ! { + panic!("alloc_error_handler"); +} diff --git a/kernel-rs/src/arch/mod.rs b/kernel-rs/src/arch/mod.rs new file mode 100644 index 00000000..baf9581d --- /dev/null +++ b/kernel-rs/src/arch/mod.rs @@ -0,0 +1,4 @@ +// we only support a single architecture at the moment +// we call it x86 but we only support all 32bit +// intel architectures (IA-32) such as i[3456]86 +pub mod x86; diff --git a/kernel-rs/src/arch/x86/boot.asm b/kernel-rs/src/arch/x86/boot.asm new file mode 100644 index 00000000..892fc549 --- /dev/null +++ b/kernel-rs/src/arch/x86/boot.asm @@ -0,0 +1,98 @@ +global start +extern x86_start + +section .text +bits 32 +start: + + ; our stack, located in bss, linker.ld puts bss at the end of the binary + mov esp, stack_top + + ; multiboot information pointer + push ebx + + call check_multiboot + call set_up_page_tables + + ; load the new gdt + lgdt [GDTR.ptr] + jmp GDTR.gdt_cs:x86_start + +check_multiboot: + cmp eax, 0x36d76289 + jne .no_multiboot + ret +.no_multiboot: + mov al, "0" + jmp error + +; minimal page tables (first 8MB identity mapped) +; core page tables will be loaded in rust +set_up_page_tables: + ; map P2 table recursively + mov eax, p2_table + or eax, 0b11 ; present + writable + mov [p2_table + 1023 * 4], eax + + ; identity map first P2 entry to a huge page + mov eax, 0x0 ; 0MB -> 4MB (first page) + or eax, 0b10000011 ; huge + present + writable + mov [p2_table], eax + + mov eax, 0x400000 ; 4MB -> 8Mb (second page) + or eax, 0b10000011 ; huge + present + writable + mov [p2_table + 4], eax + + mov eax, 0x800000 ; 8MB -> 12Mb (third page) + or eax, 0b10000011 ; huge + present + writable + mov [p2_table + 8], eax + + mov eax, p2_table + mov cr3, eax + ret + +error: + mov dword [0xb8000], 0x4f524f45 + mov dword [0xb8004], 0x4f3a4f52 + mov dword [0xb8008], 0x4f204f20 + mov byte [0xb800a], al + cli +HALT: + hlt + jmp HALT + +section .bss +align 4096 +p2_table: + resb 4096 +stack_bottom: + resb 4096 * 4 +stack_top: + +; minimal boot gdt (cs & ds) +; core gdt will be loaded in rust +section .gdt +GDTR: +; http://tuttlem.github.io/2014/07/11/a-gdt-primer.html +.gdt_top: + DD 0, 0 +.gdt_cs: equ $ - .gdt_top; the code segment Aka KERNEL CODE + DW 0xffff ; Limit ( bits 0 -15 ) + DW 0x0 ; Base ( bits 0 -15 ) + DB 0x0 ; Base ( bits 16 -23 ) + DB 0x9A ; [ Access Flags: 0x9A=10011010b = (present)|(Privilege Ring 0=00b)|(1)|(code => 1)|(expand down => 0)|(readable)|(0) ] + DB 0xCF ; [ Flags: C=1100b = (granularity)|(32bit)|(!64bit)|(0) ] / [ Limits: (bits 16-19): F=1111b ] + DB 0x0 ; Base ( bits 24 -31 ) + +.gdt_ds: equ $ - .gdt_top; the data segment Aka KERNEL DATA + DW 0xffff ; Limit ( bits 0 -15 ) + DW 0x0 ; Base ( bits 0 -15 ) + DB 0x0 ; Base ( bits 16 -23 ) + DB 0x92 ; [ Access Flags: 0x92=10010010b = (present)|(Privilege Ring 0=00b)|(1)|(data => 0)|(expand down => 0)|(readable)|(0) ] + DB 0xCF ; [ Flags: C=1100b = (granularity)|(32bit)|(!64bit)|(0) ] / [ Limits: (bits 16-19): F=1111b ] + DB 0x0 ; Base ( bits 24 -31 ) + +.gdt_bottom: +.ptr: + DW .gdt_bottom - .gdt_top - 1 ; length of the structure minus 1 + DD .gdt_top ; pointer to top of gdt diff --git a/kernel-rs/src/arch/x86/consts.rs b/kernel-rs/src/arch/x86/consts.rs new file mode 100644 index 00000000..d1fb4a29 --- /dev/null +++ b/kernel-rs/src/arch/x86/consts.rs @@ -0,0 +1,52 @@ +// p2 layout looks like this: +// [kernel 0->4MiB] +// [kernel 4->8MiB] +// [start of no man's land] +// . +// . +// . +// [end of no man's land] +// [user stack] +// [kernel heap] +// [recursive map] points to first entry +// +// no man's land should be used in the future for mmap() I guess +// the kernel right now takes up 2 pages with debug symbols, +// if it gets to 3 pages everything will crash and i'll have +// to hardcode the third page here and in `boot.asm` also! +// +// TODO +// kernel is mapped identically (start of memory) but in +// the future it should be higher-half mapped so that +// user memory starts at 0 + +use x86::structures::paging::*; +use x86::*; +use core::ops::Range; + +// macro_rules! prange { ($i:expr, $s:expr) => {$i..$i+$s+1}} + +// all of this is fucking contrived +// the rust compiler should be able to evaluate const expressions +// by using the impl for my structs, what i'm doing here is obvious +// to me but not to the compiler + +pub const RECURSIVE_PAGE_OFFSET: VirtAddr = VirtAddr(0xffc0_000); // first 10 bits +pub const RECURSIVE_PAGE: Page = Page::containing_address(RECURSIVE_PAGE_OFFSET); +pub const RECURSIVE_PAGE_SIZE: u32 = 0x0040_0000; // the whole p2 entry + +pub const KERNEL_HEAP_OFFSET: VirtAddr = VirtAddr(RECURSIVE_PAGE_OFFSET.0 - RECURSIVE_PAGE_SIZE); +// should be +// pub const KERNEL_HEAP_OFFSET: VirtAddr = RECURSIVE_PAGE_OFFSET - RECURSIVE_PAGE_SIZE); +pub const KERNEL_HEAP_SIZE: u32 = 0x0040_0000; //4MiB (1 huge page) +pub const KERNEL_HEAP_START: Page = Page::containing_address(KERNEL_HEAP_OFFSET); +pub const KERNEL_HEAP_END: Page = + Page::containing_address(VirtAddr(KERNEL_HEAP_OFFSET.0 + KERNEL_HEAP_SIZE)); +pub const KERNEL_HEAP_RANGE: Range = KERNEL_HEAP_START..KERNEL_HEAP_END; + +pub const USER_STACK_OFFSET: VirtAddr = VirtAddr(KERNEL_HEAP_OFFSET.0 - KERNEL_HEAP_SIZE); +pub const USER_STACK_START: Page = Page::containing_address(USER_STACK_OFFSET); +pub const USER_STACK_SIZE: u32 = 0x0040_0000; //4MiB (1 huge page) +pub const USER_STACK_END: Page = + Page::containing_address(VirtAddr(USER_STACK_OFFSET.0 + USER_STACK_SIZE)); +pub const USER_STACK_RANGE: Range = USER_STACK_START..USER_STACK_END; diff --git a/kernel-rs/src/arch/x86/devices/cpu.rs b/kernel-rs/src/arch/x86/devices/cpu.rs new file mode 100644 index 00000000..111ba009 --- /dev/null +++ b/kernel-rs/src/arch/x86/devices/cpu.rs @@ -0,0 +1,286 @@ +extern crate raw_cpuid; + +use self::raw_cpuid::CpuId; + +pub fn cpu_info() { + let cpuid = CpuId::new(); + + if let Some(info) = cpuid.get_vendor_info() { + println!("Vendor: {}", info.as_string()); + } + + if let Some(info) = cpuid.get_extended_function_info() { + if let Some(brand) = info.processor_brand_string() { + println!("Model: {}", brand); + } + } + + if let Some(info) = cpuid.get_processor_frequency_info() { + println!("Base MHz: {}", info.processor_base_frequency()); + println!("Max MHz: {}", info.processor_max_frequency()); + println!("Bus MHz: {}", info.bus_frequency()); + } else { + set_color!(Red); + println!("Couldn't retrieve cpu frequency info"); + set_color!(); + } + + if let Some(info) = cpuid.get_feature_info() { + print!("Features:"); + if info.has_fpu() { + print!(" fpu") + }; + if info.has_vme() { + print!(", vme") + }; + if info.has_de() { + print!(", de") + }; + if info.has_pse() { + print!(", pse") + }; + if info.has_tsc() { + print!(", tsc") + }; + if info.has_msr() { + print!(", msr") + }; + if info.has_pae() { + print!(", pae") + }; + if info.has_mce() { + print!(", mce") + }; + + if info.has_cmpxchg8b() { + print!(", cx8") + }; + if info.has_apic() { + print!(", apic") + }; + if info.has_sysenter_sysexit() { + print!(", sep") + }; + if info.has_mtrr() { + print!(", mtrr") + }; + if info.has_pge() { + print!(", pge") + }; + if info.has_mca() { + print!(", mca") + }; + if info.has_cmov() { + print!(", cmov") + }; + if info.has_pat() { + print!(", pat") + }; + + if info.has_pse36() { + print!(", pse36") + }; + if info.has_psn() { + print!(", psn") + }; + if info.has_clflush() { + print!(", clflush") + }; + if info.has_ds() { + print!(", ds") + }; + if info.has_acpi() { + print!(", acpi") + }; + if info.has_mmx() { + print!(", mmx") + }; + if info.has_fxsave_fxstor() { + print!(", fxsr") + }; + if info.has_sse() { + print!(", sse") + }; + + if info.has_sse2() { + print!(", sse2") + }; + if info.has_ss() { + print!(", ss") + }; + if info.has_htt() { + print!(", ht") + }; + if info.has_tm() { + print!(", tm") + }; + if info.has_pbe() { + print!(", pbe") + }; + + if info.has_sse3() { + print!(", sse3") + }; + if info.has_pclmulqdq() { + print!(", pclmulqdq") + }; + if info.has_ds_area() { + print!(", dtes64") + }; + if info.has_monitor_mwait() { + print!(", monitor") + }; + if info.has_cpl() { + print!(", ds_cpl") + }; + if info.has_vmx() { + print!(", vmx") + }; + if info.has_smx() { + print!(", smx") + }; + if info.has_eist() { + print!(", est") + }; + + if info.has_tm2() { + print!(", tm2") + }; + if info.has_ssse3() { + print!(", ssse3") + }; + if info.has_cnxtid() { + print!(", cnxtid") + }; + if info.has_fma() { + print!(", fma") + }; + if info.has_cmpxchg16b() { + print!(", cx16") + }; + if info.has_pdcm() { + print!(", pdcm") + }; + if info.has_pcid() { + print!(", pcid") + }; + if info.has_dca() { + print!(", dca") + }; + + if info.has_sse41() { + print!(", sse4_1") + }; + if info.has_sse42() { + print!(", sse4_2") + }; + if info.has_x2apic() { + print!(", x2apic") + }; + if info.has_movbe() { + print!(", movbe") + }; + if info.has_popcnt() { + print!(", popcnt") + }; + if info.has_tsc_deadline() { + print!(", tsc_deadline_timer") + }; + if info.has_aesni() { + print!(", aes") + }; + if info.has_xsave() { + print!(", xsave") + }; + + if info.has_oxsave() { + print!(", xsaveopt") + }; + if info.has_avx() { + print!(", avx") + }; + if info.has_f16c() { + print!(", f16c") + }; + if info.has_rdrand() { + print!(", rdrand") + }; + println!(""); + } + + if let Some(info) = cpuid.get_extended_function_info() { + print!("Extended function:"); + if info.has_64bit_mode() { + print!(" lm") + }; + if info.has_rdtscp() { + print!(", rdtscp") + }; + if info.has_1gib_pages() { + print!(", pdpe1gb") + }; + if info.has_execute_disable() { + print!(", nx") + }; + if info.has_syscall_sysret() { + print!(", syscall") + }; + if info.has_prefetchw() { + print!(", prefetchw") + }; + if info.has_lzcnt() { + print!(", lzcnt") + }; + if info.has_lahf_sahf() { + print!(", lahf_lm") + }; + if info.has_invariant_tsc() { + print!(", constant_tsc") + }; + println!(""); + } + + if let Some(info) = cpuid.get_extended_feature_info() { + print!("Extended features:"); + if info.has_fsgsbase() { + print!(" fsgsbase") + }; + if info.has_tsc_adjust_msr() { + print!(", tsc_adjust") + }; + if info.has_bmi1() { + print!(", bmi1") + }; + if info.has_hle() { + print!(", hle") + }; + if info.has_avx2() { + print!(", avx2") + }; + if info.has_smep() { + print!(", smep") + }; + if info.has_bmi2() { + print!(", bmi2") + }; + if info.has_rep_movsb_stosb() { + print!(", erms") + }; + if info.has_invpcid() { + print!(", invpcid") + }; + if info.has_rtm() { + print!(", rtm") + }; + // if info.has_qm() { + // print!(", qm") + // }; + if info.has_fpu_cs_ds_deprecated() { + print!(", fpu_seg") + }; + if info.has_mpx() { + print!(", mpx") + }; + println!(""); + } +} diff --git a/kernel-rs/src/arch/x86/devices/mod.rs b/kernel-rs/src/arch/x86/devices/mod.rs new file mode 100644 index 00000000..212805fd --- /dev/null +++ b/kernel-rs/src/arch/x86/devices/mod.rs @@ -0,0 +1,15 @@ +use x86::devices::pit; +use x86::devices::pic; +pub mod cpu; +use x86::instructions::interrupts; + +static CHAN0_DIVISOR: u16 = 2685; + +pub unsafe fn init() { + pic::init_cascade(); + pic::disable_irqs(); + pic::enable_irq(0); + pic::enable_irq(1); + pit::CHAN0.set_divisor(CHAN0_DIVISOR); + interrupts::enable(); +} diff --git a/kernel-rs/src/arch/x86/gdt.rs b/kernel-rs/src/arch/x86/gdt.rs new file mode 100644 index 00000000..1ed14506 --- /dev/null +++ b/kernel-rs/src/arch/x86/gdt.rs @@ -0,0 +1,64 @@ +use x86::structures::gdt; +use x86::structures::tss; +use x86::structures::gdt::SegmentSelector; +use x86::instructions::segmentation::*; +use x86::instructions::tables::load_tss; +use x86::PrivilegeLevel::{Ring0, Ring3}; + +pub static mut GDT: gdt::Gdt = gdt::Gdt::new(); +pub static mut TSS: tss::TaskStateSegment = tss::TaskStateSegment::new(); +pub static mut TASK_TSS: tss::TaskStateSegment = tss::TaskStateSegment::new(); + +pub static GDT_KERNEL_CODE: SegmentSelector = SegmentSelector::new(1, Ring0); +pub static GDT_KERNEL_DATA: SegmentSelector = SegmentSelector::new(2, Ring0); +pub static GDT_USER_CODE: SegmentSelector = SegmentSelector::new(3, Ring3); +pub static GDT_USER_DATA: SegmentSelector = SegmentSelector::new(4, Ring3); +pub static GDT_TSS: SegmentSelector = SegmentSelector::new(5, Ring3); +pub static GDT_TASK_TSS: SegmentSelector = SegmentSelector::new(7, Ring3); + +pub unsafe fn init() { + // the following *order* is important + let kcode_selector = GDT.add_entry(gdt::Descriptor::kernel_code_segment()); + let kdata_selector = GDT.add_entry(gdt::Descriptor::kernel_data_segment()); + let ucode_selector = GDT.add_entry(gdt::Descriptor::user_code_segment()); + let udata_selector = GDT.add_entry(gdt::Descriptor::user_data_segment()); + + //I read that the tss should be twice as long + //fuck knows why... + TSS.ss0 = GDT_KERNEL_CODE.0; + asm!("mov %esp, $0" : "=r" (TSS.esp0)); + let tss_selector = GDT.add_entry(gdt::Descriptor::tss_segment(&TSS)); + GDT.add_entry(gdt::Descriptor(0)); + + TASK_TSS.eip = self::test_task as *const () as u32; + let task_tss_selector = GDT.add_entry(gdt::Descriptor::tss_segment(&TASK_TSS)); + GDT.add_entry(gdt::Descriptor(0)); + + assert_eq!(kcode_selector, GDT_KERNEL_CODE); + assert_eq!(kdata_selector, GDT_KERNEL_DATA); + assert_eq!(ucode_selector, GDT_USER_CODE); + assert_eq!(udata_selector, GDT_USER_DATA); + assert_eq!(tss_selector, GDT_TSS); + assert_eq!(task_tss_selector, GDT_TASK_TSS); + + // use x86::structures::gdt::Descriptor; + // println!( + // "tr({:#x}):\n {:#?}", + // tss_selector.0, + // gdt::Descriptor(GDT.table[tss_selector.index() as usize]) + // ); + // flush!(); + + GDT.load(); + set_cs(kcode_selector); + load_ds(kdata_selector); + load_es(kdata_selector); + load_ss(kdata_selector); + load_tss(tss_selector); + // unreachable!(); +} + +pub fn test_task() { + println!("inside test task omg we did it !!!"); + flush!(); +} diff --git a/kernel-rs/src/arch/x86/grub.cfg b/kernel-rs/src/arch/x86/grub.cfg new file mode 100644 index 00000000..7bccc42f --- /dev/null +++ b/kernel-rs/src/arch/x86/grub.cfg @@ -0,0 +1,7 @@ +set timeout=0 +set default=0 + +menuentry "Blue Snow" { + multiboot2 /boot/bluesnow + boot +} diff --git a/kernel-rs/src/arch/x86/idt.rs b/kernel-rs/src/arch/x86/idt.rs new file mode 100644 index 00000000..ec5ca5e4 --- /dev/null +++ b/kernel-rs/src/arch/x86/idt.rs @@ -0,0 +1,52 @@ +use x86::structures::idt::*; +use super::interrupt::*; + +lazy_static! { + static ref IDT: Idt = { + let mut idt = Idt::new(); + + // set up CPU exceptions + idt.breakpoint.set_handler_fn(exception::breakpoint); + idt.debug.set_handler_fn(exception::debug); + idt.non_maskable_interrupt.set_handler_fn(exception::non_maskable); + idt.breakpoint.set_handler_fn(exception::breakpoint); + idt.overflow.set_handler_fn(exception::overflow); + idt.bound_range_exceeded.set_handler_fn(exception::bound_range); + idt.invalid_opcode.set_handler_fn(exception::invalid_opcode); + idt.device_not_available.set_handler_fn(exception::device_not_available); + idt.double_fault.set_handler_fn(exception::double_fault); + idt.segment_not_present.set_handler_fn(exception::segment_not_present); + idt.stack_segment_fault.set_handler_fn(exception::stack_segment); + idt.general_protection_fault.set_handler_fn(exception::general_protection); + idt.page_fault.set_handler_fn(exception::page_fault); + idt.x87_floating_point.set_handler_fn(exception::x87_fpu); + idt.alignment_check.set_handler_fn(exception::alignment_check); + idt.machine_check.set_handler_fn(exception::machine_check); + idt.simd_floating_point.set_handler_fn(exception::simd); + idt.virtualization.set_handler_fn(exception::virtualization); + + // set up IRQs + idt[32].set_handler_fn(irq::pit); + idt[33].set_handler_fn(irq::keyboard); + idt[34].set_handler_fn(irq::cascade); + idt[35].set_handler_fn(irq::com2); + idt[36].set_handler_fn(irq::com1); + idt[37].set_handler_fn(irq::lpt2); + idt[38].set_handler_fn(irq::floppy); + idt[39].set_handler_fn(irq::lpt1); + idt[40].set_handler_fn(irq::rtc); + idt[41].set_handler_fn(irq::pci1); + idt[42].set_handler_fn(irq::pci2); + idt[43].set_handler_fn(irq::pci3); + idt[44].set_handler_fn(irq::mouse); + idt[45].set_handler_fn(irq::fpu); + idt[46].set_handler_fn(irq::ata1); + idt[47].set_handler_fn(irq::ata2); + + idt + }; +} + +pub fn init() { + IDT.load(); +} diff --git a/kernel-rs/src/arch/x86/interrupt/exception.rs b/kernel-rs/src/arch/x86/interrupt/exception.rs new file mode 100644 index 00000000..4f86fca8 --- /dev/null +++ b/kernel-rs/src/arch/x86/interrupt/exception.rs @@ -0,0 +1,80 @@ +// https://wiki.osdev.org/Exceptions + +macro_rules! exception { + ($name:ident, $func:block) => { + pub extern "x86-interrupt" fn $name(stack_frame: &mut ExceptionStackFrame) + { + println!("#{}", stringify!($name)); + println!("{:#?}", stack_frame); + flush!(); + + #[allow(unused_variables)] + fn inner(stack: &mut ExceptionStackFrame) { + $func + } + inner(stack_frame); + } + } +} + +macro_rules! exception_err { + ($name:ident, $func:block) => { + pub extern "x86-interrupt" fn $name( + stack_frame: &mut ExceptionStackFrame, error_code: u32) + { + println!("#{}({})", stringify!($name), error_code); + println!("{:#?}", stack_frame); + flush!(); + + #[allow(unused_variables)] + fn inner(stack: &mut ExceptionStackFrame) { + $func + } + inner(stack_frame); + } + } +} + +use x86::structures::idt::*; + +exception!(divide_by_zero, { + panic!("CPU exception: division by zero") +}); + +exception!(debug, {}); +exception!(non_maskable, {}); +exception!(breakpoint, {}); +exception!(overflow, {}); +exception!(bound_range, {}); +exception!(invalid_opcode, {}); +exception!(device_not_available, {}); +exception_err!(double_fault, { + panic!("double fault non recoverable"); +}); +exception!(coprocessor_segment_overrun, {}); +exception_err!(invalid_tss, {}); +exception_err!(segment_not_present, {}); +exception_err!(stack_segment, {}); +exception_err!(general_protection, { + panic!("cannot recover from #GP"); +}); + +pub extern "x86-interrupt" fn page_fault( + stack_frame: &mut ExceptionStackFrame, + code: PageFaultErrorCode, +) { + use x86::registers::control::Cr2; + println!("Exception: page_fault"); + println!("Error code: {:?}", code); + println!("PFLA: {:?}", Cr2::read()); + println!("{:#?}", stack_frame); + flush!(); + panic!("cannot recover from #PF") +} + +exception!(x87_fpu, {}); +exception_err!(alignment_check, {}); +exception!(machine_check, {}); +exception!(simd, {}); +exception!(virtualization, {}); +exception_err!(security, {}); diff --git a/kernel-rs/src/arch/x86/interrupt/irq.rs b/kernel-rs/src/arch/x86/interrupt/irq.rs new file mode 100644 index 00000000..0a6e349c --- /dev/null +++ b/kernel-rs/src/arch/x86/interrupt/irq.rs @@ -0,0 +1,80 @@ +use x86::structures::idt::*; +use x86::devices::pic; +use time; + +#[macro_export] +macro_rules! interrupt { + ($i:expr, $name:ident, $func:block) => { + pub extern "x86-interrupt" fn $name(stack_frame: &mut ExceptionStackFrame) + { + unsafe { trigger($i); } + + #[allow(unused_variables)] + fn inner(stack: &mut ExceptionStackFrame) { + $func + } + inner(stack_frame); + + unsafe { acknowledge($i); } + } + } +} + +pub unsafe fn trigger(irq: u8) { + if irq < 16 { + if irq >= 8 { + pic::SLAVE.mask_set(irq - 8); + pic::MASTER.ack(); + pic::SLAVE.ack(); + } else { + pic::MASTER.mask_set(irq); + pic::MASTER.ack(); + } + } +} + +pub unsafe fn acknowledge(irq: usize) { + if irq < 16 { + if irq >= 8 { + pic::SLAVE.mask_clear(irq as u8 - 8); + pic::SLAVE.ack(); + } else { + pic::MASTER.mask_clear(irq as u8); + pic::MASTER.ack(); + } + } +} + +interrupt!(0, pit, { + /// t = 1/f + /// pit freq = 1.193182 MHz + /// chan0 divisor = 2685 + /// PIT_RATE in us + const PIT_RATE: u32 = 2_251; + { + let mut offset = time::OFFSET.lock(); + let sum = offset.1 + PIT_RATE; + offset.1 = sum % 1_000_000; + offset.0 += sum / 1_000_000; + } + unsafe { pic::MASTER.ack() }; +}); + +interrupt!(1, keyboard, { + ::keyboard::kbd_callback(); +}); + +interrupt!(2, cascade, {}); +interrupt!(3, com2, {}); +interrupt!(4, com1, {}); +interrupt!(5, lpt2, {}); +interrupt!(6, floppy, {}); +interrupt!(7, lpt1, {}); +interrupt!(8, rtc, {}); +interrupt!(9, pci1, {}); +interrupt!(10, pci2, {}); +interrupt!(11, pci3, {}); +interrupt!(12, mouse, {}); +interrupt!(13, fpu, {}); +interrupt!(14, ata1, {}); +interrupt!(15, ata2, {}); diff --git a/kernel-rs/src/arch/x86/interrupt/mod.rs b/kernel-rs/src/arch/x86/interrupt/mod.rs new file mode 100644 index 00000000..c8a97f14 --- /dev/null +++ b/kernel-rs/src/arch/x86/interrupt/mod.rs @@ -0,0 +1,4 @@ +#[macro_use] +pub mod exception; +#[macro_use] +pub mod irq; diff --git a/kernel-rs/src/arch/x86/linker.ld b/kernel-rs/src/arch/x86/linker.ld new file mode 100644 index 00000000..96419d19 --- /dev/null +++ b/kernel-rs/src/arch/x86/linker.ld @@ -0,0 +1,80 @@ +ENTRY(start) +OUTPUT_FORMAT(elf32-i386) + +SECTIONS { + /* VGA, cannot use section for this */ + VGA_PTR = 0xb8000; + . = 0xb8000; + . += 80 * 25 * 2; + + . = 1M; + /* ensure that the multiboot header is at the beginning */ + .multiboot : + { + /* KEEP otherwise it gets garbage collected by linker */ + KEEP(*(.multiboot)) + . = ALIGN(4K); + } + + .text : + { + *(.text .text.*) + . = ALIGN(4K); + } + + .rodata : + { + *(.rodata .rodata.*) + . = ALIGN(4K); + } + + .data : + { + *(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*) *(.data.*) + . = ALIGN(4K); + } + + /* NOT A GOOD IDEA TO GROUP debug_* SYMBOLS ! */ + /* .debug : */ + /* { */ + /* /1* KEEP(*(.debug_*)) *1/ */ + /* *(.debug_*) */ + /* . = ALIGN(4K); */ + /* } */ + + .gdt : + { + *(.gdt) + . = ALIGN(4K); + } + + .got : + { + *(.got) + . = ALIGN(4K); + } + + .got.plt : + { + *(.got.plt) + . = ALIGN(4K); + } + + .bss : + { + *(.bss .bss.*) + . = ALIGN(4K); + } + + /* .stab : */ + /* { */ + /* KEEP(*(.stab)) */ + /* . = ALIGN(4K); */ + /* } */ + + /* .stabstr : */ + /* { */ + /* KEEP(*(.stabstr)) */ + /* . = ALIGN(4K); */ + /* } */ +} diff --git a/kernel-rs/src/arch/x86/mod.rs b/kernel-rs/src/arch/x86/mod.rs new file mode 100644 index 00000000..14cd6855 --- /dev/null +++ b/kernel-rs/src/arch/x86/mod.rs @@ -0,0 +1,141 @@ +extern crate x86; + +#[macro_use] +pub mod paging; +pub mod interrupt; +pub mod devices; +pub mod consts; + +pub mod gdt; +pub mod idt; + +use multiboot2; +use acpi; + +#[no_mangle] +pub unsafe extern "C" fn x86_rust_start(multiboot_info_addr: usize) { + // parse multiboot2 info + let boot_info = multiboot2::load(multiboot_info_addr); + + // ACPI must be intialized BEFORE paging is active + if let Some(rsdp) = boot_info.rsdp_v2_tag() { + acpi::load(rsdp).expect("ACPI failed"); + } else if let Some(rsdp) = boot_info.rsdp_tag() { + acpi::load(rsdp).expect("ACPI failed"); + } else { + acpi::init().expect("ACPI failed"); + } + + // set up physical allocator + ::memory::init(&boot_info); + + // set up virtual addressing (paging) + let mut active_table = paging::init(&boot_info); + + // load idt (exceptions + irqs) + idt::init(); + + // fill and load gdt + gdt::init(); + + // set up heap + ::allocator::init(&mut active_table); + + // set up user stack + // use x86::structures::paging::*; + // for page in ::USER_STACK_RANGE { + // active_table.map(page, PageTableFlags::WRITABLE); + // } + + // set up pic, pit + devices::init(); + + // primary CPU entry point, architutecture independant now. + ::kmain(); +} + +pub unsafe fn switch(ip: u32) -> ! { + asm!("push $0; ret" :: "r"(ip) :: "volatile", "intel"); + unreachable!(); +} + +pub unsafe fn usermode(ip: u32, sp: u32) -> ! { + // use x86::structures::gdt::{Descriptor, SegmentSelector}; + // use x86::instructions::segmentation::*; + // use x86::PrivilegeLevel::{Ring0, Ring3}; + + x86::instructions::interrupts::disable(); + + // println!("sp: {:#x}", sp); + // println!("ip: {:#x}", ip); + + // load_ds(udata_selector); + // asm!("mov $0, %ds" :: "r"(gdt::GDT_USER_DATA << 3 | 3)); + + // loop {} + + // load_es(udata_selector); + // load_fs(udata_selector); + // load_gs(udata_selector); + + use x86::registers::flags; + flags::set_flags(flags::Flags::NT); + + // asm!("mov %esp, $0" : "=r" (sp)); + + println!("{:#x}", gdt::GDT_KERNEL_DATA.0); + println!("{:#x}", sp); + println!("{:#x}", 1 << 9); + println!("{:#x}", gdt::GDT_KERNEL_CODE.0); + println!("{:#x}", ip); + flush!(); + + asm!(" + push $0; \ + push $1; \ + push $2; \ + push $3; \ + push $4" + : //no output + : "r"(gdt::GDT_USER_DATA.0), + "r"(sp), + "r"(1 << 9) // interrupt enable flag + "r"(gdt::GDT_USER_CODE.0), + "r"(ip) + : //no clobbers + : "intel", "volatile" + ); + + // loop {} + + asm!("iret"); + + unreachable!(); +} + +/// Dump control registers +pub fn regs() { + use x86::registers::control::*; + use x86::instructions::tables::tr; + use x86::instructions::segmentation::*; + use x86::registers::flags::*; + use x86::structures::gdt; + println!("cr0 = {:?}", Cr0::read()); + println!("cr3 = {:?}", Cr3::read()); + println!("cr4 = {:?}", Cr4::read()); + println!("flags= {:?}", flags()); + println!("tr = {:?}", tr()); + println!("ss = {:?}", ss()); + println!("cs = {:?}", cs()); + println!("ds = {:?}", ds()); + println!("es = {:?}", es()); + println!("fs = {:?}", fs()); + println!("gs = {:?}", gs()); + unsafe { + println!( + "tss = {:#?}", + gdt::Descriptor(::arch::x86::gdt::GDT.table[tr().index() as usize]) + ); + } + flush!(); +} diff --git a/kernel-rs/src/arch/x86/multiboot_header.asm b/kernel-rs/src/arch/x86/multiboot_header.asm new file mode 100644 index 00000000..c843f927 --- /dev/null +++ b/kernel-rs/src/arch/x86/multiboot_header.asm @@ -0,0 +1,16 @@ +section .multiboot +header_start: +align 4 + dd 0xe85250d6 ; magic number (multiboot 2) + dd 0 ; TODO change it because architecture 0 means(protected mode i386) We could have problem here + dd header_end - header_start ; header length + + dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)); checksum + + ; insert optional multiboot tags here + + ; required end tag + dw 0 ; type + dw 0 ; flags + dd 8 ; size +header_end: diff --git a/kernel-rs/src/arch/x86/paging/mapper.rs b/kernel-rs/src/arch/x86/paging/mapper.rs new file mode 100644 index 00000000..cf8ce6b5 --- /dev/null +++ b/kernel-rs/src/arch/x86/paging/mapper.rs @@ -0,0 +1,89 @@ +use core::ptr::Unique; +use x86::structures::paging::*; +use x86::instructions::tlb; +use x86::usize_conversions::usize_from; +use x86::*; +use super::table::RecTable; + +// virtual address of recursively mapped P2 +// for protected mode non PAE +// https://wiki.osdev.org/Page_Tables +pub const P2: *mut PageTable = 0xffff_f000 as *mut _; + +pub struct Mapper { + pub p2: Unique, +} + +impl Mapper { + pub unsafe fn new() -> Mapper { + Mapper { + p2: Unique::new_unchecked(self::P2), + } + } + + // the remaining mapping methods, all public + pub fn p2(&self) -> &PageTable { + unsafe { self.p2.as_ref() } + } + + pub fn p2_mut(&mut self) -> &mut PageTable { + unsafe { self.p2.as_mut() } + } + + /// virtual addr to physical addr translation + pub fn translate(&self, virtual_address: VirtAddr) -> Option { + let offset = virtual_address.as_u32() % PAGE_SIZE as u32; + self.translate_page(Page::containing_address(virtual_address)) + .map(|frame| frame.start_address() + offset) + } + + /// virtual page to physical frame translation + pub fn translate_page(&self, page: Page) -> Option { + let p1 = self.p2().next_table(usize_from(u32::from(page.p2_index()))); + + let huge_page = || { + let p2_entry = &self.p2()[page.p2_index()]; + if let Some(start_frame) = p2_entry.pointed_frame() { + if p2_entry.flags().contains(PageTableFlags::HUGE_PAGE) { + // TODO 4MiB alignment check + return Some(start_frame + u32::from(page.p1_index())); + } + } + None + }; + + p1.and_then(|p1| p1[page.p1_index()].pointed_frame()) + .or_else(huge_page) + } + + /// map a virtual page to a physical frame in the page tables + pub fn map_to(&mut self, page: Page, frame: PhysFrame, flags: PageTableFlags) { + let p2 = self.p2_mut(); + let p1 = p2.next_table_create(usize_from(u32::from(page.p2_index()))); + assert!(p1[page.p1_index()].is_unused()); + p1[page.p1_index()].set(frame, flags | PageTableFlags::PRESENT); + } + + pub fn map(&mut self, page: Page, flags: PageTableFlags) { + let frame = ::memory::allocate_frames(1).expect("out of frames"); + self.map_to(page, frame, flags) + } + + pub fn identity_map(&mut self, frame: PhysFrame, flags: PageTableFlags) { + let virt_addr = VirtAddr::new(frame.start_address().as_u32()); + let page = Page::containing_address(virt_addr); + self.map_to(page, frame, flags); + } + + pub fn unmap(&mut self, page: Page) { + assert!(self.translate(page.start_address()).is_some()); + + let p1 = self.p2_mut() + .next_table_mut(usize_from(u32::from(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(page.start_address()); + ::memory::deallocate_frames(frame, 1); + } +} diff --git a/kernel-rs/src/arch/x86/paging/mod.rs b/kernel-rs/src/arch/x86/paging/mod.rs new file mode 100644 index 00000000..698d2cf9 --- /dev/null +++ b/kernel-rs/src/arch/x86/paging/mod.rs @@ -0,0 +1,189 @@ +#![allow(dead_code)] + +mod table; +mod temporary_page; +mod mapper; + +// use memory::*; +use self::mapper::Mapper; +use self::temporary_page::TemporaryPage; +use core::ops::{Deref, DerefMut}; +use multiboot2::BootInformation; +use x86::*; +use x86::registers::control::Cr3; +use x86::instructions::tlb; +use x86::structures::paging::*; +use multiboot2; + +/// should be called only once +pub fn init(boot_info: &multiboot2::BootInformation) -> ActivePageTable { + use x86::registers::control::*; + Cr4::add(Cr4Flags::PSE); + Cr0::add(Cr0Flags::PAGING | Cr0Flags::WRITE_PROTECT); + + let active_table = remap_the_kernel(boot_info); + + active_table +} + +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 { + pub unsafe fn new() -> ActivePageTable { + ActivePageTable { + mapper: Mapper::new(), + } + } + + pub fn with( + &mut self, + table: &mut InactivePageTable, + temporary_page: &mut temporary_page::TemporaryPage, + f: F, + ) where + F: FnOnce(&mut Mapper), + { + let (cr3_back, _cr3flags_back) = Cr3::read(); + + // map temp page to current p2 + let p2_table = temporary_page.map_table_frame(cr3_back.clone(), self); + + // overwrite recursive map + self.p2_mut()[1023].set( + table.p2_frame.clone(), + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + ); + tlb::flush_all(); + + // execute f in the new context + f(self); + + // restore recursive mapping to original p2 table + p2_table[1023].set(cr3_back, PageTableFlags::PRESENT | PageTableFlags::WRITABLE); + } + + pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable { + let (p2_frame, cr3_flags) = Cr3::read(); + let old_table = InactivePageTable { p2_frame }; + + unsafe { + Cr3::write(new_table.p2_frame, cr3_flags); + } + + old_table + } +} + +pub struct InactivePageTable { + p2_frame: PhysFrame, +} + +impl InactivePageTable { + pub fn new( + frame: PhysFrame, + active_table: &mut ActivePageTable, + temporary_page: &mut TemporaryPage, + ) -> InactivePageTable { + { + let table = temporary_page.map_table_frame(frame.clone(), active_table); + + table.zero(); + // set up recursive mapping for the table + table[1023].set( + frame.clone(), + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + ) + } + temporary_page.unmap(active_table); + InactivePageTable { p2_frame: frame } + } +} + +pub fn remap_the_kernel(boot_info: &BootInformation) -> ActivePageTable { + let mut temporary_page = TemporaryPage::new(Page { number: 0xcafe }); + let mut active_table = unsafe { ActivePageTable::new() }; + let mut new_table = { + let frame = ::memory::allocate_frames(1).expect("no more frames"); + InactivePageTable::new(frame, &mut active_table, &mut temporary_page) + }; + + active_table.with(&mut new_table, &mut temporary_page, |mapper| { + // id map vga buffer + let vga_buffer_frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); + mapper.identity_map(vga_buffer_frame, PageTableFlags::WRITABLE); + + let elf_sections_tag = boot_info + .elf_sections_tag() + .expect("Memory map tag required"); + + // id map kernel sections + for section in elf_sections_tag.sections() { + if !section.is_allocated() { + continue; + } + assert!( + section.start_address() % PAGE_SIZE as u64 == 0, + "sections need to be page aligned" + ); + + let flags = elf_to_pagetable_flags(§ion.flags()); + let start_frame = + PhysFrame::containing_address(PhysAddr::new(section.start_address() as u32)); + let end_frame = + PhysFrame::containing_address(PhysAddr::new(section.end_address() as u32 - 1)); + for frame in start_frame..end_frame + 1 { + mapper.identity_map(frame, flags); + } + } + + // id map multiboot + let multiboot_start = + PhysFrame::containing_address(PhysAddr::new(boot_info.start_address() as u32)); + let multiboot_end = + PhysFrame::containing_address(PhysAddr::new(boot_info.end_address() as u32 - 1)); + for frame in multiboot_start..multiboot_end + 1 { + mapper.identity_map(frame, PageTableFlags::PRESENT); + } + }); + + let old_table = active_table.switch(new_table); + + let old_p2_page = + Page::containing_address(VirtAddr::new(old_table.p2_frame.start_address().as_u32())); + + active_table.unmap(old_p2_page); + + active_table +} + +fn elf_to_pagetable_flags(elf_flags: &multiboot2::ElfSectionFlags) -> PageTableFlags { + use multiboot2::ElfSectionFlags; + + let mut flags = PageTableFlags::empty(); + + if elf_flags.contains(ElfSectionFlags::ALLOCATED) { + // section is loaded to memory + flags = flags | PageTableFlags::PRESENT; + } + if elf_flags.contains(ElfSectionFlags::WRITABLE) { + flags = flags | PageTableFlags::WRITABLE; + } + + flags +} diff --git a/kernel-rs/src/arch/x86/paging/table.rs b/kernel-rs/src/arch/x86/paging/table.rs new file mode 100644 index 00000000..3b774172 --- /dev/null +++ b/kernel-rs/src/arch/x86/paging/table.rs @@ -0,0 +1,47 @@ +use x86::structures::paging::*; + +pub trait RecTable { + fn next_table_address(&self, index: usize) -> Option; + fn next_table(&self, index: usize) -> Option<&PageTable>; + fn next_table_mut(&mut self, index: usize) -> Option<&mut PageTable>; + fn next_table_create(&mut self, index: usize) -> &mut PageTable; +} + +impl RecTable for PageTable { + fn next_table_address(&self, index: usize) -> Option { + let entry_flags = self[index].flags(); + if entry_flags.contains(PageTableFlags::PRESENT) + && !entry_flags.contains(PageTableFlags::HUGE_PAGE) + { + let table_address = self as *const _ as usize; + Some((table_address << 10 | index << 12) as u32) + } else { + None + } + } + + fn next_table(&self, index: usize) -> Option<&PageTable> { + self.next_table_address(index) + .map(|address| unsafe { &*(address as *const _) }) + } + + fn next_table_mut(&mut self, index: usize) -> Option<&mut PageTable> { + self.next_table_address(index) + .map(|address| unsafe { &mut *(address as *mut _) }) + } + + fn next_table_create(&mut self, index: usize) -> &mut PageTable { + if self.next_table(index).is_none() { + assert!( + !self[index].flags().contains(PageTableFlags::HUGE_PAGE), + "mapping code does not support huge pages" + ); + let frame = ::memory::allocate_frames(1).expect("out of memory"); + self[index].set(frame, PageTableFlags::PRESENT | PageTableFlags::WRITABLE); + self.next_table_mut(index) + .expect("next_table_mut gave None") + .zero() + } + self.next_table_mut(index).expect("no next table 2") + } +} diff --git a/kernel-rs/src/arch/x86/paging/temporary_page.rs b/kernel-rs/src/arch/x86/paging/temporary_page.rs new file mode 100644 index 00000000..b712fcfb --- /dev/null +++ b/kernel-rs/src/arch/x86/paging/temporary_page.rs @@ -0,0 +1,44 @@ +use super::ActivePageTable; +use x86::*; +use x86::structures::paging::*; + +pub struct TemporaryPage { + pub page: Page, +} + +impl TemporaryPage { + pub fn new(page: Page) -> TemporaryPage { + TemporaryPage { page: page } + } + + /// 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: PhysFrame, active_table: &mut ActivePageTable) -> VirtAddr { + assert!( + active_table.translate_page(self.page).is_none(), + "temporary page is already mapped" + ); + active_table.map_to(self.page, frame, PageTableFlags::WRITABLE); + // this kind of check should be done in a test routine + assert!( + active_table.translate_page(self.page).is_some(), + "temporary page was not mapped" + ); + 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) + } + + /// 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: PhysFrame, + active_table: &mut ActivePageTable, + ) -> &mut PageTable { + unsafe { &mut *(self.map(frame, active_table).as_u32() as *mut PageTable) } + } +} diff --git a/kernel-rs/src/arch/x86/start.asm b/kernel-rs/src/arch/x86/start.asm new file mode 100644 index 00000000..fc0fc608 --- /dev/null +++ b/kernel-rs/src/arch/x86/start.asm @@ -0,0 +1,18 @@ +global x86_start +extern x86_rust_start + +section .text +bits 32 +x86_start: + mov ax, 0x10 ; 16 bytes (0x10) is where the offset for data section (gdt_ds) + mov ds, ax + mov ss, ax + mov es, ax + mov ax, 0x0 ; fuck fs & gs, fuck them. + mov fs, ax + mov gs, ax + + call x86_rust_start +HALT: + hlt + jmp HALT diff --git a/kernel-rs/src/console.rs b/kernel-rs/src/console.rs new file mode 100644 index 00000000..fa6b5fa5 --- /dev/null +++ b/kernel-rs/src/console.rs @@ -0,0 +1,126 @@ +use vga::*; +use alloc::collections::BTreeMap; + +pub static mut CONSOLE: Console = self::Console::new(); + +lazy_static! { + static ref COMMANDS: BTreeMap<&'static str, fn()> = { + let mut commands = BTreeMap::new(); + commands.insert("help", self::help as fn()); + + // ACPI + commands.insert("acpi", ::acpi::info as fn()); + commands.insert("reboot", ::acpi::reboot as fn()); + commands.insert("shutdown", ::acpi::shutdown as fn()); + + // time + commands.insert("uptime", ::time::uptime as fn()); + + // cpu + use arch::x86; + commands.insert("cpu", x86::devices::cpu::cpu_info as fn()); + commands.insert("regs", x86::regs as fn()); + commands.insert("int3", ::x86::instructions::interrupts::int3 as fn()); + + //memory + commands.insert("stack", ::memory::print_stack as fn()); + commands.insert("page_fault", ::memory::page_fault as fn()); + commands.insert("overflow", ::memory::overflow as fn()); + + //pci + commands.insert("lspci", ::pci::lspci as fn()); + commands + }; +} + +fn help() { + println!("The following commands are available:"); + for (key, _val) in COMMANDS.iter() { + println!("{}", key); + } +} + +pub struct Console { + command: [u8; 10], + command_len: usize, +} + +impl Console { + pub const fn new() -> Console { + Console { + command: [b'\0'; 10], + command_len: 0, + } + } + + pub fn init(&self) { + set_color!(); + // print!("{}", format_args!("{: ^4000}", r#" "#)); + unsafe { + // VGA.buffer_pos = 0; + self.prompt(); + VGA.flush(); + } + } + + pub fn backspace(&mut self) { + if self.command_len > 0 { + self.command_len -= 1; + unsafe { VGA.erase_byte(); } + } + } + + pub fn prompt(&self) { + set_color!(Blue); + unsafe { VGA.write_str("> "); } + set_color!(); + flush!(); + } + + pub fn keypress(&mut self, ascii: u8) { + match ascii { + b'\n' if self.command_len == 0 => { + unsafe { VGA.write_byte(b'\n'); } + self.prompt(); + } + b'\n' => { + unsafe { VGA.write_byte(b'\n'); } + self.exec(); + self.command_len = 0; + self.prompt(); + } + // _ if self.command_len >= 10 => (), + // byte if self.command_len == 0 && byte == b' ' => (), + byte => { + if self.command_len >= 10 { + return; + }; + self.command[self.command_len] = byte; + unsafe { VGA.write_byte(byte); } + self.command_len += 1; + } + } + unsafe { VGA.flush(); } + } + + fn get_command(&self) -> Result { + match core::str::from_utf8(&self.command) { + Ok(y) => { + if let Some(command) = COMMANDS.get(&y[..self.command_len]) { + Ok(*command) + } else { + Err("Command not found, try help") + } + }, + Err(_) => Err("Command is not utf8"), + } + } + + pub fn exec(&self) { + let command = self.get_command(); + match command { + Err(msg) => println!("{}", msg), + Ok(func) => (func)(), + } + } +} diff --git a/kernel-rs/src/keyboard.rs b/kernel-rs/src/keyboard.rs new file mode 100644 index 00000000..75787813 --- /dev/null +++ b/kernel-rs/src/keyboard.rs @@ -0,0 +1,145 @@ +use console; +use x86::devices::io::{Io, Pio}; + +const MAX_KEYS: usize = 59; +const KEYMAP_US: [[u8; 2]; MAX_KEYS] = [ + *b"\0\0", + *b"\0\0",//escape + *b"1!", + *b"2@", + *b"3#", + *b"4$", + *b"5%", + *b"6^", + *b"7&", + *b"8*", + *b"9(", + *b"0)", + *b"-_", + *b"=+", + *b"\0\0",//backspace + *b"\0\0",//tab + *b"qQ", + *b"wW", + *b"eE", + *b"rR", + *b"tT", + *b"yY", + *b"uU", + *b"iI", + *b"oO", + *b"pP", + *b"[{", + *b"]}", + *b"\n\n", + *b"\0\0",//left_control + *b"aA", + *b"sS", + *b"dD", + *b"fF", + *b"gG", + *b"hH", + *b"jJ", + *b"kK", + *b"lL", + *b";:", + *b"'\"", + *b"`~", + *b"\0\0",//left shift + *b"\\|", + *b"zZ", + *b"xX", + *b"cC", + *b"vV", + *b"bB", + *b"nN", + *b"mM", + *b",<", + *b".>", + *b"/?", + *b"\0\0",//right shift + *b"**", + *b"\0\0",//left alt + *b" ", + *b"\0\0",//capslock + ]; + +pub static mut PS2: Ps2 = Ps2::new(); + +pub struct Ps2 { + status: Pio, + data: Pio, +} + +impl Ps2 { + pub const fn new() -> Ps2 { + Ps2 { + status: Pio::new(0x64), + data: Pio::new(0x60), + } + } + + pub fn clear_buffer(&self) { + let mut buffer: u8 = 0x02; + while buffer & 0x02 != 0 { + self.data.read(); + buffer = self.status.read(); + } + } + + pub fn ps2_8042_reset(&mut self) { + use x86::instructions::interrupts; + interrupts::disable(); + self.clear_buffer(); + self.status.write(0xFE); + } + + pub fn get_scancode(&self) -> u8 { + let mut scancode = 0; + loop { + if self.data.read() != scancode { + scancode = self.data.read(); + if scancode > 0 { + return scancode; + } + } + } + } +} + +const TOUCH_RELEASE: u8 = 1 << 7; + +fn check_key_state(key: u8) -> (bool, usize) { + if (key & TOUCH_RELEASE) == TOUCH_RELEASE { + (true, (key - TOUCH_RELEASE) as usize) + } else { + (false, key as usize) + } +} + +pub fn kbd_callback() { + static mut SHIFT: bool = false; + static mut CTRL: bool = false; + static mut ALT: bool = false; + let scancode = unsafe { PS2.get_scancode() }; + let (is_release, scancode) = check_key_state(scancode); + unsafe { + //TODO remove unsafe + match self::KEYMAP_US.get(scancode as usize) { + Some(b"\0\0") => match scancode { + 0x2A | 0x36 => SHIFT = !is_release, + 0x38 => ALT = !is_release, + 0x1D => CTRL = !is_release, + 0x0E if !is_release => { + console::CONSOLE.backspace(); + } + _ => {} + }, + Some(ascii) if !is_release => { + let sym = if SHIFT { ascii[1] } else { ascii[0] }; + console::CONSOLE.keypress(sym); + } + _ => {} + } + } +} diff --git a/kernel-rs/src/lib.rs b/kernel-rs/src/lib.rs new file mode 100644 index 00000000..c363c8a5 --- /dev/null +++ b/kernel-rs/src/lib.rs @@ -0,0 +1,79 @@ +//! project hosted on [github](https://github.com/jzck/kernel) + +// nightly stuff we need +#![no_std] +#![feature(lang_items)] +#![feature(naked_functions)] +#![feature(const_fn)] +#![feature(ptr_internals)] +#![feature(asm)] +#![feature(thread_local)] +// home made heap +#![feature(alloc)] +#![feature(alloc_error_handler)] +#![feature(allocator_api)] +// x86 specific +#![feature(abi_x86_interrupt)] + +// extern crate core; +extern crate alloc; +#[macro_use] +extern crate lazy_static; +extern crate multiboot2; +extern crate raw_cpuid; +extern crate rlibc; +extern crate slab_allocator; +extern crate spin; + +extern crate bitflags; + +// used by arch/x86, need conditional compilation here +extern crate x86; + +#[macro_use] +pub mod vga; +pub mod keyboard; +pub mod console; +pub mod acpi; +pub mod allocator; +pub mod memory; +pub mod arch; +pub use arch::x86::consts::*; +pub mod scheduling; +#[allow(dead_code)] +pub mod time; +pub mod pci; + +/// kernel entry point. +/// ::arch is responsible for calling this once the core has loaded. +pub fn kmain() -> ! { + // memory init after heap is available + // memory::init_noncore(); + + // unsafe VGA + unsafe { console::CONSOLE.init(); } + + if let Some(ide) = pci::get_first(0x1, 0x1) { + println!("found IDE at slot {}", ide.slot); + pci::ide::init(ide); + } + + // scheduler WIP + // scheduling::schedule(); + unreachable!(); +} + +#[lang = "eh_personality"] +#[no_mangle] +pub extern "C" fn eh_personality() {} + +#[panic_handler] +#[no_mangle] +pub extern "C" fn panic_fmt(info: &core::panic::PanicInfo) -> ! { + println!("{}", info); + flush!(); + loop {} +} + +#[global_allocator] +pub static ALLOCATOR: slab_allocator::LockedHeap = slab_allocator::LockedHeap::empty(); diff --git a/kernel-rs/src/memory/bump.rs b/kernel-rs/src/memory/bump.rs new file mode 100644 index 00000000..fce35247 --- /dev/null +++ b/kernel-rs/src/memory/bump.rs @@ -0,0 +1,115 @@ +use multiboot2::{MemoryArea, MemoryAreaIter}; +use x86::*; +use x86::structures::paging::PhysFrame; +use super::FrameAllocator; + +pub struct BumpFrameAllocator { + next_free_frame: PhysFrame, + current_area: Option<&'static MemoryArea>, + areas: MemoryAreaIter, + kernel_start: PhysFrame, + kernel_end: PhysFrame, + multiboot_start: PhysFrame, + multiboot_end: PhysFrame, +} + +impl BumpFrameAllocator { + pub fn new( + kernel_start: usize, + kernel_end: usize, + multiboot_start: usize, + multiboot_end: usize, + memory_areas: MemoryAreaIter, + ) -> BumpFrameAllocator { + let mut allocator = BumpFrameAllocator { + next_free_frame: PhysFrame { number: 0 }, + current_area: None, + areas: memory_areas, + kernel_start: PhysFrame::containing_address(PhysAddr::new(kernel_start as u32)), + kernel_end: PhysFrame::containing_address(PhysAddr::new(kernel_end as u32)), + multiboot_start: PhysFrame::containing_address(PhysAddr::new(multiboot_start as u32)), + multiboot_end: PhysFrame::containing_address(PhysAddr::new(multiboot_end as u32)), + }; + allocator.choose_next_area(); + allocator + } + + fn choose_next_area(&mut self) { + // get next area with free frames + self.current_area = self.areas + .clone() + .filter(|area| { + area.end_address() >= self.next_free_frame.start_address().as_u32() as usize + }) + .min_by_key(|area| area.start_address()); + + if let Some(area) = self.current_area { + let start_frame = + PhysFrame::containing_address(PhysAddr::new(area.start_address() as u32)); + if self.next_free_frame < start_frame { + self.next_free_frame = start_frame; + } + } + } +} + +impl FrameAllocator for BumpFrameAllocator { + fn allocate_frames(&mut self, count: usize) -> Option { + if count == 0 { + return None; + }; + // println!("allocate {}", count); + // println!("kstart {:?}", self.kernel_start); + // println!("kend {:?}", self.kernel_end); + // println!("multiboot start {:?}", self.multiboot_start); + // println!("multiboot end {:?}", self.multiboot_end); + // flush!(); + if let Some(area) = self.current_area { + let start_frame = PhysFrame { + number: self.next_free_frame.number, + }; + let end_frame = PhysFrame { + number: self.next_free_frame.number + count as u32 - 1, + }; + + let current_area_last_frame = + PhysFrame::containing_address(PhysAddr::new(area.end_address() as u32)); + + if end_frame > current_area_last_frame { + // all frames are taken in this area + self.choose_next_area(); + } else if (start_frame >= self.kernel_start && start_frame <= self.kernel_end) + || (end_frame >= self.kernel_start && end_frame <= self.kernel_end) + { + // frame used by kernel + self.next_free_frame = PhysFrame { + number: self.kernel_end.number + 1, + }; + } else if (start_frame >= self.multiboot_start && start_frame <= self.multiboot_end) + || (end_frame >= self.multiboot_start && end_frame <= self.multiboot_end) + { + // frame used by multiboot + self.next_free_frame = PhysFrame { + number: self.multiboot_end.number + 1, + }; + } else { + self.next_free_frame.number += count as u32; + return Some(start_frame); + } + // try again with next_free_frame + self.allocate_frames(count) + } else { + None + } + } + + fn deallocate_frames(&mut self, frame: PhysFrame, count: usize) { + // bump doesnt deallocate, must be used inside of a recycler + println!( + "lost frames {:#x} ({})", + frame.start_address().as_u32(), + count + ); + // unimplemented!(); + } +} diff --git a/kernel-rs/src/memory/mod.rs b/kernel-rs/src/memory/mod.rs new file mode 100644 index 00000000..2279c3e8 --- /dev/null +++ b/kernel-rs/src/memory/mod.rs @@ -0,0 +1,163 @@ +mod bump; +mod recycle; +// mod stack_allocator; + +use multiboot2; +use x86::structures::paging::*; + +use self::bump::BumpFrameAllocator; +use self::recycle::RecycleAllocator; +// use self::stack_allocator::{Stack, StackAllocator}; + +pub trait FrameAllocator { + fn allocate_frames(&mut self, size: usize) -> Option; + fn deallocate_frames(&mut self, frame: PhysFrame, size: usize); +} + +pub struct MemoryControler { + frame_allocator: RecycleAllocator, + // stack_allocator: StackAllocator, +} + +static mut MEMORY_CONTROLER: Option = None; + +pub fn init(boot_info: &multiboot2::BootInformation) { + let elf_sections_tag = boot_info.elf_sections_tag().unwrap(); + let memory_map_tag = boot_info.memory_map_tag().unwrap(); + + let kernel_start = elf_sections_tag + .sections() + .filter(|s| s.is_allocated()) + .map(|s| s.start_address()) + .min() + .unwrap(); + + let kernel_end = elf_sections_tag + .sections() + .filter(|s| s.is_allocated()) + .map(|s| s.start_address() + s.size()) + .max() + .unwrap(); + + let bump_allocator = BumpFrameAllocator::new( + kernel_start as usize, + kernel_end as usize, + boot_info.start_address(), + boot_info.end_address(), + memory_map_tag.memory_areas(), + ); + + let frame_allocator = RecycleAllocator::new(bump_allocator); + // let stack_allocator = StackAllocator::new(::USER_STACK_RANGE); + + unsafe { + MEMORY_CONTROLER = Some(MemoryControler { + frame_allocator, + // stack_allocator, + }); + } +} + +pub fn allocate_frames(count: usize) -> Option { + unsafe { + if let Some(ref mut controler) = MEMORY_CONTROLER { + controler.frame_allocator.allocate_frames(count) + } else { + panic!("frame allocator not initialized!"); + } + } +} + +pub fn deallocate_frames(frame: PhysFrame, count: usize) { + unsafe { + if let Some(ref mut controler) = MEMORY_CONTROLER { + controler.frame_allocator.deallocate_frames(frame, count) + } else { + panic!("frame allocator not initialized!"); + } + } +} + +// pub fn allocate_stack(mut active_table: &mut ActivePageTable) -> Option { +// unsafe { +// if let Some(ref mut controler) = MEMORY_CONTROLER { +// controler +// .stack_allocator +// .allocate_stack(&mut active_table, 4) +// } else { +// panic!("frame allocator not initialized!"); +// } +// } +// } + +/// Init memory module after core +/// Must be called once, and only once, +pub fn init_noncore() { + unsafe { + if let Some(ref mut controler) = MEMORY_CONTROLER { + controler.frame_allocator.set_core(true); + } else { + panic!("frame allocator not initialized"); + } + } +} + + +/// for console +#[allow(unconditional_recursion)] +pub fn overflow() { + overflow(); +} + +/// for console +pub fn page_fault() { + unsafe { + *(0xdead as *mut u32) = 42; + }; +} + +/// Print the kernel stack +pub fn print_stack() { + fn hexdump(start: usize, end: usize) { + let mut address = 0; + let data = unsafe { core::slice::from_raw_parts_mut(start as *mut u8, end - start) }; + while address <= data.len() { + let next_end = core::cmp::min(address + 16, data.len()); + print_line(&data[address..next_end], address + start); + address = address + 16; + } + println!(""); + } + + fn is_control(c: char) -> bool { + !(c >= ' ' && c <= '~') + } + + fn print_line(line: &[u8], address: usize) { + print!("\n{:#08x}: ", address); + for byte in line { + print!("{:02x} ", *byte); + } + let length: usize = 16 - line.len(); + for _ in 0..length { + print!(" "); + } + print!("|"); + for byte in line { + match is_control(*byte as char) { + true => print!("."), + false => print!("{}", *byte as char), + }; + } + print!("|"); + } + + let esp: usize; + let ebp: usize; + unsafe { asm!("" : "={esp}"(esp), "={ebp}"(ebp):::) }; + println!("esp = {:#x}", esp); + println!("ebp = {:#x}", ebp); + println!("size = {:#X} bytes", ebp - esp); + hexdump(esp, ebp); + flush!(); +} diff --git a/kernel-rs/src/memory/recycle.rs b/kernel-rs/src/memory/recycle.rs new file mode 100644 index 00000000..4772addb --- /dev/null +++ b/kernel-rs/src/memory/recycle.rs @@ -0,0 +1,105 @@ +//! Recycle allocator +//! Uses freed frames if possible, then uses inner allocator + +use alloc::vec::Vec; +use x86::*; +use x86::structures::paging::*; +use super::*; + +pub struct RecycleAllocator { + inner: T, + core: bool, + free: Vec<(usize, usize)>, +} + +impl RecycleAllocator { + pub fn new(inner: T) -> Self { + Self { + inner: inner, + core: true, + free: Vec::new(), + } + } + + pub fn set_core(&mut self, core: bool) { + self.core = core; + } + + fn merge(&mut self, address: usize, count: usize) -> bool { + for i in 0..self.free.len() { + let changed = { + let free = &mut self.free[i]; + if address + count * 4096 == free.0 { + free.0 = address; + free.1 += count; + true + } else if free.0 + free.1 * 4096 == address { + free.1 += count; + true + } else { + false + } + }; + + if changed { + //TODO: Use do not use recursion + let (address, count) = self.free[i]; + if self.merge(address, count) { + self.free.remove(i); + } + return true; + } + } + + false + } +} + +impl FrameAllocator for RecycleAllocator { + fn allocate_frames(&mut self, count: usize) -> Option { + let mut small_i = None; + { + let mut small = (0, 0); + for i in 0..self.free.len() { + let free = self.free[i]; + // Later entries can be removed faster + if free.1 >= count { + if free.1 <= small.1 || small_i.is_none() { + small_i = Some(i); + small = free; + } + } + } + } + + if let Some(i) = small_i { + let (address, remove) = { + let free = &mut self.free[i]; + free.1 -= count; + (free.0 + free.1 * 4096, free.1 == 0) + }; + + if remove { + self.free.remove(i); + } + + //println!("Restoring frame {:?}, {}", frame, count); + Some(PhysFrame::containing_address(PhysAddr::new(address as u32))) + } else { + //println!("No saved frames {}", count); + self.inner.allocate_frames(count) + } + } + + fn deallocate_frames(&mut self, frame: PhysFrame, count: usize) { + // we cant use vec! before the heap has been initialized + if self.core { + self.inner.deallocate_frames(frame, count); + } else { + let address = frame.start_address().as_u32() as usize; + if !self.merge(address, count) { + self.free.push((address, count)); + } + } + } +} diff --git a/kernel-rs/src/memory/stack_allocator.rs b/kernel-rs/src/memory/stack_allocator.rs new file mode 100644 index 00000000..710932d9 --- /dev/null +++ b/kernel-rs/src/memory/stack_allocator.rs @@ -0,0 +1,77 @@ +use x86::structures::paging::*; +use arch::x86::paging::ActivePageTable; +use memory::*; +use core::ops::Range; + +#[derive(Debug)] +pub struct Stack { + pub top: u32, + pub bottom: u32, +} + +impl Stack { + fn new(top: u32, bottom: u32) -> Stack { + assert!(top > bottom); + Stack { top, bottom } + } + + pub fn top(&self) -> u32 { + self.top + } + + pub fn bottom(&self) -> u32 { + self.bottom + } +} + +#[derive(Debug)] +pub struct StackAllocator { + range: Range, +} + +impl StackAllocator { + pub fn new(range: Range) -> StackAllocator { + StackAllocator { range } + } + + pub fn allocate_stack( + &mut self, + active_table: &mut ActivePageTable, + size_in_pages: usize, + ) -> Option { + if size_in_pages == 0 { + return None; /* a zero sized stack makes no sense */ + } + + // clone the range, since we only want to change it on success + let mut range = self.range.clone(); + + // try to allocate the stack pages and a guard page + let guard_page = range.next(); + let stack_start = range.next(); + let stack_end = if size_in_pages == 1 { + stack_start + } else { + // choose the (size_in_pages-2)th element, since index + // starts at 0 and we already allocated the start page + range.nth(size_in_pages - 2) + }; + + match (guard_page, stack_start, stack_end) { + (Some(_), Some(start), Some(end)) => { + // success! write back updated range + self.range = range.clone(); + + // map stack pages to physical frames + for page in range { + active_table.map(page, PageTableFlags::WRITABLE); + } + + // create a new stack + let top_of_stack = end.start_address().as_u32() + PAGE_SIZE as u32; + Some(Stack::new(top_of_stack, start.start_address().as_u32())) + } + _ => None, /* not enough pages */ + } + } +} diff --git a/kernel-rs/src/pci/ide.rs b/kernel-rs/src/pci/ide.rs new file mode 100644 index 00000000..f817db3e --- /dev/null +++ b/kernel-rs/src/pci/ide.rs @@ -0,0 +1,4 @@ +use super::*; + +pub fn init(drive: Pci) { +} diff --git a/kernel-rs/src/pci/mod.rs b/kernel-rs/src/pci/mod.rs new file mode 100644 index 00000000..20652210 --- /dev/null +++ b/kernel-rs/src/pci/mod.rs @@ -0,0 +1,126 @@ +// https://wiki.osdev.org/PCI +use x86::devices::io::{Pio, Io}; +use alloc::vec::*; + +pub mod ide; + +pub static mut PCI_CONFIG_ADDRESS: Pio = Pio::new(0xCF8); +pub static mut PCI_CONFIG_DATA: Pio = Pio::new(0xCFC); + +pub struct Pci { + pub bus: u32, + pub slot: u32, + pub func: u32, + pub device: u16, + pub vendor: u16, + pub class: u8, + pub subclass: u8, + pub header_type: u8, +} + +pub fn lspci() { + for pci in self::get_all() { + println!("{}", pci); + } +} + +pub fn get_all() -> Vec { + let bus = 0; + let mut slot = 0; + let mut pcis: Vec = Vec::new(); + + while let Some(pci)= pci_get(bus, slot, 0) { + if pci.header_type & 0x80 != 0 { + let mut func = 0; + while let Some(pci)= pci_get(bus, slot, func) { + // device has multiple functions + pcis.push(pci); + func += 1; + if func == 7 {break;} + } + } + pcis.push(pci); + slot += 1; + if slot == 32 {break;} + } + pcis +} + +pub fn get_first(class: u8, subclass: u8) -> Option { + let bus = 0; + let mut slot = 0; + + while let Some(pci)= pci_get(bus, slot, 0) { + if pci.class == class && pci.subclass == subclass { + return Some(pci); + } + if pci.header_type & 0x80 != 0 { + let mut func = 0; + while let Some(pci)= pci_get(bus, slot, func) { + // device has multiple functions + if pci.class == class && pci.subclass == subclass { + return Some(pci); + } + func += 1; + if func == 7 {break;} + } + } + slot += 1; + if slot == 32 {break;} + } + None +} + +pub fn pci_get(bus: u32, slot: u32, func: u32) -> Option { + let vendor = pci_config_read_word(bus, slot, func, 0); + if vendor == 0xffff { return None } + let pci = Pci {bus, slot, func, vendor, + device: pci_config_read_word(bus, slot, func, 2), + subclass: pci_config_read_byte(bus, slot, func, 10), + class: pci_config_read_byte(bus, slot, func, 11), + header_type: pci_config_read_byte(bus, slot, func, 14), + }; + Some(pci) +} + +use core::fmt; +impl fmt::Display for Pci { + fn fmt(&self, f: &mut core::fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}.{} {:#x},{:#x} {:#x} {:#x}", + self.bus, self.slot, self.func, + self.class, self.subclass, + self.vendor, self.device); + Ok(()) + } +} + +// pub fn pci_display(bus: u32, slot: u32, function: u32) { +// let vendor = pci_config_read_word(bus, slot, function, 0); +// let device = pci_config_read_word(bus, slot, function, 2); +// println!("{}:{}.{} {:#x},{:#x}: {:#x} {:#x}", +// bus, slot, function, class, subclass, vendor, device); +// } + +pub fn pci_access(bus: u32, slot: u32, func: u32, offset: u32) { + let address = (bus as u32) << 16 + | (slot as u32) << 11 + | (func as u32) << 8 + | (offset as u32) & 0xfc + | 0x80000000; + unsafe { PCI_CONFIG_ADDRESS.write(address); } +} + +pub fn pci_config_read_doubleword(bus: u32, slot: u32, func: u32, offset: u32) -> u32 { + pci_access(bus, slot, func, offset); + unsafe { PCI_CONFIG_DATA.read() } +} + +pub fn pci_config_read_word(bus: u32, slot: u32, func: u32, offset: u32) -> u16 { + pci_access(bus, slot, func, offset); + unsafe { (PCI_CONFIG_DATA.read() >> ((offset & 2) * 8)) as u16 } +} + +pub fn pci_config_read_byte(bus: u32, slot: u32, func: u32, offset: u32) -> u8 { + pci_access(bus, slot, func, offset); + unsafe { (PCI_CONFIG_DATA.read() >> ((offset & 3) * 8)) as u8 } +} diff --git a/kernel-rs/src/scheduling/fifo.rs b/kernel-rs/src/scheduling/fifo.rs new file mode 100644 index 00000000..aea1e4bb --- /dev/null +++ b/kernel-rs/src/scheduling/fifo.rs @@ -0,0 +1,36 @@ +//! simple first come first serve scheduling algorithm +//! unsound for everyday use, a process can decide to +//! hijack the cpu, also it only allows for terminating +//! processes... +//! however it's stupid simple to implement! + +use alloc::collections::vec_deque::VecDeque; + +use super::*; +// use super::process::*; + +pub struct Fifo { + list: VecDeque, + next_pid: i32, +} + +impl Fifo { + pub fn new() -> Fifo { + Fifo { + list: VecDeque::new(), + next_pid: 1, + } + } +} + +impl Scheduler for Fifo { + fn add_task(&mut self, ip: u32) { + let p = Process::new(self.next_pid, ip); + self.list.push_back(p); + self.next_pid += 1; + } + + fn next(&mut self) -> Option { + self.list.pop_front() + } +} diff --git a/kernel-rs/src/scheduling/mod.rs b/kernel-rs/src/scheduling/mod.rs new file mode 100644 index 00000000..0c05f51e --- /dev/null +++ b/kernel-rs/src/scheduling/mod.rs @@ -0,0 +1,56 @@ +mod process; +mod sleep; + +mod fifo; + +use spin::Mutex; +pub use self::process::*; +lazy_static! { + pub static ref SCHEDULER: Mutex = Mutex::new({ + let init_process: u32 = self::init as *const () as u32; + let mut f = fifo::Fifo::new(); + f.add_task(init_process); + f + }); +} + +/// Scheduler algorithm needs to implement this +pub trait Scheduler { + fn add_task(&mut self, ip: u32); + fn next(&mut self) -> Option; +} + +pub fn schedule() { + loop { + if let Some(mut p) = SCHEDULER.lock().next() { + println!("executing {:#?}", p); + flush!(); + unsafe { + SCHEDULER.force_unlock(); + p.execute(); + } + unreachable!(); + } + } +} + +pub fn fork() -> i32 { + let ip; + unsafe { asm!("pop $0" : "=r"(ip) ::: "intel") }; + println!("ip = {:#x}", ip); + flush!(); + unsafe { asm!("push $0" :: "r"(ip) :: "intel") }; + SCHEDULER.lock().add_task(ip); + 0 +} + +pub fn sleep() { + +} + +pub fn init() { + fprintln!("init first line"); + // let i = self::fork(); + // println!("fork={}", i); + fprintln!("init last line"); +} diff --git a/kernel-rs/src/scheduling/process.rs b/kernel-rs/src/scheduling/process.rs new file mode 100644 index 00000000..e09ac959 --- /dev/null +++ b/kernel-rs/src/scheduling/process.rs @@ -0,0 +1,33 @@ +#[derive(Debug)] +pub enum State { + Running, + Ready, + Sleeping(u32), + Blocked(), +} + +#[derive(Debug)] +pub struct Process { + pid: i32, + // this is eip right now + // this will be an elf blob later + ip: u32, + state: State, +} + +impl Process { + pub fn new(pid: i32, ip: u32) -> Process { + Process { + pid, + ip, + state: State::Ready, + } + } + + pub unsafe fn execute(&mut self) { + use super::schedule; + let scheduler_loop = schedule as *const () as u32; + asm!("push $0; push $1; ret" :: "r"(scheduler_loop) ,"r"(self.ip) :: "volatile", "intel"); + unreachable!(); + } +} diff --git a/kernel-rs/src/scheduling/sleep.rs b/kernel-rs/src/scheduling/sleep.rs new file mode 100644 index 00000000..ca453133 --- /dev/null +++ b/kernel-rs/src/scheduling/sleep.rs @@ -0,0 +1,41 @@ +//! sleeping processing are stored in a delta queue +//! separate from other scheduling structures: this +//! way the scheduling algorithms don't have to worry about +//! managing these +//! +//! https://wiki.osdev.org/Blocking_Process + +use alloc::collections::vec_deque::VecDeque; +use super::*; + +struct Sleeper { + process: Process, + // ms + ticks: u32, +} + +/// osdev calls this a delta queue but google doesnt know +struct DeltaQueue { + queue: VecDeque, +} + +impl DeltaQueue { + pub fn insert(&mut self, process: Process, ticks: u32) { + let sleeper = Sleeper { process, ticks }; + } + + /// decreases timer on the list and returns the number + /// of finished sleepers + pub fn tick(&mut self) -> u32 { + let mut i: u32 = 0; + while let Some(link) = self.queue.get_mut(i as usize) { + // find how many links have 0 ticks left + if link.ticks > 0 { + link.ticks -= 1; + break; + } + i += 1; + } + i + } +} diff --git a/kernel-rs/src/time.rs b/kernel-rs/src/time.rs new file mode 100644 index 00000000..ce8cc0aa --- /dev/null +++ b/kernel-rs/src/time.rs @@ -0,0 +1,22 @@ +use spin::Mutex; + +/// Kernel start time, measured in (seconds, microseconds) since Unix epoch +pub static START: Mutex<(u32, u32)> = Mutex::new((0, 0)); +/// Kernel up time, measured in (seconds, microseconds) since `START_TIME` +pub static OFFSET: Mutex<(u32, u32)> = Mutex::new((0, 0)); + +pub fn monotonic() -> (u32, u32) { + *OFFSET.lock() +} + +pub fn realtime() -> (u32, u32) { + let offset = monotonic(); + let start = *START.lock(); + let sum = start.1 + offset.1; + (start.0 + offset.0 + sum / 1_000_000, sum % 1_000_000) +} + +pub fn uptime() { + let offset = self::OFFSET.lock(); + println!("{}s", offset.0 + offset.1 / 1_000_000); +} diff --git a/kernel-rs/src/vga/color.rs b/kernel-rs/src/vga/color.rs new file mode 100644 index 00000000..91bf39c4 --- /dev/null +++ b/kernel-rs/src/vga/color.rs @@ -0,0 +1,30 @@ +#[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)] +pub struct ColorCode(pub u8); + +impl ColorCode { + pub const fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} diff --git a/kernel-rs/src/vga/cursor.rs b/kernel-rs/src/vga/cursor.rs new file mode 100644 index 00000000..575e467f --- /dev/null +++ b/kernel-rs/src/vga/cursor.rs @@ -0,0 +1,30 @@ +// https://wiki.osdev.org/Text_Mode_Cursor +// Protected mode cursor abstraction + +use x86::devices::io::{Io, Pio}; + +pub static mut CURSOR: Cursor = Cursor::new(0x3D4); + +pub struct Cursor { + cmd: Pio, + data: Pio, +} + +impl Cursor { + pub const fn new(port: u16) -> Cursor { + Cursor { + cmd: Pio::new(port), + data: Pio::new(port + 1), + } + } + + pub fn flush(&mut self, position: usize) { + // 14 awaits the rightmost 8bits + self.cmd.write(14); + self.data.write((position >> 8) as u8 & 0xff); + + // 15 awaits the leftmost 8bits + self.cmd.write(15); + self.data.write((position >> 0) as u8 & 0xff); + } +} diff --git a/kernel-rs/src/vga/mod.rs b/kernel-rs/src/vga/mod.rs new file mode 100644 index 00000000..2d8834ab --- /dev/null +++ b/kernel-rs/src/vga/mod.rs @@ -0,0 +1,160 @@ +pub mod color; +pub mod cursor; + +pub use self::color::{Color, ColorCode}; +use self::cursor::CURSOR; + +pub static mut VGA: Writer = self::Writer::new(); + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +// print wrapper macro around vga +#[allow(unused_macros)] +macro_rules! print { + ($($arg:tt)*) => ({ + $crate::vga::print(format_args!($($arg)*)); + }); +} + +// flushed print +#[allow(unused_macros)] +macro_rules! fprint { + ($($arg:tt)*) => ({ + print!($($arg)*); + flush!(); + }); +} + +// print with a line feed +#[allow(unused_macros)] +macro_rules! println { + () => ({print!("\n")}); + ($fmt:expr) => (print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); +} + +// flushed println +#[allow(unused_macros)] +macro_rules! fprintln { + ($($arg:tt)*) => ({ + println!($($arg)*); + flush!(); + }); +} + +macro_rules! flush { + () => (#[allow(unused_unsafe)] unsafe { $crate::vga::VGA.flush() }); +} + +macro_rules! set_color { + () => (unsafe { $crate::vga::VGA.color_code = + $crate::vga::ColorCode::new($crate::vga::Color::White, $crate::vga::Color::Black)} ); + ($fg:ident) => (unsafe { $crate::vga::VGA.color_code = + $crate::vga::ColorCode::new($crate::vga::Color::$fg, $crate::vga::Color::Black)} ); + ($fg:ident, $bg:ident) => (unsafe { $crate::vga::VGA.color_code = + $crate::vga::ColorCode::new($crate::vga::Color::$fg, $crate::vga::Color::$bg)} ); +} + +pub fn print(args: fmt::Arguments) { + use core::fmt::Write; + unsafe { + self::VGA.write_fmt(args).unwrap(); + } +} + +const BUFFER_ROWS: usize = 25; +const BUFFER_COLS: usize = 80 * 2; + +pub struct Writer { + pub buffer_pos: usize, + pub color_code: ColorCode, + buffer: [u8; BUFFER_ROWS * BUFFER_COLS], +} + +impl Writer { + pub const fn new() -> Writer { + Writer { + buffer_pos: 0, + color_code: ColorCode::new(Color::White, Color::Black), + buffer: [0; BUFFER_ROWS * BUFFER_COLS], + } + } + + pub fn erase_byte(&mut self) { + self.buffer_pos -= 2; + let i = self.buffer_pos; + self.buffer[i] = b' '; + self.buffer[i + 1] = self.color_code.0; + self.flush(); + } + + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => { + let current_line = self.buffer_pos / (BUFFER_COLS); + self.buffer_pos = (current_line + 1) * BUFFER_COLS; + } + byte => { + self.buffer[self.buffer_pos] = byte; + self.buffer[self.buffer_pos + 1] = self.color_code.0; + self.buffer_pos += 2; + } + } + + if self.buffer_pos >= self.buffer.len() { + self.scroll(); + self.flush(); + } + // flushing here is correct but slow + // self.flush(); + } + + pub fn write_str(&mut self, s: &str) { + for byte in s.bytes() { + self.write_byte(byte) + } + } + + fn flush_cursor(&self) { + unsafe { CURSOR.flush(self.buffer_pos / 2); } + } + + pub fn flush(&mut self) { + let slice = unsafe { core::slice::from_raw_parts_mut(0xb8000 as *mut u8, 4000) }; + slice.as_mut().clone_from_slice(&self.buffer); + self.flush_cursor(); + } + + fn scroll(&mut self) { + for row in 1..BUFFER_ROWS { + for col in 0..BUFFER_COLS { + let prev_position = ((row - 1) * BUFFER_COLS) + col; + let current_position = (row * BUFFER_COLS) + col; + self.buffer[prev_position] = self.buffer[current_position]; + } + } + + for col in (0..BUFFER_COLS / 2).map(|x| x * 2) { + self.buffer[((BUFFER_ROWS - 1) * BUFFER_COLS) + (col)] = b' '; + self.buffer[((BUFFER_ROWS - 1) * BUFFER_COLS) + (col + 1)] = + ColorCode::new(Color::White, Color::Black).0; + } + self.buffer_pos = (BUFFER_ROWS - 1) * BUFFER_COLS; + } +} + +// trait needed by formatting macros +use core::fmt; +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + self.write_byte(byte) + } + Ok(()) + } +} diff --git a/kernel-rs/x86 b/kernel-rs/x86 new file mode 160000 index 00000000..229c4acb --- /dev/null +++ b/kernel-rs/x86 @@ -0,0 +1 @@ +Subproject commit 229c4acbbe81bf4837bd05714b99614ada9dd2b3 diff --git a/kernel-rs/x86-bluesnow.json b/kernel-rs/x86-bluesnow.json new file mode 100644 index 00000000..36bdde97 --- /dev/null +++ b/kernel-rs/x86-bluesnow.json @@ -0,0 +1,17 @@ +{ + "arch": "x86", + + "_comment": "http://llvm.org/docs/LangRef.html#data-layout", + "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + + "llvm-target": "i386-unknown-none", + "linker-flavor": "gcc", + "os": "none", + "target-endian": "little", + "target-pointer-width": "32", + "target-c-int-width": "32", + "features": "-mmx,-fxsr,-sse,-sse2,+soft-float", + "eliminate-frame-pointer": false, + "disable-redzone": true, + "panic-strategy": "abort" +}